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