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