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