blob: ea59fff724eb61962c19f0b2595c24c90a17f6aa [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
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
Daniel Sandler325dc232013-06-05 22:57:57 -040017package com.android.launcher3;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Sunny Goyal3be633b2016-12-08 09:59:25 -080019import android.appwidget.AppWidgetManager;
20import android.appwidget.AppWidgetProviderInfo;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080021import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
Winson Chungf0c6ae02012-03-21 16:10:31 -070024import android.content.SharedPreferences;
Winson Chungc3a747a2012-02-29 10:56:19 -080025import android.content.pm.ActivityInfo;
Sunny Goyal3e9be432017-01-05 15:22:41 -080026import android.content.pm.LauncherActivityInfo;
Winson Chungc3a747a2012-02-29 10:56:19 -080027import android.content.pm.PackageManager;
Michael Jurka48c7a932013-05-14 20:17:58 +020028import android.graphics.Bitmap;
29import android.graphics.BitmapFactory;
Sunny Goyal91498ab2017-10-05 15:57:40 -070030import android.os.Handler;
Sunny Goyal1cc1c9a2017-01-06 16:32:57 -080031import android.os.Looper;
Sunny Goyal91498ab2017-10-05 15:57:40 -070032import android.os.Message;
Sunny Goyalaaf86fe2017-01-05 21:50:27 -080033import android.os.Parcelable;
Sunny Goyal7c74e4a2016-12-15 15:53:17 -080034import android.os.Process;
35import android.os.UserHandle;
Nilesh Agrawaldff0bfe2013-12-17 15:20:50 -080036import android.text.TextUtils;
Michael Jurka48c7a932013-05-14 20:17:58 +020037import android.util.Base64;
38import android.util.Log;
Sunny Goyala474a9b2017-05-04 16:47:11 -070039import android.util.Pair;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080040
Sunny Goyale0f58d72014-11-10 18:05:31 -080041import com.android.launcher3.compat.LauncherAppsCompat;
Sunny Goyale0f58d72014-11-10 18:05:31 -080042import com.android.launcher3.compat.UserManagerCompat;
Hyunyoung Song48cb7bc2018-09-25 17:03:34 -070043import com.android.launcher3.icons.BitmapInfo;
Sunny Goyale62d2bb2018-11-06 10:28:37 -080044import com.android.launcher3.icons.GraphicsUtils;
Hyunyoung Song48cb7bc2018-09-25 17:03:34 -070045import com.android.launcher3.icons.LauncherIcons;
Sunny Goyalf75baa92016-11-22 03:23:51 +053046import com.android.launcher3.shortcuts.DeepShortcutManager;
47import com.android.launcher3.shortcuts.ShortcutInfoCompat;
48import com.android.launcher3.shortcuts.ShortcutKey;
Sunny Goyalfb5096d2016-09-08 14:32:06 -070049import com.android.launcher3.util.PackageManagerHelper;
Sunny Goyal2bcbe132016-11-16 09:23:42 -080050import com.android.launcher3.util.Preconditions;
Adam Cohen091440a2015-03-18 14:16:05 -070051import com.android.launcher3.util.Thunk;
Adam Cohena28b78e2014-05-20 17:03:04 -070052
Sunny Goyale0f58d72014-11-10 18:05:31 -080053import org.json.JSONException;
Michael Jurka34c2e6c2013-12-13 16:07:45 +010054import org.json.JSONObject;
55import org.json.JSONStringer;
Michael Jurka34c2e6c2013-12-13 16:07:45 +010056
Sunny Goyale0f58d72014-11-10 18:05:31 -080057import java.net.URISyntaxException;
Winson Chunge428e292012-01-10 16:20:33 -080058import java.util.ArrayList;
Sunny Goyalf75baa92016-11-22 03:23:51 +053059import java.util.Arrays;
Winson Chungf0c6ae02012-03-21 16:10:31 -070060import java.util.HashSet;
Winson Chungf561bdf2012-05-03 11:20:19 -070061import java.util.Iterator;
Sunny Goyal2bcbe132016-11-16 09:23:42 -080062import java.util.List;
Winson Chungf0c6ae02012-03-21 16:10:31 -070063import java.util.Set;
Winson Chunge428e292012-01-10 16:20:33 -080064
The Android Open Source Project31dd5032009-03-03 19:32:27 -080065public class InstallShortcutReceiver extends BroadcastReceiver {
Sunny Goyala474a9b2017-05-04 16:47:11 -070066
Sunny Goyal91498ab2017-10-05 15:57:40 -070067 private static final int MSG_ADD_TO_QUEUE = 1;
68 private static final int MSG_FLUSH_QUEUE = 2;
69
Sunny Goyala474a9b2017-05-04 16:47:11 -070070 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
Bjorn Bringert4e871a22013-10-17 13:50:20 +010079 private static final String TAG = "InstallShortcutReceiver";
80 private static final boolean DBG = false;
81
Sunny Goyal2350bc92014-10-14 16:42:54 -070082 private static final String ACTION_INSTALL_SHORTCUT =
Winson Chung94d67682013-09-25 16:29:40 -070083 "com.android.launcher.action.INSTALL_SHORTCUT";
Winson Chungf0c6ae02012-03-21 16:10:31 -070084
Sunny Goyal2350bc92014-10-14 16:42:54 -070085 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";
Sunny Goyale0f58d72014-11-10 18:05:31 -080090
91 private static final String APP_SHORTCUT_TYPE_KEY = "isAppShortcut";
Sunny Goyalf75baa92016-11-22 03:23:51 +053092 private static final String DEEPSHORTCUT_TYPE_KEY = "isDeepShortcut";
Sunny Goyal3be633b2016-12-08 09:59:25 -080093 private static final String APP_WIDGET_TYPE_KEY = "isAppWidget";
Sunny Goyale0f58d72014-11-10 18:05:31 -080094 private static final String USER_HANDLE_KEY = "userHandle";
95
Michael Jurka48c7a932013-05-14 20:17:58 +020096 // The set of shortcuts that are pending install
Sunny Goyal2350bc92014-10-14 16:42:54 -070097 private static final String APPS_PENDING_INSTALL = "apps_to_install";
Michael Jurka48c7a932013-05-14 20:17:58 +020098
Winson Chungf0c6ae02012-03-21 16:10:31 -070099 public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
Winson Chung997a9232013-07-24 15:33:46 -0700100 public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
Winson Chungf0c6ae02012-03-21 16:10:31 -0700101
Sunny Goyal91498ab2017-10-05 15:57:40 -0700102 private static final Handler sHandler = new Handler(LauncherModel.getWorkerLooper()) {
Michael Jurka48c7a932013-05-14 20:17:58 +0200103
Sunny Goyal91498ab2017-10-05 15:57:40 -0700104 @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 }
Michael Jurka48c7a932013-05-14 20:17:58 +0200158 }
159 }
Sunny Goyal91498ab2017-10-05 15:57:40 -0700160 };
Michael Jurka48c7a932013-05-14 20:17:58 +0200161
Sunny Goyal3bbbabc2016-03-15 09:16:30 -0700162 public static void removeFromInstallQueue(Context context, HashSet<String> packageNames,
Sunny Goyal7c74e4a2016-12-15 15:53:17 -0800163 UserHandle user) {
Winson Chungdf95eb12013-10-16 14:57:07 -0700164 if (packageNames.isEmpty()) {
165 return;
166 }
Sunny Goyal91498ab2017-10-05 15:57:40 -0700167 Preconditions.assertWorkerThread();
168
Sunny Goyalf7258242015-10-19 16:59:07 -0700169 SharedPreferences sp = Utilities.getPrefs(context);
Sunny Goyal91498ab2017-10-05 15:57:40 -0700170 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)) {
Sunny Goyalf75baa92016-11-22 03:23:51 +0530186 newStringsIter.remove();
Winson Chung780fe592013-09-26 14:48:44 -0700187 }
Sunny Goyal91498ab2017-10-05 15:57:40 -0700188 } catch (JSONException | URISyntaxException e) {
189 Log.d(TAG, "Exception reading shortcut to add: " + e);
190 newStringsIter.remove();
Winson Chung780fe592013-09-26 14:48:44 -0700191 }
192 }
Sunny Goyal91498ab2017-10-05 15:57:40 -0700193 sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
Michael Jurka48c7a932013-05-14 20:17:58 +0200194 }
195
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800196 public void onReceive(Context context, Intent data) {
Romain Guy51ed5b92009-06-17 10:20:34 -0700197 if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
198 return;
199 }
Sunny Goyal7606a412015-12-07 14:51:23 -0800200 PendingInstallShortcutInfo info = createPendingInfo(context, data);
201 if (info != null) {
Sunny Goyalfb5096d2016-09-08 14:32:06 -0700202 if (!info.isLauncherActivity()) {
203 // Since its a custom shortcut, verify that it is safe to launch.
Sunny Goyal342e4662017-02-02 15:21:08 -0800204 if (!new PackageManagerHelper(context).hasPermissionForActivity(
205 info.launchIntent, null)) {
Sunny Goyalfb5096d2016-09-08 14:32:06 -0700206 // 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 }
Sunny Goyal7606a412015-12-07 14:51:23 -0800211 queuePendingShortcutInfo(info, context);
Sunny Goyal3290ec42015-05-04 13:53:42 -0700212 }
Sunny Goyale0f58d72014-11-10 18:05:31 -0800213 }
Nilesh Agrawaldff0bfe2013-12-17 15:20:50 -0800214
Sunny Goyal7606a412015-12-07 14:51:23 -0800215 /**
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
Sunny Goyalf75baa92016-11-22 03:23:51 +0530236 PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(
Sunny Goyal7c74e4a2016-12-15 15:53:17 -0800237 data, Process.myUserHandle(), context);
Sunny Goyal5c97f512015-05-19 16:03:28 -0700238 if (info.launchIntent == null || info.label == null) {
239 if (DBG) Log.e(TAG, "Invalid install shortcut intent");
240 return null;
241 }
Sunny Goyal7606a412015-12-07 14:51:23 -0800242
243 return convertToLauncherActivityIfPossible(info);
Sunny Goyal5c97f512015-05-19 16:03:28 -0700244 }
245
Sunny Goyal7606a412015-12-07 14:51:23 -0800246 public static ShortcutInfo fromShortcutIntent(Context context, Intent data) {
247 PendingInstallShortcutInfo info = createPendingInfo(context, data);
Sunny Goyala474a9b2017-05-04 16:47:11 -0700248 return info == null ? null : (ShortcutInfo) info.getItemInfo().first;
Sunny Goyale0f58d72014-11-10 18:05:31 -0800249 }
Michael Jurka48c7a932013-05-14 20:17:58 +0200250
Sunny Goyal027fba32017-06-19 15:30:19 -0700251 public static ShortcutInfo fromActivityInfo(LauncherActivityInfo info, Context context) {
252 return (ShortcutInfo) (new PendingInstallShortcutInfo(info, context).getItemInfo().first);
253 }
254
Sunny Goyalf75baa92016-11-22 03:23:51 +0530255 public static void queueShortcut(ShortcutInfoCompat info, Context context) {
256 queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
257 }
258
Sunny Goyal3be633b2016-12-08 09:59:25 -0800259 public static void queueWidget(AppWidgetProviderInfo info, int widgetId, Context context) {
260 queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId, context), context);
261 }
262
Sunny Goyal4179e9b2017-03-08 14:25:09 -0800263 public static void queueActivityInfo(LauncherActivityInfo activity, Context context) {
264 queuePendingShortcutInfo(new PendingInstallShortcutInfo(activity, context), context);
265 }
266
Sunny Goyalf75baa92016-11-22 03:23:51 +0530267 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
Sunny Goyale0f58d72014-11-10 18:05:31 -0800288 private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
Winson Chungde0fb8f2012-05-08 14:37:08 -0700289 // Queue the item up for adding if launcher has not loaded properly yet
Sunny Goyal91498ab2017-10-05 15:57:40 -0700290 Message.obtain(sHandler, MSG_ADD_TO_QUEUE, Pair.create(context, info)).sendToTarget();
Sunny Goyala474a9b2017-05-04 16:47:11 -0700291 flushInstallQueue(context);
Winson Chungf561bdf2012-05-03 11:20:19 -0700292 }
293
Sunny Goyala474a9b2017-05-04 16:47:11 -0700294 public static void enableInstallQueue(int flag) {
295 sInstallQueueDisabledFlags |= flag;
Winson Chungf561bdf2012-05-03 11:20:19 -0700296 }
Sunny Goyala474a9b2017-05-04 16:47:11 -0700297 public static void disableAndFlushInstallQueue(int flag, Context context) {
298 sInstallQueueDisabledFlags &= ~flag;
Winson Chungde0fb8f2012-05-08 14:37:08 -0700299 flushInstallQueue(context);
300 }
Sunny Goyal2bcbe132016-11-16 09:23:42 -0800301
Winson Chungde0fb8f2012-05-08 14:37:08 -0700302 static void flushInstallQueue(Context context) {
Sunny Goyal91498ab2017-10-05 15:57:40 -0700303 if (sInstallQueueDisabledFlags != 0) {
Sunny Goyala474a9b2017-05-04 16:47:11 -0700304 return;
305 }
Sunny Goyal91498ab2017-10-05 15:57:40 -0700306 Message.obtain(sHandler, MSG_FLUSH_QUEUE, context.getApplicationContext()).sendToTarget();
Winson Chungf561bdf2012-05-03 11:20:19 -0700307 }
308
Nilesh Agrawaldff0bfe2013-12-17 15:20:50 -0800309 /**
Winson Chung2c1afde2013-10-17 11:03:24 -0700310 * Ensures that we have a valid, non-null name. If the provided name is null, we will return
311 * the application name instead.
312 */
Adam Cohen091440a2015-03-18 14:16:05 -0700313 @Thunk static CharSequence ensureValidName(Context context, Intent intent, CharSequence name) {
Winson Chung2c1afde2013-10-17 11:03:24 -0700314 if (name == null) {
315 try {
316 PackageManager pm = context.getPackageManager();
317 ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
Winson Chung82b016c2015-05-08 17:00:10 -0700318 name = info.loadLabel(pm);
Winson Chung2c1afde2013-10-17 11:03:24 -0700319 } catch (PackageManager.NameNotFoundException nnfe) {
320 return "";
321 }
322 }
323 return name;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800324 }
Sunny Goyale0f58d72014-11-10 18:05:31 -0800325
326 private static class PendingInstallShortcutInfo {
327
Sunny Goyal3e9be432017-01-05 15:22:41 -0800328 final LauncherActivityInfo activityInfo;
Sunny Goyalf75baa92016-11-22 03:23:51 +0530329 final ShortcutInfoCompat shortcutInfo;
Sunny Goyal3be633b2016-12-08 09:59:25 -0800330 final AppWidgetProviderInfo providerInfo;
Sunny Goyale0f58d72014-11-10 18:05:31 -0800331
332 final Intent data;
333 final Context mContext;
334 final Intent launchIntent;
335 final String label;
Sunny Goyal7c74e4a2016-12-15 15:53:17 -0800336 final UserHandle user;
Sunny Goyale0f58d72014-11-10 18:05:31 -0800337
338 /**
339 * Initializes a PendingInstallShortcutInfo received from a different app.
340 */
Sunny Goyal7c74e4a2016-12-15 15:53:17 -0800341 public PendingInstallShortcutInfo(Intent data, UserHandle user, Context context) {
Sunny Goyal3be633b2016-12-08 09:59:25 -0800342 activityInfo = null;
343 shortcutInfo = null;
344 providerInfo = null;
345
Sunny Goyale0f58d72014-11-10 18:05:31 -0800346 this.data = data;
Sunny Goyalf75baa92016-11-22 03:23:51 +0530347 this.user = user;
Sunny Goyale0f58d72014-11-10 18:05:31 -0800348 mContext = context;
349
350 launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
351 label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
Sunny Goyal3be633b2016-12-08 09:59:25 -0800352
Sunny Goyale0f58d72014-11-10 18:05:31 -0800353 }
354
355 /**
356 * Initializes a PendingInstallShortcutInfo to represent a launcher target.
357 */
Sunny Goyal3e9be432017-01-05 15:22:41 -0800358 public PendingInstallShortcutInfo(LauncherActivityInfo info, Context context) {
Sunny Goyale0f58d72014-11-10 18:05:31 -0800359 activityInfo = info;
Sunny Goyalf75baa92016-11-22 03:23:51 +0530360 shortcutInfo = null;
Sunny Goyal3be633b2016-12-08 09:59:25 -0800361 providerInfo = null;
362
363 data = null;
Sunny Goyale0f58d72014-11-10 18:05:31 -0800364 user = info.getUser();
Sunny Goyal3be633b2016-12-08 09:59:25 -0800365 mContext = context;
Sunny Goyale0f58d72014-11-10 18:05:31 -0800366
Sunny Goyal24bb66a2017-03-21 15:12:01 -0700367 launchIntent = AppInfo.makeLaunchIntent(info);
Sunny Goyale0f58d72014-11-10 18:05:31 -0800368 label = info.getLabel().toString();
369 }
370
Sunny Goyalf75baa92016-11-22 03:23:51 +0530371 /**
372 * Initializes a PendingInstallShortcutInfo to represent a launcher target.
373 */
374 public PendingInstallShortcutInfo(ShortcutInfoCompat info, Context context) {
Sunny Goyalf75baa92016-11-22 03:23:51 +0530375 activityInfo = null;
Sunny Goyal3be633b2016-12-08 09:59:25 -0800376 shortcutInfo = info;
377 providerInfo = null;
378
379 data = null;
380 mContext = context;
Sunny Goyalf75baa92016-11-22 03:23:51 +0530381 user = info.getUserHandle();
382
Sunny Goyal24bb66a2017-03-21 15:12:01 -0700383 launchIntent = info.makeIntent();
Sunny Goyalf75baa92016-11-22 03:23:51 +0530384 label = info.getShortLabel().toString();
385 }
386
Sunny Goyal3be633b2016-12-08 09:59:25 -0800387 /**
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;
Sunny Goyal7c74e4a2016-12-15 15:53:17 -0800398 user = info.getProfile();
Sunny Goyal3be633b2016-12-08 09:59:25 -0800399
400 launchIntent = new Intent().setComponent(info.provider)
401 .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
402 label = info.label;
403 }
404
Sunny Goyale0f58d72014-11-10 18:05:31 -0800405 public String encodeToString() {
Sunny Goyalf75baa92016-11-22 03:23:51 +0530406 try {
407 if (activityInfo != null) {
Sunny Goyale0f58d72014-11-10 18:05:31 -0800408 // 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();
Sunny Goyalf75baa92016-11-22 03:23:51 +0530417 } 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();
Sunny Goyal3be633b2016-12-08 09:59:25 -0800427 } 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();
Sunny Goyale0f58d72014-11-10 18:05:31 -0800437 }
Sunny Goyale0f58d72014-11-10 18:05:31 -0800438
Sunny Goyalf75baa92016-11-22 03:23:51 +0530439 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 }
Sunny Goyale0f58d72014-11-10 18:05:31 -0800447
Sunny Goyalf75baa92016-11-22 03:23:51 +0530448 // 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);
Sunny Goyale0f58d72014-11-10 18:05:31 -0800454
Sunny Goyalf75baa92016-11-22 03:23:51 +0530455 // Only encode the parameters which are supported by the API.
Sunny Goyale0f58d72014-11-10 18:05:31 -0800456 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) {
Sunny Goyale62d2bb2018-11-06 10:28:37 -0800461 byte[] iconByteArray = GraphicsUtils.flattenBitmap(icon);
Sunny Goyale0f58d72014-11-10 18:05:31 -0800462 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);
Sunny Goyalf75baa92016-11-22 03:23:51 +0530474 return null;
Sunny Goyale0f58d72014-11-10 18:05:31 -0800475 }
Sunny Goyale0f58d72014-11-10 18:05:31 -0800476 }
477
Sunny Goyala474a9b2017-05-04 16:47:11 -0700478 public Pair<ItemInfo, Object> getItemInfo() {
Sunny Goyale0f58d72014-11-10 18:05:31 -0800479 if (activityInfo != null) {
Sunny Goyal1cc1c9a2017-01-06 16:32:57 -0800480 AppInfo appInfo = new AppInfo(mContext, activityInfo, user);
Sunny Goyal87f784c2017-01-11 10:48:34 -0800481 final LauncherAppState app = LauncherAppState.getInstance(mContext);
Sunny Goyal1cc1c9a2017-01-06 16:32:57 -0800482 // Set default values until proper values is loaded.
483 appInfo.title = "";
Hyunyoung Songcda96a52018-10-18 15:05:45 -0700484 appInfo.applyFrom(app.getIconCache().getDefaultIcon(user));
Sunny Goyal1cc1c9a2017-01-06 16:32:57 -0800485 final ShortcutInfo si = appInfo.makeShortcut();
486 if (Looper.myLooper() == LauncherModel.getWorkerLooper()) {
487 app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */);
488 } else {
Sunny Goyal83fd25e2018-07-09 16:47:01 -0700489 app.getModel().updateAndBindShortcutInfo(() -> {
490 app.getIconCache().getTitleAndIcon(
491 si, activityInfo, false /* useLowResIcon */);
492 return si;
Sunny Goyal1cc1c9a2017-01-06 16:32:57 -0800493 });
494 }
Sunny Goyala474a9b2017-05-04 16:47:11 -0700495 return Pair.create((ItemInfo) si, (Object) activityInfo);
Sunny Goyalf75baa92016-11-22 03:23:51 +0530496 } else if (shortcutInfo != null) {
Sunny Goyal1b072632017-01-18 11:30:23 -0800497 ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext);
Sunny Goyal18a4e5a2018-01-09 15:34:38 -0800498 LauncherIcons li = LauncherIcons.obtain(mContext);
Hyunyoung Songcda96a52018-10-18 15:05:45 -0700499 si.applyFrom(li.createShortcutIcon(shortcutInfo));
Sunny Goyal18a4e5a2018-01-09 15:34:38 -0800500 li.recycle();
Sunny Goyala474a9b2017-05-04 16:47:11 -0700501 return Pair.create((ItemInfo) si, (Object) shortcutInfo);
Sunny Goyal3be633b2016-12-08 09:59:25 -0800502 } else if (providerInfo != null) {
503 LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
504 .fromProviderInfo(mContext, providerInfo);
505 LauncherAppWidgetInfo widgetInfo = new LauncherAppWidgetInfo(
506 launchIntent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0),
507 info.provider);
Sunny Goyal87f784c2017-01-11 10:48:34 -0800508 InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
Sunny Goyal3be633b2016-12-08 09:59:25 -0800509 widgetInfo.minSpanX = info.minSpanX;
510 widgetInfo.minSpanY = info.minSpanY;
511 widgetInfo.spanX = Math.min(info.spanX, idp.numColumns);
512 widgetInfo.spanY = Math.min(info.spanY, idp.numRows);
Sunny Goyala474a9b2017-05-04 16:47:11 -0700513 return Pair.create((ItemInfo) widgetInfo, (Object) providerInfo);
Sunny Goyale0f58d72014-11-10 18:05:31 -0800514 } else {
Sunny Goyala474a9b2017-05-04 16:47:11 -0700515 ShortcutInfo si = createShortcutInfo(data, LauncherAppState.getInstance(mContext));
516 return Pair.create((ItemInfo) si, null);
Sunny Goyale0f58d72014-11-10 18:05:31 -0800517 }
518 }
519
Sunny Goyalb740f592015-12-17 23:22:42 -0800520 public boolean isLauncherActivity() {
Sunny Goyal0b037782015-04-02 10:27:03 -0700521 return activityInfo != null;
522 }
Sunny Goyale0f58d72014-11-10 18:05:31 -0800523 }
524
Sunny Goyalf75baa92016-11-22 03:23:51 +0530525 private static String getIntentPackage(Intent intent) {
526 return intent.getComponent() == null
527 ? intent.getPackage() : intent.getComponent().getPackageName();
528 }
529
Sunny Goyale0f58d72014-11-10 18:05:31 -0800530 private static PendingInstallShortcutInfo decode(String encoded, Context context) {
531 try {
Sunny Goyalf75baa92016-11-22 03:23:51 +0530532 Decoder decoder = new Decoder(encoded, context);
533 if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
Sunny Goyal3e9be432017-01-05 15:22:41 -0800534 LauncherActivityInfo info = LauncherAppsCompat.getInstance(context)
Sunny Goyalf75baa92016-11-22 03:23:51 +0530535 .resolveActivity(decoder.launcherIntent, decoder.user);
Sunny Goyale0f58d72014-11-10 18:05:31 -0800536 return info == null ? null : new PendingInstallShortcutInfo(info, context);
Sunny Goyalf75baa92016-11-22 03:23:51 +0530537 } else if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
538 DeepShortcutManager sm = DeepShortcutManager.getInstance(context);
539 List<ShortcutInfoCompat> si = sm.queryForFullDetails(
540 decoder.launcherIntent.getPackage(),
Sunny Goyal27835952017-01-13 12:15:53 -0800541 Arrays.asList(decoder.launcherIntent.getStringExtra(
542 ShortcutInfoCompat.EXTRA_SHORTCUT_ID)),
543 decoder.user);
Sunny Goyalf75baa92016-11-22 03:23:51 +0530544 if (si.isEmpty()) {
545 return null;
546 } else {
547 return new PendingInstallShortcutInfo(si.get(0), context);
548 }
Sunny Goyal3be633b2016-12-08 09:59:25 -0800549 } else if (decoder.optBoolean(APP_WIDGET_TYPE_KEY)) {
550 int widgetId = decoder.launcherIntent
551 .getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0);
552 AppWidgetProviderInfo info = AppWidgetManager.getInstance(context)
553 .getAppWidgetInfo(widgetId);
554 if (info == null || !info.provider.equals(decoder.launcherIntent.getComponent()) ||
Sunny Goyal7c74e4a2016-12-15 15:53:17 -0800555 !info.getProfile().equals(decoder.user)) {
Sunny Goyal3be633b2016-12-08 09:59:25 -0800556 return null;
557 }
558 return new PendingInstallShortcutInfo(info, widgetId, context);
Sunny Goyale0f58d72014-11-10 18:05:31 -0800559 }
560
561 Intent data = new Intent();
Sunny Goyalf75baa92016-11-22 03:23:51 +0530562 data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, decoder.launcherIntent);
563 data.putExtra(Intent.EXTRA_SHORTCUT_NAME, decoder.getString(NAME_KEY));
Sunny Goyale0f58d72014-11-10 18:05:31 -0800564
Sunny Goyalf75baa92016-11-22 03:23:51 +0530565 String iconBase64 = decoder.optString(ICON_KEY);
566 String iconResourceName = decoder.optString(ICON_RESOURCE_NAME_KEY);
567 String iconResourcePackageName = decoder.optString(ICON_RESOURCE_PACKAGE_NAME_KEY);
Sunny Goyale0f58d72014-11-10 18:05:31 -0800568 if (iconBase64 != null && !iconBase64.isEmpty()) {
569 byte[] iconArray = Base64.decode(iconBase64, Base64.DEFAULT);
570 Bitmap b = BitmapFactory.decodeByteArray(iconArray, 0, iconArray.length);
571 data.putExtra(Intent.EXTRA_SHORTCUT_ICON, b);
572 } else if (iconResourceName != null && !iconResourceName.isEmpty()) {
573 Intent.ShortcutIconResource iconResource =
574 new Intent.ShortcutIconResource();
575 iconResource.resourceName = iconResourceName;
576 iconResource.packageName = iconResourcePackageName;
577 data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
578 }
579
Sunny Goyalf75baa92016-11-22 03:23:51 +0530580 return new PendingInstallShortcutInfo(data, decoder.user, context);
Sunny Goyalb740f592015-12-17 23:22:42 -0800581 } catch (JSONException | URISyntaxException e) {
Sunny Goyale0f58d72014-11-10 18:05:31 -0800582 Log.d(TAG, "Exception reading shortcut to add: " + e);
583 }
584 return null;
585 }
Sunny Goyal0b037782015-04-02 10:27:03 -0700586
Sunny Goyalf75baa92016-11-22 03:23:51 +0530587 private static class Decoder extends JSONObject {
588 public final Intent launcherIntent;
Sunny Goyal7c74e4a2016-12-15 15:53:17 -0800589 public final UserHandle user;
Sunny Goyalf75baa92016-11-22 03:23:51 +0530590
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) ? UserManagerCompat.getInstance(context)
595 .getUserForSerialNumber(getLong(USER_HANDLE_KEY))
Sunny Goyal7c74e4a2016-12-15 15:53:17 -0800596 : Process.myUserHandle();
Sunny Goyalf75baa92016-11-22 03:23:51 +0530597 if (user == null) {
598 throw new JSONException("Invalid user");
599 }
600 }
601 }
602
Sunny Goyal0b037782015-04-02 10:27:03 -0700603 /**
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 */
608 private static PendingInstallShortcutInfo convertToLauncherActivityIfPossible(
609 PendingInstallShortcutInfo original) {
Sunny Goyalb740f592015-12-17 23:22:42 -0800610 if (original.isLauncherActivity()) {
Sunny Goyal0b037782015-04-02 10:27:03 -0700611 // Already an activity target
612 return original;
613 }
Sunny Goyal1582f162016-11-08 14:40:53 -0800614 if (!Utilities.isLauncherAppTarget(original.launchIntent)) {
Sunny Goyal0b037782015-04-02 10:27:03 -0700615 return original;
616 }
617
Sunny Goyal3e9be432017-01-05 15:22:41 -0800618 LauncherActivityInfo info = LauncherAppsCompat.getInstance(original.mContext)
Sunny Goyal1582f162016-11-08 14:40:53 -0800619 .resolveActivity(original.launchIntent, original.user);
Sunny Goyal0b037782015-04-02 10:27:03 -0700620 if (info == null) {
621 return original;
622 }
Sunny Goyal0b037782015-04-02 10:27:03 -0700623 // Ignore any conflicts in the label name, as that can change based on locale.
Sunny Goyal1582f162016-11-08 14:40:53 -0800624 return new PendingInstallShortcutInfo(info, original.mContext);
Sunny Goyal0b037782015-04-02 10:27:03 -0700625 }
Sunny Goyal2bcbe132016-11-16 09:23:42 -0800626
Sunny Goyalaaf86fe2017-01-05 21:50:27 -0800627 private static ShortcutInfo createShortcutInfo(Intent data, LauncherAppState app) {
628 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
629 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
630 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
631
632 if (intent == null) {
633 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
634 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
635 return null;
636 }
637
638 final ShortcutInfo info = new ShortcutInfo();
639
640 // Only support intents for current user for now. Intents sent from other
641 // users wouldn't get here without intent forwarding anyway.
642 info.user = Process.myUserHandle();
643
Sunny Goyal179249d2017-12-19 16:49:24 -0800644 BitmapInfo iconInfo = null;
Sunny Goyal18a4e5a2018-01-09 15:34:38 -0800645 LauncherIcons li = LauncherIcons.obtain(app.getContext());
Sunny Goyalaaf86fe2017-01-05 21:50:27 -0800646 if (bitmap instanceof Bitmap) {
Sunny Goyal18a4e5a2018-01-09 15:34:38 -0800647 iconInfo = li.createIconBitmap((Bitmap) bitmap);
Sunny Goyalaaf86fe2017-01-05 21:50:27 -0800648 } else {
649 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
650 if (extra instanceof Intent.ShortcutIconResource) {
651 info.iconResource = (Intent.ShortcutIconResource) extra;
Sunny Goyal18a4e5a2018-01-09 15:34:38 -0800652 iconInfo = li.createIconBitmap(info.iconResource);
Sunny Goyalaaf86fe2017-01-05 21:50:27 -0800653 }
654 }
Sunny Goyal18a4e5a2018-01-09 15:34:38 -0800655 li.recycle();
656
Sunny Goyal179249d2017-12-19 16:49:24 -0800657 if (iconInfo == null) {
658 iconInfo = app.getIconCache().getDefaultIcon(info.user);
Sunny Goyalaaf86fe2017-01-05 21:50:27 -0800659 }
Hyunyoung Songcda96a52018-10-18 15:05:45 -0700660 info.applyFrom(iconInfo);
Sunny Goyalaaf86fe2017-01-05 21:50:27 -0800661
662 info.title = Utilities.trim(name);
Sunny Goyald7239fc2018-11-05 17:19:45 -0800663 info.contentDescription = app.getContext().getPackageManager()
664 .getUserBadgedLabel(info.title, info.user);
Sunny Goyalaaf86fe2017-01-05 21:50:27 -0800665 info.intent = intent;
666 return info;
667 }
668
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800669}