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