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