• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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