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