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