The Android Open Source Project | 31dd503 | 2009-03-03 19:32:27 -0800 | [diff] [blame] | 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 | |
Daniel Sandler | 325dc23 | 2013-06-05 22:57:57 -0400 | [diff] [blame] | 17 | package com.android.launcher3; |
The Android Open Source Project | 31dd503 | 2009-03-03 19:32:27 -0800 | [diff] [blame] | 18 | |
| 19 | import android.content.BroadcastReceiver; |
| 20 | import android.content.Context; |
| 21 | import android.content.Intent; |
Winson Chung | f0c6ae0 | 2012-03-21 16:10:31 -0700 | [diff] [blame] | 22 | import android.content.SharedPreferences; |
Winson Chung | c3a747a | 2012-02-29 10:56:19 -0800 | [diff] [blame] | 23 | import android.content.pm.ActivityInfo; |
| 24 | import android.content.pm.PackageManager; |
Sunny Goyal | 0b03778 | 2015-04-02 10:27:03 -0700 | [diff] [blame] | 25 | import android.content.pm.ResolveInfo; |
Michael Jurka | 48c7a93 | 2013-05-14 20:17:58 +0200 | [diff] [blame] | 26 | import android.graphics.Bitmap; |
| 27 | import android.graphics.BitmapFactory; |
Nilesh Agrawal | dff0bfe | 2013-12-17 15:20:50 -0800 | [diff] [blame] | 28 | import android.text.TextUtils; |
Michael Jurka | 48c7a93 | 2013-05-14 20:17:58 +0200 | [diff] [blame] | 29 | import android.util.Base64; |
| 30 | import android.util.Log; |
The Android Open Source Project | 31dd503 | 2009-03-03 19:32:27 -0800 | [diff] [blame] | 31 | |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 32 | import com.android.launcher3.compat.LauncherActivityInfoCompat; |
| 33 | import com.android.launcher3.compat.LauncherAppsCompat; |
Adam Cohen | a28b78e | 2014-05-20 17:03:04 -0700 | [diff] [blame] | 34 | import com.android.launcher3.compat.UserHandleCompat; |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 35 | import com.android.launcher3.compat.UserManagerCompat; |
Adam Cohen | 091440a | 2015-03-18 14:16:05 -0700 | [diff] [blame] | 36 | import com.android.launcher3.util.Thunk; |
Adam Cohen | a28b78e | 2014-05-20 17:03:04 -0700 | [diff] [blame] | 37 | |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 38 | import org.json.JSONException; |
Michael Jurka | 34c2e6c | 2013-12-13 16:07:45 +0100 | [diff] [blame] | 39 | import org.json.JSONObject; |
| 40 | import org.json.JSONStringer; |
| 41 | import org.json.JSONTokener; |
| 42 | |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 43 | import java.net.URISyntaxException; |
Winson Chung | e428e29 | 2012-01-10 16:20:33 -0800 | [diff] [blame] | 44 | import java.util.ArrayList; |
Winson Chung | f0c6ae0 | 2012-03-21 16:10:31 -0700 | [diff] [blame] | 45 | import java.util.HashSet; |
Winson Chung | f561bdf | 2012-05-03 11:20:19 -0700 | [diff] [blame] | 46 | import java.util.Iterator; |
Winson Chung | f0c6ae0 | 2012-03-21 16:10:31 -0700 | [diff] [blame] | 47 | import java.util.Set; |
Winson Chung | e428e29 | 2012-01-10 16:20:33 -0800 | [diff] [blame] | 48 | |
The Android Open Source Project | 31dd503 | 2009-03-03 19:32:27 -0800 | [diff] [blame] | 49 | public class InstallShortcutReceiver extends BroadcastReceiver { |
Bjorn Bringert | 4e871a2 | 2013-10-17 13:50:20 +0100 | [diff] [blame] | 50 | private static final String TAG = "InstallShortcutReceiver"; |
| 51 | private static final boolean DBG = false; |
| 52 | |
Sunny Goyal | 2350bc9 | 2014-10-14 16:42:54 -0700 | [diff] [blame] | 53 | private static final String ACTION_INSTALL_SHORTCUT = |
Winson Chung | 94d6768 | 2013-09-25 16:29:40 -0700 | [diff] [blame] | 54 | "com.android.launcher.action.INSTALL_SHORTCUT"; |
Winson Chung | f0c6ae0 | 2012-03-21 16:10:31 -0700 | [diff] [blame] | 55 | |
Sunny Goyal | 2350bc9 | 2014-10-14 16:42:54 -0700 | [diff] [blame] | 56 | private static final String LAUNCH_INTENT_KEY = "intent.launch"; |
| 57 | private static final String NAME_KEY = "name"; |
| 58 | private static final String ICON_KEY = "icon"; |
| 59 | private static final String ICON_RESOURCE_NAME_KEY = "iconResource"; |
| 60 | private static final String ICON_RESOURCE_PACKAGE_NAME_KEY = "iconResourcePackage"; |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 61 | |
| 62 | private static final String APP_SHORTCUT_TYPE_KEY = "isAppShortcut"; |
| 63 | private static final String USER_HANDLE_KEY = "userHandle"; |
| 64 | |
Michael Jurka | 48c7a93 | 2013-05-14 20:17:58 +0200 | [diff] [blame] | 65 | // The set of shortcuts that are pending install |
Sunny Goyal | 2350bc9 | 2014-10-14 16:42:54 -0700 | [diff] [blame] | 66 | private static final String APPS_PENDING_INSTALL = "apps_to_install"; |
Michael Jurka | 48c7a93 | 2013-05-14 20:17:58 +0200 | [diff] [blame] | 67 | |
Winson Chung | f0c6ae0 | 2012-03-21 16:10:31 -0700 | [diff] [blame] | 68 | public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450; |
Winson Chung | 997a923 | 2013-07-24 15:33:46 -0700 | [diff] [blame] | 69 | public static final int NEW_SHORTCUT_STAGGER_DELAY = 85; |
Winson Chung | f0c6ae0 | 2012-03-21 16:10:31 -0700 | [diff] [blame] | 70 | |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 71 | private static final Object sLock = new Object(); |
Michael Jurka | 48c7a93 | 2013-05-14 20:17:58 +0200 | [diff] [blame] | 72 | |
| 73 | private static void addToInstallQueue( |
| 74 | SharedPreferences sharedPrefs, PendingInstallShortcutInfo info) { |
| 75 | synchronized(sLock) { |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 76 | String encoded = info.encodeToString(); |
| 77 | if (encoded != null) { |
| 78 | Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null); |
| 79 | if (strings == null) { |
| 80 | strings = new HashSet<String>(1); |
| 81 | } else { |
| 82 | strings = new HashSet<String>(strings); |
Michael Jurka | 48c7a93 | 2013-05-14 20:17:58 +0200 | [diff] [blame] | 83 | } |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 84 | strings.add(encoded); |
| 85 | sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).commit(); |
Michael Jurka | 48c7a93 | 2013-05-14 20:17:58 +0200 | [diff] [blame] | 86 | } |
| 87 | } |
| 88 | } |
| 89 | |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 90 | public static void removeFromInstallQueue(Context context, ArrayList<String> packageNames, |
| 91 | UserHandleCompat user) { |
Winson Chung | df95eb1 | 2013-10-16 14:57:07 -0700 | [diff] [blame] | 92 | if (packageNames.isEmpty()) { |
| 93 | return; |
| 94 | } |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 95 | String spKey = LauncherAppState.getSharedPreferencesKey(); |
| 96 | SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE); |
Winson Chung | 780fe59 | 2013-09-26 14:48:44 -0700 | [diff] [blame] | 97 | synchronized(sLock) { |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 98 | Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null); |
Bjorn Bringert | 4e871a2 | 2013-10-17 13:50:20 +0100 | [diff] [blame] | 99 | if (DBG) { |
| 100 | Log.d(TAG, "APPS_PENDING_INSTALL: " + strings |
| 101 | + ", removing packages: " + packageNames); |
| 102 | } |
Winson Chung | 780fe59 | 2013-09-26 14:48:44 -0700 | [diff] [blame] | 103 | if (strings != null) { |
| 104 | Set<String> newStrings = new HashSet<String>(strings); |
Bjorn Bringert | 4e871a2 | 2013-10-17 13:50:20 +0100 | [diff] [blame] | 105 | Iterator<String> newStringsIter = newStrings.iterator(); |
| 106 | while (newStringsIter.hasNext()) { |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 107 | String encoded = newStringsIter.next(); |
| 108 | PendingInstallShortcutInfo info = decode(encoded, context); |
| 109 | if (info == null || (packageNames.contains(info.getTargetPackage()) |
| 110 | && user.equals(info.user))) { |
| 111 | newStringsIter.remove(); |
Winson Chung | 780fe59 | 2013-09-26 14:48:44 -0700 | [diff] [blame] | 112 | } |
| 113 | } |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 114 | sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).commit(); |
Winson Chung | 780fe59 | 2013-09-26 14:48:44 -0700 | [diff] [blame] | 115 | } |
| 116 | } |
| 117 | } |
| 118 | |
Michael Jurka | 48c7a93 | 2013-05-14 20:17:58 +0200 | [diff] [blame] | 119 | private static ArrayList<PendingInstallShortcutInfo> getAndClearInstallQueue( |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 120 | SharedPreferences sharedPrefs, Context context) { |
Michael Jurka | 48c7a93 | 2013-05-14 20:17:58 +0200 | [diff] [blame] | 121 | synchronized(sLock) { |
| 122 | Set<String> strings = sharedPrefs.getStringSet(APPS_PENDING_INSTALL, null); |
Bjorn Bringert | 4e871a2 | 2013-10-17 13:50:20 +0100 | [diff] [blame] | 123 | if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings); |
Michael Jurka | 48c7a93 | 2013-05-14 20:17:58 +0200 | [diff] [blame] | 124 | if (strings == null) { |
| 125 | return new ArrayList<PendingInstallShortcutInfo>(); |
| 126 | } |
| 127 | ArrayList<PendingInstallShortcutInfo> infos = |
| 128 | new ArrayList<PendingInstallShortcutInfo>(); |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 129 | for (String encoded : strings) { |
| 130 | PendingInstallShortcutInfo info = decode(encoded, context); |
| 131 | if (info != null) { |
Michael Jurka | 48c7a93 | 2013-05-14 20:17:58 +0200 | [diff] [blame] | 132 | infos.add(info); |
Michael Jurka | 48c7a93 | 2013-05-14 20:17:58 +0200 | [diff] [blame] | 133 | } |
| 134 | } |
| 135 | sharedPrefs.edit().putStringSet(APPS_PENDING_INSTALL, new HashSet<String>()).commit(); |
| 136 | return infos; |
| 137 | } |
| 138 | } |
| 139 | |
Winson Chung | f561bdf | 2012-05-03 11:20:19 -0700 | [diff] [blame] | 140 | // Determines whether to defer installing shortcuts immediately until |
| 141 | // processAllPendingInstalls() is called. |
| 142 | private static boolean mUseInstallQueue = false; |
| 143 | |
The Android Open Source Project | 31dd503 | 2009-03-03 19:32:27 -0800 | [diff] [blame] | 144 | public void onReceive(Context context, Intent data) { |
Romain Guy | 51ed5b9 | 2009-06-17 10:20:34 -0700 | [diff] [blame] | 145 | if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) { |
| 146 | return; |
| 147 | } |
| 148 | |
Bjorn Bringert | 4e871a2 | 2013-10-17 13:50:20 +0100 | [diff] [blame] | 149 | if (DBG) Log.d(TAG, "Got INSTALL_SHORTCUT: " + data.toUri(0)); |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 150 | PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context); |
Sunny Goyal | 0b03778 | 2015-04-02 10:27:03 -0700 | [diff] [blame] | 151 | info = convertToLauncherActivityIfPossible(info); |
Bjorn Bringert | 4e871a2 | 2013-10-17 13:50:20 +0100 | [diff] [blame] | 152 | |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 153 | queuePendingShortcutInfo(info, context); |
| 154 | } |
Nilesh Agrawal | dff0bfe | 2013-12-17 15:20:50 -0800 | [diff] [blame] | 155 | |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 156 | static void queueInstallShortcut(LauncherActivityInfoCompat info, Context context) { |
| 157 | queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context); |
| 158 | } |
Michael Jurka | 48c7a93 | 2013-05-14 20:17:58 +0200 | [diff] [blame] | 159 | |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 160 | private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) { |
Winson Chung | de0fb8f | 2012-05-08 14:37:08 -0700 | [diff] [blame] | 161 | // Queue the item up for adding if launcher has not loaded properly yet |
Winson Chung | c88dceb | 2013-09-27 14:59:38 -0700 | [diff] [blame] | 162 | LauncherAppState.setApplicationContext(context.getApplicationContext()); |
Winson Chung | 892c74d | 2013-08-22 16:15:50 -0700 | [diff] [blame] | 163 | LauncherAppState app = LauncherAppState.getInstance(); |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 164 | boolean launcherNotLoaded = app.getModel().getCallback() == null; |
Winson Chung | 997a923 | 2013-07-24 15:33:46 -0700 | [diff] [blame] | 165 | |
| 166 | String spKey = LauncherAppState.getSharedPreferencesKey(); |
| 167 | SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE); |
| 168 | addToInstallQueue(sp, info); |
| 169 | if (!mUseInstallQueue && !launcherNotLoaded) { |
| 170 | flushInstallQueue(context); |
Winson Chung | f561bdf | 2012-05-03 11:20:19 -0700 | [diff] [blame] | 171 | } |
| 172 | } |
| 173 | |
| 174 | static void enableInstallQueue() { |
| 175 | mUseInstallQueue = true; |
| 176 | } |
Winson Chung | f561bdf | 2012-05-03 11:20:19 -0700 | [diff] [blame] | 177 | static void disableAndFlushInstallQueue(Context context) { |
| 178 | mUseInstallQueue = false; |
Winson Chung | de0fb8f | 2012-05-08 14:37:08 -0700 | [diff] [blame] | 179 | flushInstallQueue(context); |
| 180 | } |
| 181 | static void flushInstallQueue(Context context) { |
Daniel Sandler | cc8befa | 2013-06-11 14:45:48 -0400 | [diff] [blame] | 182 | String spKey = LauncherAppState.getSharedPreferencesKey(); |
Michael Jurka | 48c7a93 | 2013-05-14 20:17:58 +0200 | [diff] [blame] | 183 | SharedPreferences sp = context.getSharedPreferences(spKey, Context.MODE_PRIVATE); |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 184 | ArrayList<PendingInstallShortcutInfo> installQueue = getAndClearInstallQueue(sp, context); |
Winson Chung | 997a923 | 2013-07-24 15:33:46 -0700 | [diff] [blame] | 185 | if (!installQueue.isEmpty()) { |
| 186 | Iterator<PendingInstallShortcutInfo> iter = installQueue.iterator(); |
| 187 | ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>(); |
Winson Chung | 997a923 | 2013-07-24 15:33:46 -0700 | [diff] [blame] | 188 | while (iter.hasNext()) { |
| 189 | final PendingInstallShortcutInfo pendingInfo = iter.next(); |
Winson Chung | 997a923 | 2013-07-24 15:33:46 -0700 | [diff] [blame] | 190 | final Intent intent = pendingInfo.launchIntent; |
Nilesh Agrawal | dff0bfe | 2013-12-17 15:20:50 -0800 | [diff] [blame] | 191 | |
Adam Cohen | a28b78e | 2014-05-20 17:03:04 -0700 | [diff] [blame] | 192 | // If the intent specifies a package, make sure the package exists |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 193 | String packageName = pendingInfo.getTargetPackage(); |
Sunny Goyal | f204877 | 2014-12-03 14:25:16 +0530 | [diff] [blame] | 194 | if (!TextUtils.isEmpty(packageName)) { |
Adam Cohen | a28b78e | 2014-05-20 17:03:04 -0700 | [diff] [blame] | 195 | UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle(); |
| 196 | if (!LauncherModel.isValidPackage(context, packageName, myUserHandle)) { |
| 197 | if (DBG) Log.d(TAG, "Ignoring shortcut for absent package:" + intent); |
| 198 | continue; |
| 199 | } |
| 200 | } |
| 201 | |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 202 | final boolean exists = LauncherModel.shortcutExists(context, pendingInfo.label, |
| 203 | intent, pendingInfo.user); |
Adam Cohen | a28b78e | 2014-05-20 17:03:04 -0700 | [diff] [blame] | 204 | if (!exists) { |
Winson Chung | 997a923 | 2013-07-24 15:33:46 -0700 | [diff] [blame] | 205 | // Generate a shortcut info to add into the model |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 206 | addShortcuts.add(pendingInfo.getShortcutInfo()); |
Winson Chung | 997a923 | 2013-07-24 15:33:46 -0700 | [diff] [blame] | 207 | } |
Winson Chung | 997a923 | 2013-07-24 15:33:46 -0700 | [diff] [blame] | 208 | } |
| 209 | |
| 210 | // Add the new apps to the model and bind them |
| 211 | if (!addShortcuts.isEmpty()) { |
| 212 | LauncherAppState app = LauncherAppState.getInstance(); |
Sunny Goyal | 18bf8e2 | 2015-04-08 18:13:46 -0700 | [diff] [blame^] | 213 | app.getModel().addAndBindAddedWorkspaceItems(context, addShortcuts); |
Winson Chung | 997a923 | 2013-07-24 15:33:46 -0700 | [diff] [blame] | 214 | } |
Winson Chung | f561bdf | 2012-05-03 11:20:19 -0700 | [diff] [blame] | 215 | } |
| 216 | } |
| 217 | |
Nilesh Agrawal | dff0bfe | 2013-12-17 15:20:50 -0800 | [diff] [blame] | 218 | /** |
| 219 | * Returns true if the intent is a valid launch intent for a shortcut. |
| 220 | * This is used to identify shortcuts which are different from the ones exposed by the |
| 221 | * applications' manifest file. |
| 222 | * |
| 223 | * When DISABLE_ALL_APPS is true, shortcuts exposed via the app's manifest should never be |
| 224 | * duplicated or removed(unless the app is un-installed). |
| 225 | * |
| 226 | * @param launchIntent The intent that will be launched when the shortcut is clicked. |
| 227 | */ |
| 228 | static boolean isValidShortcutLaunchIntent(Intent launchIntent) { |
| 229 | if (launchIntent != null |
| 230 | && Intent.ACTION_MAIN.equals(launchIntent.getAction()) |
| 231 | && launchIntent.getComponent() != null |
| 232 | && launchIntent.getCategories() != null |
| 233 | && launchIntent.getCategories().size() == 1 |
| 234 | && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER) |
| 235 | && launchIntent.getExtras() == null |
| 236 | && TextUtils.isEmpty(launchIntent.getDataString())) { |
| 237 | return false; |
| 238 | } |
| 239 | return true; |
| 240 | } |
| 241 | |
Winson Chung | 2c1afde | 2013-10-17 11:03:24 -0700 | [diff] [blame] | 242 | /** |
| 243 | * Ensures that we have a valid, non-null name. If the provided name is null, we will return |
| 244 | * the application name instead. |
| 245 | */ |
Adam Cohen | 091440a | 2015-03-18 14:16:05 -0700 | [diff] [blame] | 246 | @Thunk static CharSequence ensureValidName(Context context, Intent intent, CharSequence name) { |
Winson Chung | 2c1afde | 2013-10-17 11:03:24 -0700 | [diff] [blame] | 247 | if (name == null) { |
| 248 | try { |
| 249 | PackageManager pm = context.getPackageManager(); |
| 250 | ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0); |
| 251 | name = info.loadLabel(pm).toString(); |
| 252 | } catch (PackageManager.NameNotFoundException nnfe) { |
| 253 | return ""; |
| 254 | } |
| 255 | } |
| 256 | return name; |
The Android Open Source Project | 31dd503 | 2009-03-03 19:32:27 -0800 | [diff] [blame] | 257 | } |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 258 | |
| 259 | private static class PendingInstallShortcutInfo { |
| 260 | |
| 261 | final LauncherActivityInfoCompat activityInfo; |
| 262 | |
| 263 | final Intent data; |
| 264 | final Context mContext; |
| 265 | final Intent launchIntent; |
| 266 | final String label; |
| 267 | final UserHandleCompat user; |
| 268 | |
| 269 | /** |
| 270 | * Initializes a PendingInstallShortcutInfo received from a different app. |
| 271 | */ |
| 272 | public PendingInstallShortcutInfo(Intent data, Context context) { |
| 273 | this.data = data; |
| 274 | mContext = context; |
| 275 | |
| 276 | launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); |
| 277 | label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); |
| 278 | user = UserHandleCompat.myUserHandle(); |
| 279 | activityInfo = null; |
| 280 | } |
| 281 | |
| 282 | /** |
| 283 | * Initializes a PendingInstallShortcutInfo to represent a launcher target. |
| 284 | */ |
| 285 | public PendingInstallShortcutInfo(LauncherActivityInfoCompat info, Context context) { |
| 286 | this.data = null; |
| 287 | mContext = context; |
| 288 | activityInfo = info; |
| 289 | user = info.getUser(); |
| 290 | |
| 291 | launchIntent = AppInfo.makeLaunchIntent(context, info, user); |
| 292 | label = info.getLabel().toString(); |
| 293 | } |
| 294 | |
| 295 | public String encodeToString() { |
| 296 | if (activityInfo != null) { |
| 297 | try { |
| 298 | // If it a launcher target, we only need component name, and user to |
| 299 | // recreate this. |
| 300 | return new JSONStringer() |
| 301 | .object() |
| 302 | .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0)) |
| 303 | .key(APP_SHORTCUT_TYPE_KEY).value(true) |
| 304 | .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext) |
| 305 | .getSerialNumberForUser(user)) |
| 306 | .endObject().toString(); |
| 307 | } catch (JSONException e) { |
| 308 | Log.d(TAG, "Exception when adding shortcut: " + e); |
| 309 | return null; |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | if (launchIntent.getAction() == null) { |
| 314 | launchIntent.setAction(Intent.ACTION_VIEW); |
| 315 | } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) && |
| 316 | launchIntent.getCategories() != null && |
| 317 | launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { |
| 318 | launchIntent.addFlags( |
| 319 | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); |
| 320 | } |
| 321 | |
| 322 | // This name is only used for comparisons and notifications, so fall back to activity |
| 323 | // name if not supplied |
| 324 | String name = ensureValidName(mContext, launchIntent, label).toString(); |
| 325 | Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); |
| 326 | Intent.ShortcutIconResource iconResource = |
| 327 | data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); |
| 328 | |
| 329 | // Only encode the parameters which are supported by the API. |
| 330 | try { |
| 331 | JSONStringer json = new JSONStringer() |
| 332 | .object() |
| 333 | .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0)) |
| 334 | .key(NAME_KEY).value(name); |
| 335 | if (icon != null) { |
Sunny Goyal | 5b0e669 | 2015-03-19 14:31:19 -0700 | [diff] [blame] | 336 | byte[] iconByteArray = Utilities.flattenBitmap(icon); |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 337 | json = json.key(ICON_KEY).value( |
| 338 | Base64.encodeToString( |
| 339 | iconByteArray, 0, iconByteArray.length, Base64.DEFAULT)); |
| 340 | } |
| 341 | if (iconResource != null) { |
| 342 | json = json.key(ICON_RESOURCE_NAME_KEY).value(iconResource.resourceName); |
| 343 | json = json.key(ICON_RESOURCE_PACKAGE_NAME_KEY) |
| 344 | .value(iconResource.packageName); |
| 345 | } |
| 346 | return json.endObject().toString(); |
| 347 | } catch (JSONException e) { |
| 348 | Log.d(TAG, "Exception when adding shortcut: " + e); |
| 349 | } |
| 350 | return null; |
| 351 | } |
| 352 | |
| 353 | public ShortcutInfo getShortcutInfo() { |
| 354 | if (activityInfo != null) { |
Sunny Goyal | 18bf8e2 | 2015-04-08 18:13:46 -0700 | [diff] [blame^] | 355 | return ShortcutInfo.fromActivityInfo(activityInfo, mContext); |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 356 | } else { |
| 357 | return LauncherAppState.getInstance().getModel().infoFromShortcutIntent(mContext, data); |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | public String getTargetPackage() { |
| 362 | String packageName = launchIntent.getPackage(); |
| 363 | if (packageName == null) { |
| 364 | packageName = launchIntent.getComponent() == null ? null : |
| 365 | launchIntent.getComponent().getPackageName(); |
| 366 | } |
| 367 | return packageName; |
| 368 | } |
Sunny Goyal | 0b03778 | 2015-04-02 10:27:03 -0700 | [diff] [blame] | 369 | |
| 370 | public boolean isLuncherActivity() { |
| 371 | return activityInfo != null; |
| 372 | } |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 373 | } |
| 374 | |
| 375 | private static PendingInstallShortcutInfo decode(String encoded, Context context) { |
| 376 | try { |
| 377 | JSONObject object = (JSONObject) new JSONTokener(encoded).nextValue(); |
| 378 | Intent launcherIntent = Intent.parseUri(object.getString(LAUNCH_INTENT_KEY), 0); |
| 379 | |
| 380 | if (object.optBoolean(APP_SHORTCUT_TYPE_KEY)) { |
| 381 | // The is an internal launcher target shortcut. |
| 382 | UserHandleCompat user = UserManagerCompat.getInstance(context) |
| 383 | .getUserForSerialNumber(object.getLong(USER_HANDLE_KEY)); |
Sunny Goyal | 06910a8 | 2014-11-14 10:14:18 -0800 | [diff] [blame] | 384 | if (user == null) { |
| 385 | return null; |
| 386 | } |
Sunny Goyal | e0f58d7 | 2014-11-10 18:05:31 -0800 | [diff] [blame] | 387 | |
| 388 | LauncherActivityInfoCompat info = LauncherAppsCompat.getInstance(context) |
| 389 | .resolveActivity(launcherIntent, user); |
| 390 | return info == null ? null : new PendingInstallShortcutInfo(info, context); |
| 391 | } |
| 392 | |
| 393 | Intent data = new Intent(); |
| 394 | data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launcherIntent); |
| 395 | data.putExtra(Intent.EXTRA_SHORTCUT_NAME, object.getString(NAME_KEY)); |
| 396 | |
| 397 | String iconBase64 = object.optString(ICON_KEY); |
| 398 | String iconResourceName = object.optString(ICON_RESOURCE_NAME_KEY); |
| 399 | String iconResourcePackageName = object.optString(ICON_RESOURCE_PACKAGE_NAME_KEY); |
| 400 | if (iconBase64 != null && !iconBase64.isEmpty()) { |
| 401 | byte[] iconArray = Base64.decode(iconBase64, Base64.DEFAULT); |
| 402 | Bitmap b = BitmapFactory.decodeByteArray(iconArray, 0, iconArray.length); |
| 403 | data.putExtra(Intent.EXTRA_SHORTCUT_ICON, b); |
| 404 | } else if (iconResourceName != null && !iconResourceName.isEmpty()) { |
| 405 | Intent.ShortcutIconResource iconResource = |
| 406 | new Intent.ShortcutIconResource(); |
| 407 | iconResource.resourceName = iconResourceName; |
| 408 | iconResource.packageName = iconResourcePackageName; |
| 409 | data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); |
| 410 | } |
| 411 | |
| 412 | return new PendingInstallShortcutInfo(data, context); |
| 413 | } catch (JSONException e) { |
| 414 | Log.d(TAG, "Exception reading shortcut to add: " + e); |
| 415 | } catch (URISyntaxException e) { |
| 416 | Log.d(TAG, "Exception reading shortcut to add: " + e); |
| 417 | } |
| 418 | return null; |
| 419 | } |
Sunny Goyal | 0b03778 | 2015-04-02 10:27:03 -0700 | [diff] [blame] | 420 | |
| 421 | /** |
| 422 | * Tries to create a new PendingInstallShortcutInfo which represents the same target, |
| 423 | * but is an app target and not a shortcut. |
| 424 | * @return the newly created info or the original one. |
| 425 | */ |
| 426 | private static PendingInstallShortcutInfo convertToLauncherActivityIfPossible( |
| 427 | PendingInstallShortcutInfo original) { |
| 428 | if (original.isLuncherActivity()) { |
| 429 | // Already an activity target |
| 430 | return original; |
| 431 | } |
| 432 | if (isValidShortcutLaunchIntent(original.launchIntent) |
| 433 | || !original.user.equals(UserHandleCompat.myUserHandle())) { |
| 434 | // We can only convert shortcuts which point to a main activity in the current user. |
| 435 | return original; |
| 436 | } |
| 437 | |
| 438 | PackageManager pm = original.mContext.getPackageManager(); |
| 439 | ResolveInfo info = pm.resolveActivity(original.launchIntent, 0); |
| 440 | |
| 441 | if (info == null) { |
| 442 | return original; |
| 443 | } |
| 444 | |
| 445 | // Ignore any conflicts in the label name, as that can change based on locale. |
| 446 | LauncherActivityInfoCompat launcherInfo = LauncherActivityInfoCompat |
| 447 | .fromResolveInfo(info, original.mContext); |
| 448 | return new PendingInstallShortcutInfo(launcherInfo, original.mContext); |
| 449 | } |
| 450 | |
| 451 | public static boolean isLauncherActivity(Intent intent, Context context) { |
| 452 | Intent data = new Intent().putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent); |
| 453 | PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(data, context); |
| 454 | return convertToLauncherActivityIfPossible(info).isLuncherActivity(); |
| 455 | } |
The Android Open Source Project | 31dd503 | 2009-03-03 19:32:27 -0800 | [diff] [blame] | 456 | } |