1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3; 18 19 import android.appwidget.AppWidgetManager; 20 import android.appwidget.AppWidgetProviderInfo; 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.SharedPreferences; 25 import android.content.pm.ActivityInfo; 26 import android.content.pm.LauncherActivityInfo; 27 import android.content.pm.PackageManager; 28 import android.graphics.Bitmap; 29 import android.graphics.BitmapFactory; 30 import android.os.Handler; 31 import android.os.Looper; 32 import android.os.Message; 33 import android.os.Parcelable; 34 import android.os.Process; 35 import android.os.UserHandle; 36 import android.text.TextUtils; 37 import android.util.Base64; 38 import android.util.Log; 39 import android.util.Pair; 40 41 import com.android.launcher3.compat.LauncherAppsCompat; 42 import com.android.launcher3.compat.UserManagerCompat; 43 import com.android.launcher3.graphics.BitmapInfo; 44 import com.android.launcher3.graphics.LauncherIcons; 45 import com.android.launcher3.shortcuts.DeepShortcutManager; 46 import com.android.launcher3.shortcuts.ShortcutInfoCompat; 47 import com.android.launcher3.shortcuts.ShortcutKey; 48 import com.android.launcher3.util.PackageManagerHelper; 49 import com.android.launcher3.util.Preconditions; 50 import com.android.launcher3.util.Provider; 51 import com.android.launcher3.util.Thunk; 52 53 import org.json.JSONException; 54 import org.json.JSONObject; 55 import org.json.JSONStringer; 56 57 import java.net.URISyntaxException; 58 import java.util.ArrayList; 59 import java.util.Arrays; 60 import java.util.HashSet; 61 import java.util.Iterator; 62 import java.util.List; 63 import java.util.Set; 64 65 public class InstallShortcutReceiver extends BroadcastReceiver { 66 67 private static final int MSG_ADD_TO_QUEUE = 1; 68 private static final int MSG_FLUSH_QUEUE = 2; 69 70 public static final int FLAG_ACTIVITY_PAUSED = 1; 71 public static final int FLAG_LOADER_RUNNING = 2; 72 public static final int FLAG_DRAG_AND_DROP = 4; 73 public static final int FLAG_BULK_ADD = 4; 74 75 // Determines whether to defer installing shortcuts immediately until 76 // processAllPendingInstalls() is called. 77 private static int sInstallQueueDisabledFlags = 0; 78 79 private static final String TAG = "InstallShortcutReceiver"; 80 private static final boolean DBG = false; 81 82 private static final String ACTION_INSTALL_SHORTCUT = 83 "com.android.launcher.action.INSTALL_SHORTCUT"; 84 85 private static final String LAUNCH_INTENT_KEY = "intent.launch"; 86 private static final String NAME_KEY = "name"; 87 private static final String ICON_KEY = "icon"; 88 private static final String ICON_RESOURCE_NAME_KEY = "iconResource"; 89 private static final String ICON_RESOURCE_PACKAGE_NAME_KEY = "iconResourcePackage"; 90 91 private static final String APP_SHORTCUT_TYPE_KEY = "isAppShortcut"; 92 private static final String DEEPSHORTCUT_TYPE_KEY = "isDeepShortcut"; 93 private static final String APP_WIDGET_TYPE_KEY = "isAppWidget"; 94 private static final String USER_HANDLE_KEY = "userHandle"; 95 96 // The set of shortcuts that are pending install 97 private static final String APPS_PENDING_INSTALL = "apps_to_install"; 98 99 public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450; 100 public static final int NEW_SHORTCUT_STAGGER_DELAY = 85; 101 102 private static final Handler sHandler = new Handler(LauncherModel.getWorkerLooper()) { 103 104 @Override 105 public void handleMessage(Message msg) { 106 switch (msg.what) { 107 case MSG_ADD_TO_QUEUE: { 108 Pair<Context, PendingInstallShortcutInfo> pair = 109 (Pair<Context, PendingInstallShortcutInfo>) msg.obj; 110 String encoded = pair.second.encodeToString(); 111 SharedPreferences prefs = Utilities.getPrefs(pair.first); 112 Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null); 113 strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1); 114 strings.add(encoded); 115 prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply(); 116 return; 117 } 118 case MSG_FLUSH_QUEUE: { 119 Context context = (Context) msg.obj; 120 LauncherModel model = LauncherAppState.getInstance(context).getModel(); 121 if (model.getCallback() == null) { 122 // Launcher not loaded 123 return; 124 } 125 126 ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>(); 127 SharedPreferences prefs = Utilities.getPrefs(context); 128 Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null); 129 if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings); 130 if (strings == null) { 131 return; 132 } 133 134 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); 135 for (String encoded : strings) { 136 PendingInstallShortcutInfo info = decode(encoded, context); 137 if (info == null) { 138 continue; 139 } 140 141 String pkg = getIntentPackage(info.launchIntent); 142 if (!TextUtils.isEmpty(pkg) 143 && !launcherApps.isPackageEnabledForProfile(pkg, info.user)) { 144 if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: " 145 + info.launchIntent); 146 continue; 147 } 148 149 // Generate a shortcut info to add into the model 150 installQueue.add(info.getItemInfo()); 151 } 152 prefs.edit().remove(APPS_PENDING_INSTALL).apply(); 153 if (!installQueue.isEmpty()) { 154 model.addAndBindAddedWorkspaceItems(installQueue); 155 } 156 return; 157 } 158 } 159 } 160 }; 161 162 public static void removeFromInstallQueue(Context context, HashSet<String> packageNames, 163 UserHandle user) { 164 if (packageNames.isEmpty()) { 165 return; 166 } 167 Preconditions.assertWorkerThread(); 168 169 SharedPreferences sp = Utilities.getPrefs(context); 170 Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null); 171 if (DBG) { 172 Log.d(TAG, "APPS_PENDING_INSTALL: " + strings 173 + ", removing packages: " + packageNames); 174 } 175 if (Utilities.isEmpty(strings)) { 176 return; 177 } 178 Set<String> newStrings = new HashSet<>(strings); 179 Iterator<String> newStringsIter = newStrings.iterator(); 180 while (newStringsIter.hasNext()) { 181 String encoded = newStringsIter.next(); 182 try { 183 Decoder decoder = new Decoder(encoded, context); 184 if (packageNames.contains(getIntentPackage(decoder.launcherIntent)) && 185 user.equals(decoder.user)) { 186 newStringsIter.remove(); 187 } 188 } catch (JSONException | URISyntaxException e) { 189 Log.d(TAG, "Exception reading shortcut to add: " + e); 190 newStringsIter.remove(); 191 } 192 } 193 sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply(); 194 } 195 196 public void onReceive(Context context, Intent data) { 197 if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) { 198 return; 199 } 200 PendingInstallShortcutInfo info = createPendingInfo(context, data); 201 if (info != null) { 202 if (!info.isLauncherActivity()) { 203 // Since its a custom shortcut, verify that it is safe to launch. 204 if (!new PackageManagerHelper(context).hasPermissionForActivity( 205 info.launchIntent, null)) { 206 // Target cannot be launched, or requires some special permission to launch 207 Log.e(TAG, "Ignoring malicious intent " + info.launchIntent.toUri(0)); 208 return; 209 } 210 } 211 queuePendingShortcutInfo(info, context); 212 } 213 } 214 215 /** 216 * @return true is the extra is either null or is of type {@param type} 217 */ 218 private static boolean isValidExtraType(Intent intent, String key, Class type) { 219 Object extra = intent.getParcelableExtra(key); 220 return extra == null || type.isInstance(extra); 221 } 222 223 /** 224 * Verifies the intent and creates a {@link PendingInstallShortcutInfo} 225 */ 226 private static PendingInstallShortcutInfo createPendingInfo(Context context, Intent data) { 227 if (!isValidExtraType(data, Intent.EXTRA_SHORTCUT_INTENT, Intent.class) || 228 !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON_RESOURCE, 229 Intent.ShortcutIconResource.class)) || 230 !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON, Bitmap.class))) { 231 232 if (DBG) Log.e(TAG, "Invalid install shortcut intent"); 233 return null; 234 } 235 236 PendingInstallShortcutInfo info = new PendingInstallShortcutInfo( 237 data, Process.myUserHandle(), context); 238 if (info.launchIntent == null || info.label == null) { 239 if (DBG) Log.e(TAG, "Invalid install shortcut intent"); 240 return null; 241 } 242 243 return convertToLauncherActivityIfPossible(info); 244 } 245 246 public static ShortcutInfo fromShortcutIntent(Context context, Intent data) { 247 PendingInstallShortcutInfo info = createPendingInfo(context, data); 248 return info == null ? null : (ShortcutInfo) info.getItemInfo().first; 249 } 250 251 public static ShortcutInfo fromActivityInfo(LauncherActivityInfo info, Context context) { 252 return (ShortcutInfo) (new PendingInstallShortcutInfo(info, context).getItemInfo().first); 253 } 254 255 public static void queueShortcut(ShortcutInfoCompat info, Context context) { 256 queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context); 257 } 258 259 public static void queueWidget(AppWidgetProviderInfo info, int widgetId, Context context) { 260 queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId, context), context); 261 } 262 263 public static void queueActivityInfo(LauncherActivityInfo activity, Context context) { 264 queuePendingShortcutInfo(new PendingInstallShortcutInfo(activity, context), context); 265 } 266 267 public static HashSet<ShortcutKey> getPendingShortcuts(Context context) { 268 HashSet<ShortcutKey> result = new HashSet<>(); 269 270 Set<String> strings = Utilities.getPrefs(context).getStringSet(APPS_PENDING_INSTALL, null); 271 if (Utilities.isEmpty(strings)) { 272 return result; 273 } 274 275 for (String encoded : strings) { 276 try { 277 Decoder decoder = new Decoder(encoded, context); 278 if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) { 279 result.add(ShortcutKey.fromIntent(decoder.launcherIntent, decoder.user)); 280 } 281 } catch (JSONException | URISyntaxException e) { 282 Log.d(TAG, "Exception reading shortcut to add: " + e); 283 } 284 } 285 return result; 286 } 287 288 private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) { 289 // Queue the item up for adding if launcher has not loaded properly yet 290 Message.obtain(sHandler, MSG_ADD_TO_QUEUE, Pair.create(context, info)).sendToTarget(); 291 flushInstallQueue(context); 292 } 293 294 public static void enableInstallQueue(int flag) { 295 sInstallQueueDisabledFlags |= flag; 296 } 297 public static void disableAndFlushInstallQueue(int flag, Context context) { 298 sInstallQueueDisabledFlags &= ~flag; 299 flushInstallQueue(context); 300 } 301 302 static void flushInstallQueue(Context context) { 303 if (sInstallQueueDisabledFlags != 0) { 304 return; 305 } 306 Message.obtain(sHandler, MSG_FLUSH_QUEUE, context.getApplicationContext()).sendToTarget(); 307 } 308 309 /** 310 * Ensures that we have a valid, non-null name. If the provided name is null, we will return 311 * the application name instead. 312 */ 313 @Thunk static CharSequence ensureValidName(Context context, Intent intent, CharSequence name) { 314 if (name == null) { 315 try { 316 PackageManager pm = context.getPackageManager(); 317 ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0); 318 name = info.loadLabel(pm); 319 } catch (PackageManager.NameNotFoundException nnfe) { 320 return ""; 321 } 322 } 323 return name; 324 } 325 326 private static class PendingInstallShortcutInfo { 327 328 final LauncherActivityInfo activityInfo; 329 final ShortcutInfoCompat shortcutInfo; 330 final AppWidgetProviderInfo providerInfo; 331 332 final Intent data; 333 final Context mContext; 334 final Intent launchIntent; 335 final String label; 336 final UserHandle user; 337 338 /** 339 * Initializes a PendingInstallShortcutInfo received from a different app. 340 */ 341 public PendingInstallShortcutInfo(Intent data, UserHandle user, Context context) { 342 activityInfo = null; 343 shortcutInfo = null; 344 providerInfo = null; 345 346 this.data = data; 347 this.user = user; 348 mContext = context; 349 350 launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); 351 label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); 352 353 } 354 355 /** 356 * Initializes a PendingInstallShortcutInfo to represent a launcher target. 357 */ 358 public PendingInstallShortcutInfo(LauncherActivityInfo info, Context context) { 359 activityInfo = info; 360 shortcutInfo = null; 361 providerInfo = null; 362 363 data = null; 364 user = info.getUser(); 365 mContext = context; 366 367 launchIntent = AppInfo.makeLaunchIntent(info); 368 label = info.getLabel().toString(); 369 } 370 371 /** 372 * Initializes a PendingInstallShortcutInfo to represent a launcher target. 373 */ 374 public PendingInstallShortcutInfo(ShortcutInfoCompat info, Context context) { 375 activityInfo = null; 376 shortcutInfo = info; 377 providerInfo = null; 378 379 data = null; 380 mContext = context; 381 user = info.getUserHandle(); 382 383 launchIntent = info.makeIntent(); 384 label = info.getShortLabel().toString(); 385 } 386 387 /** 388 * Initializes a PendingInstallShortcutInfo to represent a launcher target. 389 */ 390 public PendingInstallShortcutInfo( 391 AppWidgetProviderInfo info, int widgetId, Context context) { 392 activityInfo = null; 393 shortcutInfo = null; 394 providerInfo = info; 395 396 data = null; 397 mContext = context; 398 user = info.getProfile(); 399 400 launchIntent = new Intent().setComponent(info.provider) 401 .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); 402 label = info.label; 403 } 404 405 public String encodeToString() { 406 try { 407 if (activityInfo != null) { 408 // If it a launcher target, we only need component name, and user to 409 // recreate this. 410 return new JSONStringer() 411 .object() 412 .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0)) 413 .key(APP_SHORTCUT_TYPE_KEY).value(true) 414 .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext) 415 .getSerialNumberForUser(user)) 416 .endObject().toString(); 417 } else if (shortcutInfo != null) { 418 // If it a launcher target, we only need component name, and user to 419 // recreate this. 420 return new JSONStringer() 421 .object() 422 .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0)) 423 .key(DEEPSHORTCUT_TYPE_KEY).value(true) 424 .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext) 425 .getSerialNumberForUser(user)) 426 .endObject().toString(); 427 } else if (providerInfo != null) { 428 // If it a launcher target, we only need component name, and user to 429 // recreate this. 430 return new JSONStringer() 431 .object() 432 .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0)) 433 .key(APP_WIDGET_TYPE_KEY).value(true) 434 .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext) 435 .getSerialNumberForUser(user)) 436 .endObject().toString(); 437 } 438 439 if (launchIntent.getAction() == null) { 440 launchIntent.setAction(Intent.ACTION_VIEW); 441 } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) && 442 launchIntent.getCategories() != null && 443 launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { 444 launchIntent.addFlags( 445 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 446 } 447 448 // This name is only used for comparisons and notifications, so fall back to activity 449 // name if not supplied 450 String name = ensureValidName(mContext, launchIntent, label).toString(); 451 Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); 452 Intent.ShortcutIconResource iconResource = 453 data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); 454 455 // Only encode the parameters which are supported by the API. 456 JSONStringer json = new JSONStringer() 457 .object() 458 .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0)) 459 .key(NAME_KEY).value(name); 460 if (icon != null) { 461 byte[] iconByteArray = Utilities.flattenBitmap(icon); 462 json = json.key(ICON_KEY).value( 463 Base64.encodeToString( 464 iconByteArray, 0, iconByteArray.length, Base64.DEFAULT)); 465 } 466 if (iconResource != null) { 467 json = json.key(ICON_RESOURCE_NAME_KEY).value(iconResource.resourceName); 468 json = json.key(ICON_RESOURCE_PACKAGE_NAME_KEY) 469 .value(iconResource.packageName); 470 } 471 return json.endObject().toString(); 472 } catch (JSONException e) { 473 Log.d(TAG, "Exception when adding shortcut: " + e); 474 return null; 475 } 476 } 477 478 public Pair<ItemInfo, Object> getItemInfo() { 479 if (activityInfo != null) { 480 AppInfo appInfo = new AppInfo(mContext, activityInfo, user); 481 final LauncherAppState app = LauncherAppState.getInstance(mContext); 482 // Set default values until proper values is loaded. 483 appInfo.title = ""; 484 app.getIconCache().getDefaultIcon(user).applyTo(appInfo); 485 final ShortcutInfo si = appInfo.makeShortcut(); 486 if (Looper.myLooper() == LauncherModel.getWorkerLooper()) { 487 app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */); 488 } else { 489 app.getModel().updateAndBindShortcutInfo(new Provider<ShortcutInfo>() { 490 @Override 491 public ShortcutInfo get() { 492 app.getIconCache().getTitleAndIcon( 493 si, activityInfo, false /* useLowResIcon */); 494 return si; 495 } 496 }); 497 } 498 return Pair.create((ItemInfo) si, (Object) activityInfo); 499 } else if (shortcutInfo != null) { 500 ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext); 501 LauncherIcons li = LauncherIcons.obtain(mContext); 502 li.createShortcutIcon(shortcutInfo).applyTo(si); 503 li.recycle(); 504 return Pair.create((ItemInfo) si, (Object) shortcutInfo); 505 } else if (providerInfo != null) { 506 LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo 507 .fromProviderInfo(mContext, providerInfo); 508 LauncherAppWidgetInfo widgetInfo = new LauncherAppWidgetInfo( 509 launchIntent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0), 510 info.provider); 511 InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext); 512 widgetInfo.minSpanX = info.minSpanX; 513 widgetInfo.minSpanY = info.minSpanY; 514 widgetInfo.spanX = Math.min(info.spanX, idp.numColumns); 515 widgetInfo.spanY = Math.min(info.spanY, idp.numRows); 516 return Pair.create((ItemInfo) widgetInfo, (Object) providerInfo); 517 } else { 518 ShortcutInfo si = createShortcutInfo(data, LauncherAppState.getInstance(mContext)); 519 return Pair.create((ItemInfo) si, null); 520 } 521 } 522 523 public boolean isLauncherActivity() { 524 return activityInfo != null; 525 } 526 } 527 528 private static String getIntentPackage(Intent intent) { 529 return intent.getComponent() == null 530 ? intent.getPackage() : intent.getComponent().getPackageName(); 531 } 532 533 private static PendingInstallShortcutInfo decode(String encoded, Context context) { 534 try { 535 Decoder decoder = new Decoder(encoded, context); 536 if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) { 537 LauncherActivityInfo info = LauncherAppsCompat.getInstance(context) 538 .resolveActivity(decoder.launcherIntent, decoder.user); 539 return info == null ? null : new PendingInstallShortcutInfo(info, context); 540 } else if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) { 541 DeepShortcutManager sm = DeepShortcutManager.getInstance(context); 542 List<ShortcutInfoCompat> si = sm.queryForFullDetails( 543 decoder.launcherIntent.getPackage(), 544 Arrays.asList(decoder.launcherIntent.getStringExtra( 545 ShortcutInfoCompat.EXTRA_SHORTCUT_ID)), 546 decoder.user); 547 if (si.isEmpty()) { 548 return null; 549 } else { 550 return new PendingInstallShortcutInfo(si.get(0), context); 551 } 552 } else if (decoder.optBoolean(APP_WIDGET_TYPE_KEY)) { 553 int widgetId = decoder.launcherIntent 554 .getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0); 555 AppWidgetProviderInfo info = AppWidgetManager.getInstance(context) 556 .getAppWidgetInfo(widgetId); 557 if (info == null || !info.provider.equals(decoder.launcherIntent.getComponent()) || 558 !info.getProfile().equals(decoder.user)) { 559 return null; 560 } 561 return new PendingInstallShortcutInfo(info, widgetId, context); 562 } 563 564 Intent data = new Intent(); 565 data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, decoder.launcherIntent); 566 data.putExtra(Intent.EXTRA_SHORTCUT_NAME, decoder.getString(NAME_KEY)); 567 568 String iconBase64 = decoder.optString(ICON_KEY); 569 String iconResourceName = decoder.optString(ICON_RESOURCE_NAME_KEY); 570 String iconResourcePackageName = decoder.optString(ICON_RESOURCE_PACKAGE_NAME_KEY); 571 if (iconBase64 != null && !iconBase64.isEmpty()) { 572 byte[] iconArray = Base64.decode(iconBase64, Base64.DEFAULT); 573 Bitmap b = BitmapFactory.decodeByteArray(iconArray, 0, iconArray.length); 574 data.putExtra(Intent.EXTRA_SHORTCUT_ICON, b); 575 } else if (iconResourceName != null && !iconResourceName.isEmpty()) { 576 Intent.ShortcutIconResource iconResource = 577 new Intent.ShortcutIconResource(); 578 iconResource.resourceName = iconResourceName; 579 iconResource.packageName = iconResourcePackageName; 580 data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); 581 } 582 583 return new PendingInstallShortcutInfo(data, decoder.user, context); 584 } catch (JSONException | URISyntaxException e) { 585 Log.d(TAG, "Exception reading shortcut to add: " + e); 586 } 587 return null; 588 } 589 590 private static class Decoder extends JSONObject { 591 public final Intent launcherIntent; 592 public final UserHandle user; 593 594 private Decoder(String encoded, Context context) throws JSONException, URISyntaxException { 595 super(encoded); 596 launcherIntent = Intent.parseUri(getString(LAUNCH_INTENT_KEY), 0); 597 user = has(USER_HANDLE_KEY) ? UserManagerCompat.getInstance(context) 598 .getUserForSerialNumber(getLong(USER_HANDLE_KEY)) 599 : Process.myUserHandle(); 600 if (user == null) { 601 throw new JSONException("Invalid user"); 602 } 603 } 604 } 605 606 /** 607 * Tries to create a new PendingInstallShortcutInfo which represents the same target, 608 * but is an app target and not a shortcut. 609 * @return the newly created info or the original one. 610 */ 611 private static PendingInstallShortcutInfo convertToLauncherActivityIfPossible( 612 PendingInstallShortcutInfo original) { 613 if (original.isLauncherActivity()) { 614 // Already an activity target 615 return original; 616 } 617 if (!Utilities.isLauncherAppTarget(original.launchIntent)) { 618 return original; 619 } 620 621 LauncherActivityInfo info = LauncherAppsCompat.getInstance(original.mContext) 622 .resolveActivity(original.launchIntent, original.user); 623 if (info == null) { 624 return original; 625 } 626 // Ignore any conflicts in the label name, as that can change based on locale. 627 return new PendingInstallShortcutInfo(info, original.mContext); 628 } 629 630 private static ShortcutInfo createShortcutInfo(Intent data, LauncherAppState app) { 631 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); 632 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); 633 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); 634 635 if (intent == null) { 636 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null 637 Log.e(TAG, "Can't construct ShorcutInfo with null intent"); 638 return null; 639 } 640 641 final ShortcutInfo info = new ShortcutInfo(); 642 643 // Only support intents for current user for now. Intents sent from other 644 // users wouldn't get here without intent forwarding anyway. 645 info.user = Process.myUserHandle(); 646 647 BitmapInfo iconInfo = null; 648 LauncherIcons li = LauncherIcons.obtain(app.getContext()); 649 if (bitmap instanceof Bitmap) { 650 iconInfo = li.createIconBitmap((Bitmap) bitmap); 651 } else { 652 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); 653 if (extra instanceof Intent.ShortcutIconResource) { 654 info.iconResource = (Intent.ShortcutIconResource) extra; 655 iconInfo = li.createIconBitmap(info.iconResource); 656 } 657 } 658 li.recycle(); 659 660 if (iconInfo == null) { 661 iconInfo = app.getIconCache().getDefaultIcon(info.user); 662 } 663 iconInfo.applyTo(info); 664 665 info.title = Utilities.trim(name); 666 info.contentDescription = UserManagerCompat.getInstance(app.getContext()) 667 .getBadgedLabelForUser(info.title, info.user); 668 info.intent = intent; 669 return info; 670 } 671 672 } 673