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