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