Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2016 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 | package com.android.server.pm; |
| 17 | |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 18 | import android.annotation.NonNull; |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 19 | import android.annotation.Nullable; |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 20 | import android.appwidget.AppWidgetProviderInfo; |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 21 | import android.content.ComponentName; |
| 22 | import android.content.Intent; |
| 23 | import android.content.IntentSender; |
| 24 | import android.content.pm.IPinItemRequest; |
| 25 | import android.content.pm.LauncherApps; |
| 26 | import android.content.pm.LauncherApps.PinItemRequest; |
| 27 | import android.content.pm.ShortcutInfo; |
| 28 | import android.os.Bundle; |
Sunny Goyal | 4ad6b57 | 2017-02-28 11:11:51 -0800 | [diff] [blame] | 29 | import android.os.RemoteException; |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 30 | import android.os.UserHandle; |
| 31 | import android.util.Log; |
| 32 | import android.util.Pair; |
| 33 | import android.util.Slog; |
| 34 | |
| 35 | import com.android.internal.annotations.GuardedBy; |
| 36 | import com.android.internal.annotations.VisibleForTesting; |
| 37 | import com.android.internal.util.Preconditions; |
| 38 | |
| 39 | /** |
| 40 | * Handles {@link android.content.pm.ShortcutManager#requestPinShortcut} related tasks. |
| 41 | */ |
| 42 | class ShortcutRequestPinProcessor { |
| 43 | private static final String TAG = ShortcutService.TAG; |
| 44 | private static final boolean DEBUG = ShortcutService.DEBUG; |
| 45 | |
| 46 | private final ShortcutService mService; |
| 47 | private final Object mLock; |
| 48 | |
| 49 | /** |
| 50 | * Internal for {@link android.content.pm.LauncherApps.PinItemRequest} which receives callbacks. |
| 51 | */ |
Makoto Onuki | 8abba3a | 2017-02-24 11:34:54 -0800 | [diff] [blame] | 52 | private abstract static class PinItemRequestInner extends IPinItemRequest.Stub { |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 53 | protected final ShortcutRequestPinProcessor mProcessor; |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 54 | private final IntentSender mResultIntent; |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 55 | private final int mLauncherUid; |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 56 | |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 57 | @GuardedBy("this") |
| 58 | private boolean mAccepted; |
| 59 | |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 60 | private PinItemRequestInner(ShortcutRequestPinProcessor processor, |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 61 | IntentSender resultIntent, int launcherUid) { |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 62 | mProcessor = processor; |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 63 | mResultIntent = resultIntent; |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 64 | mLauncherUid = launcherUid; |
| 65 | } |
| 66 | |
Sunny Goyal | 4ad6b57 | 2017-02-28 11:11:51 -0800 | [diff] [blame] | 67 | @Override |
Makoto Onuki | 8abba3a | 2017-02-24 11:34:54 -0800 | [diff] [blame] | 68 | public ShortcutInfo getShortcutInfo() { |
| 69 | return null; |
| 70 | } |
| 71 | |
Sunny Goyal | 4ad6b57 | 2017-02-28 11:11:51 -0800 | [diff] [blame] | 72 | @Override |
Makoto Onuki | 8abba3a | 2017-02-24 11:34:54 -0800 | [diff] [blame] | 73 | public AppWidgetProviderInfo getAppWidgetProviderInfo() { |
| 74 | return null; |
| 75 | } |
| 76 | |
Sunny Goyal | 4ad6b57 | 2017-02-28 11:11:51 -0800 | [diff] [blame] | 77 | @Override |
| 78 | public Bundle getExtras() { |
| 79 | return null; |
| 80 | } |
| 81 | |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 82 | /** |
| 83 | * Returns true if the caller is same as the default launcher app when this request |
| 84 | * object was created. |
| 85 | */ |
| 86 | private boolean isCallerValid() { |
| 87 | return mProcessor.isCallerUid(mLauncherUid); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 88 | } |
| 89 | |
| 90 | @Override |
| 91 | public boolean isValid() { |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 92 | if (!isCallerValid()) { |
| 93 | return false; |
| 94 | } |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 95 | // TODO When an app calls requestPinShortcut(), all pending requests should be |
| 96 | // invalidated. |
| 97 | synchronized (this) { |
| 98 | return !mAccepted; |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | /** |
| 103 | * Called when the launcher calls {@link PinItemRequest#accept}. |
| 104 | */ |
| 105 | @Override |
| 106 | public boolean accept(Bundle options) { |
| 107 | // Make sure the options are unparcellable by the FW. (e.g. not containing unknown |
| 108 | // classes.) |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 109 | if (!isCallerValid()) { |
| 110 | throw new SecurityException("Calling uid mismatch"); |
| 111 | } |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 112 | Intent extras = null; |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 113 | if (options != null) { |
| 114 | try { |
| 115 | options.size(); |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 116 | extras = new Intent().putExtras(options); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 117 | } catch (RuntimeException e) { |
| 118 | throw new IllegalArgumentException("options cannot be unparceled", e); |
| 119 | } |
| 120 | } |
| 121 | synchronized (this) { |
| 122 | if (mAccepted) { |
| 123 | throw new IllegalStateException("accept() called already"); |
| 124 | } |
| 125 | mAccepted = true; |
| 126 | } |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 127 | |
| 128 | // Pin it and send the result intent. |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 129 | if (tryAccept()) { |
| 130 | mProcessor.sendResultIntent(mResultIntent, extras); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 131 | return true; |
| 132 | } else { |
| 133 | return false; |
| 134 | } |
| 135 | } |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 136 | |
| 137 | protected boolean tryAccept() { |
| 138 | return true; |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | /** |
| 143 | * Internal for {@link android.content.pm.LauncherApps.PinItemRequest} which receives callbacks. |
| 144 | */ |
Makoto Onuki | 8abba3a | 2017-02-24 11:34:54 -0800 | [diff] [blame] | 145 | private static class PinAppWidgetRequestInner extends PinItemRequestInner { |
| 146 | final AppWidgetProviderInfo mAppWidgetProviderInfo; |
Sunny Goyal | 4ad6b57 | 2017-02-28 11:11:51 -0800 | [diff] [blame] | 147 | final Bundle mExtras; |
Makoto Onuki | 8abba3a | 2017-02-24 11:34:54 -0800 | [diff] [blame] | 148 | |
| 149 | private PinAppWidgetRequestInner(ShortcutRequestPinProcessor processor, |
| 150 | IntentSender resultIntent, int launcherUid, |
Sunny Goyal | 4ad6b57 | 2017-02-28 11:11:51 -0800 | [diff] [blame] | 151 | AppWidgetProviderInfo appWidgetProviderInfo, Bundle extras) { |
Makoto Onuki | 8abba3a | 2017-02-24 11:34:54 -0800 | [diff] [blame] | 152 | super(processor, resultIntent, launcherUid); |
| 153 | |
| 154 | mAppWidgetProviderInfo = appWidgetProviderInfo; |
Sunny Goyal | 4ad6b57 | 2017-02-28 11:11:51 -0800 | [diff] [blame] | 155 | mExtras = extras; |
Makoto Onuki | 8abba3a | 2017-02-24 11:34:54 -0800 | [diff] [blame] | 156 | } |
| 157 | |
| 158 | @Override |
| 159 | public AppWidgetProviderInfo getAppWidgetProviderInfo() { |
| 160 | return mAppWidgetProviderInfo; |
| 161 | } |
Sunny Goyal | 4ad6b57 | 2017-02-28 11:11:51 -0800 | [diff] [blame] | 162 | |
| 163 | @Override |
| 164 | public Bundle getExtras() { |
| 165 | return mExtras; |
| 166 | } |
Makoto Onuki | 8abba3a | 2017-02-24 11:34:54 -0800 | [diff] [blame] | 167 | } |
| 168 | |
| 169 | /** |
| 170 | * Internal for {@link android.content.pm.LauncherApps.PinItemRequest} which receives callbacks. |
| 171 | */ |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 172 | private static class PinShortcutRequestInner extends PinItemRequestInner { |
| 173 | /** Original shortcut passed by the app. */ |
| 174 | public final ShortcutInfo shortcutOriginal; |
| 175 | |
| 176 | /** |
| 177 | * Cloned shortcut that's passed to the launcher. The notable difference from |
| 178 | * {@link #shortcutOriginal} is it must not have the intent. |
| 179 | */ |
| 180 | public final ShortcutInfo shortcutForLauncher; |
| 181 | |
| 182 | public final String launcherPackage; |
| 183 | public final int launcherUserId; |
| 184 | public final boolean preExisting; |
| 185 | |
| 186 | private PinShortcutRequestInner(ShortcutRequestPinProcessor processor, |
| 187 | ShortcutInfo shortcutOriginal, ShortcutInfo shortcutForLauncher, |
| 188 | IntentSender resultIntent, |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 189 | String launcherPackage, int launcherUserId, int launcherUid, boolean preExisting) { |
| 190 | super(processor, resultIntent, launcherUid); |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 191 | this.shortcutOriginal = shortcutOriginal; |
| 192 | this.shortcutForLauncher = shortcutForLauncher; |
| 193 | this.launcherPackage = launcherPackage; |
| 194 | this.launcherUserId = launcherUserId; |
| 195 | this.preExisting = preExisting; |
| 196 | } |
| 197 | |
| 198 | @Override |
Makoto Onuki | 8abba3a | 2017-02-24 11:34:54 -0800 | [diff] [blame] | 199 | public ShortcutInfo getShortcutInfo() { |
| 200 | return shortcutForLauncher; |
| 201 | } |
| 202 | |
| 203 | @Override |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 204 | protected boolean tryAccept() { |
| 205 | if (DEBUG) { |
| 206 | Slog.d(TAG, "Launcher accepted shortcut. ID=" + shortcutOriginal.getId() |
| 207 | + " package=" + shortcutOriginal.getPackage()); |
| 208 | } |
| 209 | return mProcessor.directPinShortcut(this); |
| 210 | } |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 211 | } |
| 212 | |
| 213 | public ShortcutRequestPinProcessor(ShortcutService service, Object lock) { |
| 214 | mService = service; |
| 215 | mLock = lock; |
| 216 | } |
| 217 | |
Sunny Goyal | 7f7372a | 2017-01-24 11:53:54 -0800 | [diff] [blame] | 218 | public boolean isRequestPinItemSupported(int callingUserId, int requestType) { |
| 219 | return getRequestPinConfirmationActivity(callingUserId, requestType) != null; |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 220 | } |
| 221 | |
| 222 | /** |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 223 | * Handle {@link android.content.pm.ShortcutManager#requestPinShortcut)} and |
| 224 | * {@link android.appwidget.AppWidgetManager#requestPinAppWidget}. |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 225 | * In this flow the PinItemRequest is delivered directly to the default launcher app. |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 226 | * One of {@param inShortcut} and {@param inAppWidget} is always non-null and the other is |
| 227 | * always null. |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 228 | */ |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 229 | public boolean requestPinItemLocked(ShortcutInfo inShortcut, AppWidgetProviderInfo inAppWidget, |
Sunny Goyal | 4ad6b57 | 2017-02-28 11:11:51 -0800 | [diff] [blame] | 230 | Bundle extras, int userId, IntentSender resultIntent) { |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 231 | |
| 232 | // First, make sure the launcher supports it. |
| 233 | |
| 234 | // Find the confirmation activity in the default launcher. |
Sunny Goyal | 7f7372a | 2017-01-24 11:53:54 -0800 | [diff] [blame] | 235 | final int requestType = inShortcut != null ? |
| 236 | PinItemRequest.REQUEST_TYPE_SHORTCUT : PinItemRequest.REQUEST_TYPE_APPWIDGET; |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 237 | final Pair<ComponentName, Integer> confirmActivity = |
Sunny Goyal | 7f7372a | 2017-01-24 11:53:54 -0800 | [diff] [blame] | 238 | getRequestPinConfirmationActivity(userId, requestType); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 239 | |
| 240 | // If the launcher doesn't support it, just return a rejected result and finish. |
| 241 | if (confirmActivity == null) { |
| 242 | Log.w(TAG, "Launcher doesn't support requestPinnedShortcut(). Shortcut not created."); |
| 243 | return false; |
| 244 | } |
| 245 | |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 246 | final int launcherUserId = confirmActivity.second; |
| 247 | |
| 248 | // Make sure the launcher user is unlocked. (it's always the parent profile, so should |
| 249 | // really be unlocked here though.) |
| 250 | mService.throwIfUserLockedL(launcherUserId); |
| 251 | |
| 252 | // Next, validate the incoming shortcut, etc. |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 253 | final PinItemRequest request; |
| 254 | if (inShortcut != null) { |
Makoto Onuki | 0c28071 | 2017-01-19 15:14:27 -0800 | [diff] [blame] | 255 | request = requestPinShortcutLocked(inShortcut, resultIntent, confirmActivity); |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 256 | } else { |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 257 | int launcherUid = mService.injectGetPackageUid( |
| 258 | confirmActivity.first.getPackageName(), launcherUserId); |
Makoto Onuki | 8abba3a | 2017-02-24 11:34:54 -0800 | [diff] [blame] | 259 | request = new PinItemRequest( |
Sunny Goyal | 4ad6b57 | 2017-02-28 11:11:51 -0800 | [diff] [blame] | 260 | new PinAppWidgetRequestInner(this, resultIntent, launcherUid, inAppWidget, |
| 261 | extras), |
Makoto Onuki | 8abba3a | 2017-02-24 11:34:54 -0800 | [diff] [blame] | 262 | PinItemRequest.REQUEST_TYPE_APPWIDGET); |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 263 | } |
Sunny Goyal | 7f7372a | 2017-01-24 11:53:54 -0800 | [diff] [blame] | 264 | return startRequestConfirmActivity(confirmActivity.first, launcherUserId, request, |
| 265 | requestType); |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 266 | } |
| 267 | |
| 268 | /** |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 269 | * Handle {@link android.content.pm.ShortcutManager#createShortcutResultIntent(ShortcutInfo)}. |
| 270 | * In this flow the PinItemRequest is delivered to the caller app. Its the app's responsibility |
| 271 | * to send it to the Launcher app (via {@link android.app.Activity#setResult(int, Intent)}). |
| 272 | */ |
| 273 | public Intent createShortcutResultIntent(@NonNull ShortcutInfo inShortcut, int userId) { |
| 274 | // Find the default launcher activity |
| 275 | final int launcherUserId = mService.getParentOrSelfUserId(userId); |
| 276 | final ComponentName defaultLauncher = mService.getDefaultLauncher(launcherUserId); |
| 277 | if (defaultLauncher == null) { |
| 278 | Log.e(TAG, "Default launcher not found."); |
| 279 | return null; |
| 280 | } |
| 281 | |
| 282 | // Make sure the launcher user is unlocked. (it's always the parent profile, so should |
| 283 | // really be unlocked here though.) |
| 284 | mService.throwIfUserLockedL(launcherUserId); |
| 285 | |
| 286 | // Next, validate the incoming shortcut, etc. |
Makoto Onuki | 0c28071 | 2017-01-19 15:14:27 -0800 | [diff] [blame] | 287 | final PinItemRequest request = requestPinShortcutLocked(inShortcut, null, |
| 288 | Pair.create(defaultLauncher, launcherUserId)); |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 289 | return new Intent().putExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST, request); |
| 290 | } |
| 291 | |
| 292 | /** |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 293 | * Handle {@link android.content.pm.ShortcutManager#requestPinShortcut)}. |
| 294 | */ |
Makoto Onuki | 0c28071 | 2017-01-19 15:14:27 -0800 | [diff] [blame] | 295 | @NonNull |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 296 | private PinItemRequest requestPinShortcutLocked(ShortcutInfo inShortcut, |
Makoto Onuki | 0c28071 | 2017-01-19 15:14:27 -0800 | [diff] [blame] | 297 | IntentSender resultIntentOriginal, Pair<ComponentName, Integer> confirmActivity) { |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 298 | final ShortcutPackage ps = mService.getPackageShortcutsForPublisherLocked( |
| 299 | inShortcut.getPackage(), inShortcut.getUserId()); |
| 300 | |
| 301 | final ShortcutInfo existing = ps.findShortcutById(inShortcut.getId()); |
| 302 | final boolean existsAlready = existing != null; |
Makoto Onuki | a4f89b1 | 2017-10-05 10:37:55 -0700 | [diff] [blame] | 303 | final boolean existingIsVisible = existsAlready && existing.isVisibleToPublisher(); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 304 | |
| 305 | if (DEBUG) { |
Makoto Onuki | 255461f | 2017-01-10 11:47:25 -0800 | [diff] [blame] | 306 | Slog.d(TAG, "requestPinnedShortcut: package=" + inShortcut.getPackage() |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 307 | + " existsAlready=" + existsAlready |
Makoto Onuki | a4f89b1 | 2017-10-05 10:37:55 -0700 | [diff] [blame] | 308 | + " existingIsVisible=" + existingIsVisible |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 309 | + " shortcut=" + inShortcut.toInsecureString()); |
| 310 | } |
| 311 | |
| 312 | // This is the shortcut that'll be sent to the launcher. |
Makoto Onuki | a01f4f0 | 2016-12-15 15:58:41 -0800 | [diff] [blame] | 313 | final ShortcutInfo shortcutForLauncher; |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 314 | final String launcherPackage = confirmActivity.first.getPackageName(); |
| 315 | final int launcherUserId = confirmActivity.second; |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 316 | |
Makoto Onuki | 0c28071 | 2017-01-19 15:14:27 -0800 | [diff] [blame] | 317 | IntentSender resultIntentToSend = resultIntentOriginal; |
| 318 | |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 319 | if (existsAlready) { |
| 320 | validateExistingShortcut(existing); |
| 321 | |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 322 | final boolean isAlreadyPinned = mService.getLauncherShortcutsLocked( |
| 323 | launcherPackage, existing.getUserId(), launcherUserId).hasPinned(existing); |
Makoto Onuki | 0c28071 | 2017-01-19 15:14:27 -0800 | [diff] [blame] | 324 | if (isAlreadyPinned) { |
| 325 | // When the shortcut is already pinned by this launcher, the request will always |
| 326 | // succeed, so just send the result at this point. |
| 327 | sendResultIntent(resultIntentOriginal, null); |
| 328 | |
| 329 | // So, do not send the intent again. |
| 330 | resultIntentToSend = null; |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 331 | } |
| 332 | |
| 333 | // Pass a clone, not the original. |
| 334 | // Note this will remove the intent and icons. |
Makoto Onuki | a01f4f0 | 2016-12-15 15:58:41 -0800 | [diff] [blame] | 335 | shortcutForLauncher = existing.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); |
| 336 | |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 337 | if (!isAlreadyPinned) { |
Makoto Onuki | 0c28071 | 2017-01-19 15:14:27 -0800 | [diff] [blame] | 338 | // FLAG_PINNED may still be set, if it's pinned by other launchers. |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 339 | shortcutForLauncher.clearFlags(ShortcutInfo.FLAG_PINNED); |
| 340 | } |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 341 | } else { |
Makoto Onuki | 255461f | 2017-01-10 11:47:25 -0800 | [diff] [blame] | 342 | // If the shortcut has no default activity, try to set the main activity. |
| 343 | // But in the request-pin case, it's optional, so it's okay even if the caller |
| 344 | // has no default activity. |
| 345 | if (inShortcut.getActivity() == null) { |
| 346 | inShortcut.setActivity(mService.injectGetDefaultMainActivity( |
| 347 | inShortcut.getPackage(), inShortcut.getUserId())); |
| 348 | } |
| 349 | |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 350 | // It doesn't exist, so it must have all mandatory fields. |
| 351 | mService.validateShortcutForPinRequest(inShortcut); |
| 352 | |
| 353 | // Initialize the ShortcutInfo for pending approval. |
| 354 | inShortcut.resolveResourceStrings(mService.injectGetResourcesForApplicationAsUser( |
| 355 | inShortcut.getPackage(), inShortcut.getUserId())); |
| 356 | if (DEBUG) { |
Makoto Onuki | 255461f | 2017-01-10 11:47:25 -0800 | [diff] [blame] | 357 | Slog.d(TAG, "Resolved shortcut=" + inShortcut.toInsecureString()); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 358 | } |
Makoto Onuki | a01f4f0 | 2016-12-15 15:58:41 -0800 | [diff] [blame] | 359 | // We should strip out the intent, but should preserve the icon. |
| 360 | shortcutForLauncher = inShortcut.clone( |
| 361 | ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER_APPROVAL); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 362 | } |
Makoto Onuki | 255461f | 2017-01-10 11:47:25 -0800 | [diff] [blame] | 363 | if (DEBUG) { |
| 364 | Slog.d(TAG, "Sending to launcher=" + shortcutForLauncher.toInsecureString()); |
| 365 | } |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 366 | |
| 367 | // Create a request object. |
| 368 | final PinShortcutRequestInner inner = |
Makoto Onuki | 0c28071 | 2017-01-19 15:14:27 -0800 | [diff] [blame] | 369 | new PinShortcutRequestInner(this, inShortcut, shortcutForLauncher, |
| 370 | resultIntentToSend, launcherPackage, launcherUserId, |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 371 | mService.injectGetPackageUid(launcherPackage, launcherUserId), |
| 372 | existsAlready); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 373 | |
Makoto Onuki | 8abba3a | 2017-02-24 11:34:54 -0800 | [diff] [blame] | 374 | return new PinItemRequest(inner, PinItemRequest.REQUEST_TYPE_SHORTCUT); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 375 | } |
| 376 | |
| 377 | private void validateExistingShortcut(ShortcutInfo shortcutInfo) { |
| 378 | // Make sure it's enabled. |
| 379 | // (Because we can't always force enable it automatically as it may be a stale |
| 380 | // manifest shortcut.) |
Makoto Onuki | a01f4f0 | 2016-12-15 15:58:41 -0800 | [diff] [blame] | 381 | Preconditions.checkArgument(shortcutInfo.isEnabled(), |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 382 | "Shortcut ID=" + shortcutInfo + " already exists but disabled."); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 383 | } |
| 384 | |
| 385 | private boolean startRequestConfirmActivity(ComponentName activity, int launcherUserId, |
Sunny Goyal | 7f7372a | 2017-01-24 11:53:54 -0800 | [diff] [blame] | 386 | PinItemRequest request, int requestType) { |
| 387 | final String action = requestType == LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ? |
| 388 | LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT : |
| 389 | LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET; |
| 390 | |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 391 | // Start the activity. |
Sunny Goyal | 7f7372a | 2017-01-24 11:53:54 -0800 | [diff] [blame] | 392 | final Intent confirmIntent = new Intent(action); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 393 | confirmIntent.setComponent(activity); |
| 394 | confirmIntent.putExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST, request); |
| 395 | confirmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); |
| 396 | |
| 397 | final long token = mService.injectClearCallingIdentity(); |
| 398 | try { |
| 399 | mService.mContext.startActivityAsUser( |
| 400 | confirmIntent, UserHandle.of(launcherUserId)); |
| 401 | } catch (RuntimeException e) { // ActivityNotFoundException, etc. |
| 402 | Log.e(TAG, "Unable to start activity " + activity, e); |
| 403 | return false; |
| 404 | } finally { |
| 405 | mService.injectRestoreCallingIdentity(token); |
| 406 | } |
| 407 | return true; |
| 408 | } |
| 409 | |
| 410 | /** |
Sunny Goyal | 7f7372a | 2017-01-24 11:53:54 -0800 | [diff] [blame] | 411 | * Find the activity that handles {@link LauncherApps#ACTION_CONFIRM_PIN_SHORTCUT} in the |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 412 | * default launcher. |
| 413 | */ |
| 414 | @Nullable |
| 415 | @VisibleForTesting |
Sunny Goyal | 7f7372a | 2017-01-24 11:53:54 -0800 | [diff] [blame] | 416 | Pair<ComponentName, Integer> getRequestPinConfirmationActivity( |
| 417 | int callingUserId, int requestType) { |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 418 | // Find the default launcher. |
| 419 | final int launcherUserId = mService.getParentOrSelfUserId(callingUserId); |
| 420 | final ComponentName defaultLauncher = mService.getDefaultLauncher(launcherUserId); |
| 421 | |
| 422 | if (defaultLauncher == null) { |
| 423 | Log.e(TAG, "Default launcher not found."); |
| 424 | return null; |
| 425 | } |
| 426 | final ComponentName activity = mService.injectGetPinConfirmationActivity( |
Sunny Goyal | 7f7372a | 2017-01-24 11:53:54 -0800 | [diff] [blame] | 427 | defaultLauncher.getPackageName(), launcherUserId, requestType); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 428 | return (activity == null) ? null : Pair.create(activity, launcherUserId); |
| 429 | } |
| 430 | |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 431 | public void sendResultIntent(@Nullable IntentSender intent, @Nullable Intent extras) { |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 432 | if (DEBUG) { |
| 433 | Slog.d(TAG, "Sending result intent."); |
| 434 | } |
Sunny Goyal | 87a563e | 2017-01-01 19:42:45 -0800 | [diff] [blame] | 435 | mService.injectSendIntentSender(intent, extras); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 436 | } |
| 437 | |
Sunny Goyal | a6be88a | 2017-01-12 16:27:58 -0800 | [diff] [blame] | 438 | public boolean isCallerUid(int uid) { |
| 439 | return uid == mService.injectBinderCallingUid(); |
| 440 | } |
| 441 | |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 442 | /** |
| 443 | * The last step of the "request pin shortcut" flow. Called when the launcher accepted a |
| 444 | * request. |
| 445 | */ |
| 446 | public boolean directPinShortcut(PinShortcutRequestInner request) { |
| 447 | |
Makoto Onuki | a01f4f0 | 2016-12-15 15:58:41 -0800 | [diff] [blame] | 448 | final ShortcutInfo original = request.shortcutOriginal; |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 449 | final int appUserId = original.getUserId(); |
| 450 | final String appPackageName = original.getPackage(); |
| 451 | final int launcherUserId = request.launcherUserId; |
| 452 | final String launcherPackage = request.launcherPackage; |
| 453 | final String shortcutId = original.getId(); |
| 454 | |
| 455 | synchronized (mLock) { |
| 456 | if (!(mService.isUserUnlockedL(appUserId) |
| 457 | && mService.isUserUnlockedL(request.launcherUserId))) { |
| 458 | Log.w(TAG, "User is locked now."); |
| 459 | return false; |
| 460 | } |
| 461 | |
Makoto Onuki | 0c28071 | 2017-01-19 15:14:27 -0800 | [diff] [blame] | 462 | final ShortcutLauncher launcher = mService.getLauncherShortcutsLocked( |
| 463 | launcherPackage, appUserId, launcherUserId); |
| 464 | launcher.attemptToRestoreIfNeededAndSave(); |
| 465 | if (launcher.hasPinned(original)) { |
| 466 | if (DEBUG) { |
Makoto Onuki | a4f89b1 | 2017-10-05 10:37:55 -0700 | [diff] [blame] | 467 | Slog.d(TAG, "Shortcut " + original + " already pinned."); // This too. |
Makoto Onuki | 0c28071 | 2017-01-19 15:14:27 -0800 | [diff] [blame] | 468 | } |
| 469 | return true; |
| 470 | } |
| 471 | |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 472 | final ShortcutPackage ps = mService.getPackageShortcutsForPublisherLocked( |
| 473 | appPackageName, appUserId); |
| 474 | final ShortcutInfo current = ps.findShortcutById(shortcutId); |
| 475 | |
| 476 | // The shortcut might have been changed, so we need to do the same validation again. |
| 477 | try { |
| 478 | if (current == null) { |
| 479 | // It doesn't exist, so it must have all necessary fields. |
Makoto Onuki | a01f4f0 | 2016-12-15 15:58:41 -0800 | [diff] [blame] | 480 | mService.validateShortcutForPinRequest(original); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 481 | } else { |
| 482 | validateExistingShortcut(current); |
| 483 | } |
| 484 | } catch (RuntimeException e) { |
| 485 | Log.w(TAG, "Unable to pin shortcut: " + e.getMessage()); |
| 486 | return false; |
| 487 | } |
| 488 | |
| 489 | // If the shortcut doesn't exist, need to create it. |
| 490 | // First, create it as a dynamic shortcut. |
| 491 | if (current == null) { |
| 492 | if (DEBUG) { |
| 493 | Slog.d(TAG, "Temporarily adding " + shortcutId + " as dynamic"); |
| 494 | } |
Makoto Onuki | 255461f | 2017-01-10 11:47:25 -0800 | [diff] [blame] | 495 | // Add as a dynamic shortcut. In order for a shortcut to be dynamic, it must |
| 496 | // have a target activity, so we set a dummy here. It's later removed |
| 497 | // in deleteDynamicWithId(). |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 498 | if (original.getActivity() == null) { |
| 499 | original.setActivity(mService.getDummyMainActivity(appPackageName)); |
| 500 | } |
Makoto Onuki | a4f89b1 | 2017-10-05 10:37:55 -0700 | [diff] [blame] | 501 | ps.addOrReplaceDynamicShortcut(original); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 502 | } |
| 503 | |
| 504 | // Pin the shortcut. |
| 505 | if (DEBUG) { |
| 506 | Slog.d(TAG, "Pinning " + shortcutId); |
| 507 | } |
| 508 | |
Makoto Onuki | a4f89b1 | 2017-10-05 10:37:55 -0700 | [diff] [blame] | 509 | launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId, |
| 510 | /*forPinRequest=*/ true); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 511 | |
| 512 | if (current == null) { |
| 513 | if (DEBUG) { |
| 514 | Slog.d(TAG, "Removing " + shortcutId + " as dynamic"); |
| 515 | } |
Makoto Onuki | a4f89b1 | 2017-10-05 10:37:55 -0700 | [diff] [blame] | 516 | ps.deleteDynamicWithId(shortcutId, /*ignoreInvisible=*/ false); |
Makoto Onuki | 2d895c3 | 2016-12-02 15:48:40 -0800 | [diff] [blame] | 517 | } |
| 518 | |
| 519 | ps.adjustRanks(); // Shouldn't be needed, but just in case. |
| 520 | } |
| 521 | |
| 522 | mService.verifyStates(); |
| 523 | mService.packageShortcutsChanged(appPackageName, appUserId); |
| 524 | |
| 525 | return true; |
| 526 | } |
| 527 | } |