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