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 18 import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; 19 20 import android.Manifest.permission; 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.UserIdInt; 25 import android.app.ActivityManager; 26 import android.app.ActivityManagerInternal; 27 import android.app.AppGlobals; 28 import android.app.IUidObserver; 29 import android.app.IUriGrantsManager; 30 import android.app.UriGrantsManager; 31 import android.app.role.OnRoleHoldersChangedListener; 32 import android.app.role.RoleManager; 33 import android.app.usage.UsageStatsManagerInternal; 34 import android.appwidget.AppWidgetProviderInfo; 35 import android.content.BroadcastReceiver; 36 import android.content.ComponentName; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.content.IntentFilter; 40 import android.content.IntentSender; 41 import android.content.IntentSender.SendIntentException; 42 import android.content.LocusId; 43 import android.content.pm.ActivityInfo; 44 import android.content.pm.AppSearchShortcutInfo; 45 import android.content.pm.ApplicationInfo; 46 import android.content.pm.ComponentInfo; 47 import android.content.pm.IPackageManager; 48 import android.content.pm.IShortcutService; 49 import android.content.pm.LauncherApps; 50 import android.content.pm.LauncherApps.ShortcutQuery; 51 import android.content.pm.PackageInfo; 52 import android.content.pm.PackageManager; 53 import android.content.pm.PackageManager.NameNotFoundException; 54 import android.content.pm.PackageManagerInternal; 55 import android.content.pm.ParceledListSlice; 56 import android.content.pm.ResolveInfo; 57 import android.content.pm.ShortcutInfo; 58 import android.content.pm.ShortcutManager; 59 import android.content.pm.ShortcutServiceInternal; 60 import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; 61 import android.content.res.Resources; 62 import android.content.res.XmlResourceParser; 63 import android.graphics.Bitmap; 64 import android.graphics.Bitmap.CompressFormat; 65 import android.graphics.Canvas; 66 import android.graphics.RectF; 67 import android.graphics.drawable.AdaptiveIconDrawable; 68 import android.graphics.drawable.Icon; 69 import android.net.Uri; 70 import android.os.Binder; 71 import android.os.Build; 72 import android.os.Bundle; 73 import android.os.Environment; 74 import android.os.FileUtils; 75 import android.os.Handler; 76 import android.os.IBinder; 77 import android.os.LocaleList; 78 import android.os.Looper; 79 import android.os.ParcelFileDescriptor; 80 import android.os.PersistableBundle; 81 import android.os.Process; 82 import android.os.RemoteException; 83 import android.os.ResultReceiver; 84 import android.os.SELinux; 85 import android.os.ServiceManager; 86 import android.os.ShellCallback; 87 import android.os.ShellCommand; 88 import android.os.SystemClock; 89 import android.os.UserHandle; 90 import android.provider.DeviceConfig; 91 import android.text.TextUtils; 92 import android.text.format.TimeMigrationUtils; 93 import android.util.ArraySet; 94 import android.util.AtomicFile; 95 import android.util.KeyValueListParser; 96 import android.util.Log; 97 import android.util.Slog; 98 import android.util.SparseArray; 99 import android.util.SparseBooleanArray; 100 import android.util.SparseIntArray; 101 import android.util.SparseLongArray; 102 import android.util.TypedValue; 103 import android.util.TypedXmlPullParser; 104 import android.util.TypedXmlSerializer; 105 import android.util.Xml; 106 import android.view.IWindowManager; 107 108 import com.android.internal.annotations.GuardedBy; 109 import com.android.internal.annotations.VisibleForTesting; 110 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 111 import com.android.internal.infra.AndroidFuture; 112 import com.android.internal.logging.MetricsLogger; 113 import com.android.internal.os.BackgroundThread; 114 import com.android.internal.util.CollectionUtils; 115 import com.android.internal.util.DumpUtils; 116 import com.android.internal.util.Preconditions; 117 import com.android.internal.util.StatLogger; 118 import com.android.server.LocalServices; 119 import com.android.server.SystemService; 120 import com.android.server.pm.ShortcutUser.PackageWithUser; 121 import com.android.server.uri.UriGrantsManagerInternal; 122 123 import libcore.io.IoUtils; 124 125 import org.json.JSONArray; 126 import org.json.JSONException; 127 import org.json.JSONObject; 128 import org.xmlpull.v1.XmlPullParser; 129 import org.xmlpull.v1.XmlPullParserException; 130 131 import java.io.ByteArrayInputStream; 132 import java.io.ByteArrayOutputStream; 133 import java.io.File; 134 import java.io.FileDescriptor; 135 import java.io.FileInputStream; 136 import java.io.FileNotFoundException; 137 import java.io.FileOutputStream; 138 import java.io.IOException; 139 import java.io.InputStream; 140 import java.io.OutputStream; 141 import java.io.PrintWriter; 142 import java.lang.annotation.Retention; 143 import java.lang.annotation.RetentionPolicy; 144 import java.net.URISyntaxException; 145 import java.nio.charset.StandardCharsets; 146 import java.util.ArrayList; 147 import java.util.Arrays; 148 import java.util.Collections; 149 import java.util.List; 150 import java.util.Objects; 151 import java.util.concurrent.ExecutionException; 152 import java.util.concurrent.atomic.AtomicBoolean; 153 import java.util.function.Consumer; 154 import java.util.function.Predicate; 155 import java.util.regex.Pattern; 156 157 /** 158 * TODO: 159 * - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi. 160 * -> But TypedValue.applyDimension() doesn't differentiate x and y..? 161 * 162 * - Detect when already registered instances are passed to APIs again, which might break 163 * internal bitmap handling. 164 */ 165 public class ShortcutService extends IShortcutService.Stub { 166 static final String TAG = "ShortcutService"; 167 168 static final boolean DEBUG = false; // STOPSHIP if true 169 static final boolean DEBUG_LOAD = false; // STOPSHIP if true 170 static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true 171 static final boolean DEBUG_REBOOT = false; // STOPSHIP if true 172 173 @VisibleForTesting 174 static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day 175 176 @VisibleForTesting 177 static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10; 178 179 @VisibleForTesting 180 static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15; 181 182 @VisibleForTesting 183 static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96; 184 185 @VisibleForTesting 186 static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48; 187 188 @VisibleForTesting 189 static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name(); 190 191 @VisibleForTesting 192 static final int DEFAULT_ICON_PERSIST_QUALITY = 100; 193 194 @VisibleForTesting 195 static final int DEFAULT_SAVE_DELAY_MS = 3000; 196 197 @VisibleForTesting 198 static final String FILENAME_BASE_STATE = "shortcut_service.xml"; 199 200 @VisibleForTesting 201 static final String DIRECTORY_PER_USER = "shortcut_service"; 202 203 @VisibleForTesting 204 static final String DIRECTORY_DUMP = "shortcut_dump"; 205 206 @VisibleForTesting 207 static final String FILENAME_USER_PACKAGES = "shortcuts.xml"; 208 209 static final String DIRECTORY_BITMAPS = "bitmaps"; 210 211 private static final String TAG_ROOT = "root"; 212 private static final String TAG_LAST_RESET_TIME = "last_reset_time"; 213 214 private static final String ATTR_VALUE = "value"; 215 216 private static final String LAUNCHER_INTENT_CATEGORY = Intent.CATEGORY_LAUNCHER; 217 218 private static final String KEY_SHORTCUT = "shortcut"; 219 private static final String KEY_LOW_RAM = "lowRam"; 220 private static final String KEY_ICON_SIZE = "iconSize"; 221 222 private static final String DUMMY_MAIN_ACTIVITY = "android.__dummy__"; 223 224 @VisibleForTesting 225 interface ConfigConstants { 226 /** 227 * Key name for the save delay, in milliseconds. (int) 228 */ 229 String KEY_SAVE_DELAY_MILLIS = "save_delay_ms"; 230 231 /** 232 * Key name for the throttling reset interval, in seconds. (long) 233 */ 234 String KEY_RESET_INTERVAL_SEC = "reset_interval_sec"; 235 236 /** 237 * Key name for the max number of modifying API calls per app for every interval. (int) 238 */ 239 String KEY_MAX_UPDATES_PER_INTERVAL = "max_updates_per_interval"; 240 241 /** 242 * Key name for the max icon dimensions in DP, for non-low-memory devices. 243 */ 244 String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp"; 245 246 /** 247 * Key name for the max icon dimensions in DP, for low-memory devices. 248 */ 249 String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram"; 250 251 /** 252 * Key name for the max dynamic shortcuts per activity. (int) 253 */ 254 String KEY_MAX_SHORTCUTS = "max_shortcuts"; 255 256 /** 257 * Key name for icon compression quality, 0-100. 258 */ 259 String KEY_ICON_QUALITY = "icon_quality"; 260 261 /** 262 * Key name for icon compression format: "PNG", "JPEG" or "WEBP" 263 */ 264 String KEY_ICON_FORMAT = "icon_format"; 265 } 266 267 private static final int PACKAGE_MATCH_FLAGS = 268 PackageManager.MATCH_DIRECT_BOOT_AWARE 269 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 270 | PackageManager.MATCH_UNINSTALLED_PACKAGES 271 | PackageManager.MATCH_DISABLED_COMPONENTS; 272 273 private static final int SYSTEM_APP_MASK = 274 ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; 275 276 final Context mContext; 277 278 private final Object mLock = new Object(); 279 private final Object mNonPersistentUsersLock = new Object(); 280 281 private static List<ResolveInfo> EMPTY_RESOLVE_INFO = new ArrayList<>(0); 282 283 // Temporarily reverted to anonymous inner class form due to: b/32554459 284 private static Predicate<ResolveInfo> ACTIVITY_NOT_EXPORTED = new Predicate<ResolveInfo>() { 285 public boolean test(ResolveInfo ri) { 286 return !ri.activityInfo.exported; 287 } 288 }; 289 290 private static Predicate<ResolveInfo> ACTIVITY_NOT_INSTALLED = (ri) -> 291 !isInstalled(ri.activityInfo); 292 293 // Temporarily reverted to anonymous inner class form due to: b/32554459 294 private static Predicate<PackageInfo> PACKAGE_NOT_INSTALLED = new Predicate<PackageInfo>() { 295 public boolean test(PackageInfo pi) { 296 return !isInstalled(pi); 297 } 298 }; 299 300 private final Handler mHandler; 301 302 @GuardedBy("mLock") 303 private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1); 304 305 @GuardedBy("mLock") 306 private final ArrayList<LauncherApps.ShortcutChangeCallback> mShortcutChangeCallbacks = 307 new ArrayList<>(1); 308 309 @GuardedBy("mLock") 310 private long mRawLastResetTime; 311 312 /** 313 * User ID -> UserShortcuts 314 */ 315 @GuardedBy("mLock") 316 private final SparseArray<ShortcutUser> mUsers = new SparseArray<>(); 317 318 /** 319 * User ID -> ShortcutNonPersistentUser 320 * 321 * Note we use a fine-grained lock for {@link #mShortcutNonPersistentUsers} due to b/183618378. 322 */ 323 @GuardedBy("mNonPersistentUsersLock") 324 private final SparseArray<ShortcutNonPersistentUser> mShortcutNonPersistentUsers = 325 new SparseArray<>(); 326 327 /** 328 * Max number of dynamic + manifest shortcuts that each application can have at a time. 329 */ 330 private int mMaxShortcuts; 331 332 /** 333 * Max number of updating API calls that each application can make during the interval. 334 */ 335 int mMaxUpdatesPerInterval; 336 337 /** 338 * Actual throttling-reset interval. By default it's a day. 339 */ 340 private long mResetInterval; 341 342 /** 343 * Icon max width/height in pixels. 344 */ 345 private int mMaxIconDimension; 346 347 private CompressFormat mIconPersistFormat; 348 private int mIconPersistQuality; 349 350 private int mSaveDelayMillis; 351 352 private final IPackageManager mIPackageManager; 353 private final PackageManagerInternal mPackageManagerInternal; 354 final UserManagerInternal mUserManagerInternal; 355 private final UsageStatsManagerInternal mUsageStatsManagerInternal; 356 private final ActivityManagerInternal mActivityManagerInternal; 357 private final IUriGrantsManager mUriGrantsManager; 358 private final UriGrantsManagerInternal mUriGrantsManagerInternal; 359 private final IBinder mUriPermissionOwner; 360 private final RoleManager mRoleManager; 361 362 private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor; 363 private final ShortcutBitmapSaver mShortcutBitmapSaver; 364 private final ShortcutDumpFiles mShortcutDumpFiles; 365 366 @GuardedBy("mLock") 367 final SparseIntArray mUidState = new SparseIntArray(); 368 369 @GuardedBy("mLock") 370 final SparseLongArray mUidLastForegroundElapsedTime = new SparseLongArray(); 371 372 @GuardedBy("mLock") 373 private List<Integer> mDirtyUserIds = new ArrayList<>(); 374 375 private final AtomicBoolean mBootCompleted = new AtomicBoolean(); 376 private final AtomicBoolean mShutdown = new AtomicBoolean(); 377 378 /** 379 * Note we use a fine-grained lock for {@link #mUnlockedUsers} due to b/64303666. 380 */ 381 @GuardedBy("mUnlockedUsers") 382 final SparseBooleanArray mUnlockedUsers = new SparseBooleanArray(); 383 384 // Stats 385 @VisibleForTesting 386 interface Stats { 387 int GET_DEFAULT_HOME = 0; 388 int GET_PACKAGE_INFO = 1; 389 int GET_PACKAGE_INFO_WITH_SIG = 2; 390 int GET_APPLICATION_INFO = 3; 391 int LAUNCHER_PERMISSION_CHECK = 4; 392 int CLEANUP_DANGLING_BITMAPS = 5; 393 int GET_ACTIVITY_WITH_METADATA = 6; 394 int GET_INSTALLED_PACKAGES = 7; 395 int CHECK_PACKAGE_CHANGES = 8; 396 int GET_APPLICATION_RESOURCES = 9; 397 int RESOURCE_NAME_LOOKUP = 10; 398 int GET_LAUNCHER_ACTIVITY = 11; 399 int CHECK_LAUNCHER_ACTIVITY = 12; 400 int IS_ACTIVITY_ENABLED = 13; 401 int PACKAGE_UPDATE_CHECK = 14; 402 int ASYNC_PRELOAD_USER_DELAY = 15; 403 int GET_DEFAULT_LAUNCHER = 16; 404 405 int COUNT = GET_DEFAULT_LAUNCHER + 1; 406 } 407 408 private final StatLogger mStatLogger = new StatLogger(new String[] { 409 "getHomeActivities()", 410 "Launcher permission check", 411 "getPackageInfo()", 412 "getPackageInfo(SIG)", 413 "getApplicationInfo", 414 "cleanupDanglingBitmaps", 415 "getActivity+metadata", 416 "getInstalledPackages", 417 "checkPackageChanges", 418 "getApplicationResources", 419 "resourceNameLookup", 420 "getLauncherActivity", 421 "checkLauncherActivity", 422 "isActivityEnabled", 423 "packageUpdateCheck", 424 "asyncPreloadUserDelay", 425 "getDefaultLauncher()" 426 }); 427 428 private static final int PROCESS_STATE_FOREGROUND_THRESHOLD = 429 ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; 430 431 static final int OPERATION_SET = 0; 432 static final int OPERATION_ADD = 1; 433 static final int OPERATION_UPDATE = 2; 434 435 /** @hide */ 436 @IntDef(value = { 437 OPERATION_SET, 438 OPERATION_ADD, 439 OPERATION_UPDATE 440 }) 441 @Retention(RetentionPolicy.SOURCE) 442 @interface ShortcutOperation { 443 } 444 445 @GuardedBy("mLock") 446 private int mWtfCount = 0; 447 448 @GuardedBy("mLock") 449 private Exception mLastWtfStacktrace; 450 451 @GuardedBy("mLock") 452 private final MetricsLogger mMetricsLogger = new MetricsLogger(); 453 454 private final boolean mIsAppSearchEnabled; 455 456 static class InvalidFileFormatException extends Exception { InvalidFileFormatException(String message, Throwable cause)457 public InvalidFileFormatException(String message, Throwable cause) { 458 super(message, cause); 459 } 460 } 461 ShortcutService(Context context)462 public ShortcutService(Context context) { 463 this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false); 464 } 465 466 @VisibleForTesting ShortcutService(Context context, Looper looper, boolean onlyForPackageManagerApis)467 ShortcutService(Context context, Looper looper, boolean onlyForPackageManagerApis) { 468 mContext = Objects.requireNonNull(context); 469 LocalServices.addService(ShortcutServiceInternal.class, new LocalService()); 470 mHandler = new Handler(looper); 471 mIPackageManager = AppGlobals.getPackageManager(); 472 mPackageManagerInternal = Objects.requireNonNull( 473 LocalServices.getService(PackageManagerInternal.class)); 474 mUserManagerInternal = Objects.requireNonNull( 475 LocalServices.getService(UserManagerInternal.class)); 476 mUsageStatsManagerInternal = Objects.requireNonNull( 477 LocalServices.getService(UsageStatsManagerInternal.class)); 478 mActivityManagerInternal = Objects.requireNonNull( 479 LocalServices.getService(ActivityManagerInternal.class)); 480 481 mUriGrantsManager = Objects.requireNonNull(UriGrantsManager.getService()); 482 mUriGrantsManagerInternal = Objects.requireNonNull( 483 LocalServices.getService(UriGrantsManagerInternal.class)); 484 mUriPermissionOwner = mUriGrantsManagerInternal.newUriPermissionOwner(TAG); 485 mRoleManager = Objects.requireNonNull(mContext.getSystemService(RoleManager.class)); 486 487 mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock); 488 mShortcutBitmapSaver = new ShortcutBitmapSaver(this); 489 mShortcutDumpFiles = new ShortcutDumpFiles(this); 490 mIsAppSearchEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, 491 SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, false); 492 493 if (onlyForPackageManagerApis) { 494 return; // Don't do anything further. For unit tests only. 495 } 496 497 // Register receivers. 498 499 // We need to set a priority, so let's just not use PackageMonitor for now. 500 // TODO Refactor PackageMonitor to support priorities. 501 final IntentFilter packageFilter = new IntentFilter(); 502 packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 503 packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 504 packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); 505 packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); 506 packageFilter.addDataScheme("package"); 507 packageFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 508 mContext.registerReceiverAsUser(mPackageMonitor, UserHandle.ALL, 509 packageFilter, null, mHandler); 510 511 final IntentFilter localeFilter = new IntentFilter(); 512 localeFilter.addAction(Intent.ACTION_LOCALE_CHANGED); 513 localeFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 514 mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, 515 localeFilter, null, mHandler); 516 517 IntentFilter shutdownFilter = new IntentFilter(); 518 shutdownFilter.addAction(Intent.ACTION_SHUTDOWN); 519 shutdownFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 520 mContext.registerReceiverAsUser(mShutdownReceiver, UserHandle.SYSTEM, 521 shutdownFilter, null, mHandler); 522 523 injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE 524 | ActivityManager.UID_OBSERVER_GONE); 525 526 injectRegisterRoleHoldersListener(mOnRoleHoldersChangedListener); 527 } 528 isAppSearchEnabled()529 boolean isAppSearchEnabled() { 530 return mIsAppSearchEnabled; 531 } 532 getStatStartTime()533 long getStatStartTime() { 534 return mStatLogger.getTime(); 535 } 536 logDurationStat(int statId, long start)537 void logDurationStat(int statId, long start) { 538 mStatLogger.logDurationStat(statId, start); 539 } 540 injectGetLocaleTagsForUser(@serIdInt int userId)541 public String injectGetLocaleTagsForUser(@UserIdInt int userId) { 542 // TODO This should get the per-user locale. b/30123329 b/30119489 543 return LocaleList.getDefault().toLanguageTags(); 544 } 545 546 private final OnRoleHoldersChangedListener mOnRoleHoldersChangedListener = 547 new OnRoleHoldersChangedListener() { 548 @Override 549 public void onRoleHoldersChanged(String roleName, UserHandle user) { 550 if (RoleManager.ROLE_HOME.equals(roleName)) { 551 injectPostToHandler(() -> handleOnDefaultLauncherChanged(user.getIdentifier())); 552 } 553 } 554 }; 555 handleOnDefaultLauncherChanged(int userId)556 void handleOnDefaultLauncherChanged(int userId) { 557 if (DEBUG) { 558 Slog.v(TAG, "Default launcher changed for user: " + userId); 559 } 560 561 // Default launcher is removed or changed, revoke all URI permissions. 562 mUriGrantsManagerInternal.revokeUriPermissionFromOwner(mUriPermissionOwner, null, ~0, 0); 563 564 synchronized (mLock) { 565 // Clear the launcher cache for this user. It will be set again next time the default 566 // launcher is read from RoleManager. 567 if (isUserLoadedLocked(userId)) { 568 getUserShortcutsLocked(userId).setCachedLauncher(null); 569 } 570 } 571 } 572 573 final private IUidObserver mUidObserver = new IUidObserver.Stub() { 574 @Override 575 public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { 576 injectPostToHandler(() -> handleOnUidStateChanged(uid, procState)); 577 } 578 579 @Override 580 public void onUidGone(int uid, boolean disabled) { 581 injectPostToHandler(() -> 582 handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT)); 583 } 584 585 @Override 586 public void onUidActive(int uid) { 587 } 588 589 @Override 590 public void onUidIdle(int uid, boolean disabled) { 591 } 592 593 @Override public void onUidCachedChanged(int uid, boolean cached) { 594 } 595 }; 596 handleOnUidStateChanged(int uid, int procState)597 void handleOnUidStateChanged(int uid, int procState) { 598 if (DEBUG_PROCSTATE) { 599 Slog.d(TAG, "onUidStateChanged: uid=" + uid + " state=" + procState); 600 } 601 synchronized (mLock) { 602 mUidState.put(uid, procState); 603 604 // We need to keep track of last time an app comes to foreground. 605 // See ShortcutPackage.getApiCallCount() for how it's used. 606 // It doesn't have to be persisted, but it needs to be the elapsed time. 607 if (isProcessStateForeground(procState)) { 608 mUidLastForegroundElapsedTime.put(uid, injectElapsedRealtime()); 609 } 610 } 611 } 612 isProcessStateForeground(int processState)613 private boolean isProcessStateForeground(int processState) { 614 return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD; 615 } 616 617 @GuardedBy("mLock") isUidForegroundLocked(int uid)618 boolean isUidForegroundLocked(int uid) { 619 if (uid == Process.SYSTEM_UID) { 620 // IUidObserver doesn't report the state of SYSTEM, but it always has bound services, 621 // so it's foreground anyway. 622 return true; 623 } 624 // First, check with the local cache. 625 if (isProcessStateForeground(mUidState.get(uid, ActivityManager.MAX_PROCESS_STATE))) { 626 return true; 627 } 628 // If the cache says background, reach out to AM. Since it'll internally need to hold 629 // the AM lock, we use it as a last resort. 630 return isProcessStateForeground(mActivityManagerInternal.getUidProcessState(uid)); 631 } 632 633 @GuardedBy("mLock") getUidLastForegroundElapsedTimeLocked(int uid)634 long getUidLastForegroundElapsedTimeLocked(int uid) { 635 return mUidLastForegroundElapsedTime.get(uid); 636 } 637 638 /** 639 * System service lifecycle. 640 */ 641 public static final class Lifecycle extends SystemService { 642 final ShortcutService mService; 643 Lifecycle(Context context)644 public Lifecycle(Context context) { 645 super(context); 646 if (DEBUG) { 647 Binder.LOG_RUNTIME_EXCEPTION = true; 648 } 649 mService = new ShortcutService(context); 650 } 651 652 @Override onStart()653 public void onStart() { 654 publishBinderService(Context.SHORTCUT_SERVICE, mService); 655 } 656 657 @Override onBootPhase(int phase)658 public void onBootPhase(int phase) { 659 mService.onBootPhase(phase); 660 } 661 662 @Override onUserStopping(@onNull TargetUser user)663 public void onUserStopping(@NonNull TargetUser user) { 664 mService.handleStopUser(user.getUserIdentifier()); 665 } 666 667 @Override onUserUnlocking(@onNull TargetUser user)668 public void onUserUnlocking(@NonNull TargetUser user) { 669 mService.handleUnlockUser(user.getUserIdentifier()); 670 } 671 } 672 673 /** lifecycle event */ onBootPhase(int phase)674 void onBootPhase(int phase) { 675 if (DEBUG || DEBUG_REBOOT) { 676 Slog.d(TAG, "onBootPhase: " + phase); 677 } 678 switch (phase) { 679 case SystemService.PHASE_LOCK_SETTINGS_READY: 680 initialize(); 681 break; 682 case SystemService.PHASE_BOOT_COMPLETED: 683 mBootCompleted.set(true); 684 break; 685 } 686 } 687 688 /** lifecycle event */ handleUnlockUser(int userId)689 void handleUnlockUser(int userId) { 690 if (DEBUG || DEBUG_REBOOT) { 691 Slog.d(TAG, "handleUnlockUser: user=" + userId); 692 } 693 synchronized (mUnlockedUsers) { 694 mUnlockedUsers.put(userId, true); 695 } 696 697 // Preload the user data. 698 // Note, we don't use mHandler here but instead just start a new thread. 699 // This is because mHandler (which uses com.android.internal.os.BackgroundThread) is very 700 // busy at this point and this could take hundreds of milliseconds, which would be too 701 // late since the launcher would already have started. 702 // So we just create a new thread. This code runs rarely, so we don't use a thread pool 703 // or anything. 704 final long start = getStatStartTime(); 705 injectRunOnNewThread(() -> { 706 synchronized (mLock) { 707 logDurationStat(Stats.ASYNC_PRELOAD_USER_DELAY, start); 708 getUserShortcutsLocked(userId); 709 } 710 }); 711 } 712 713 /** lifecycle event */ handleStopUser(int userId)714 void handleStopUser(int userId) { 715 if (DEBUG || DEBUG_REBOOT) { 716 Slog.d(TAG, "handleStopUser: user=" + userId); 717 } 718 synchronized (mLock) { 719 unloadUserLocked(userId); 720 721 synchronized (mUnlockedUsers) { 722 mUnlockedUsers.put(userId, false); 723 } 724 } 725 } 726 727 @GuardedBy("mLock") unloadUserLocked(int userId)728 private void unloadUserLocked(int userId) { 729 if (DEBUG || DEBUG_REBOOT) { 730 Slog.d(TAG, "unloadUserLocked: user=" + userId); 731 } 732 // Save all dirty information. 733 saveDirtyInfo(); 734 735 // Unload 736 mUsers.delete(userId); 737 } 738 739 /** Return the base state file name */ getBaseStateFile()740 private AtomicFile getBaseStateFile() { 741 final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE); 742 path.mkdirs(); 743 return new AtomicFile(path); 744 } 745 746 /** 747 * Init the instance. (load the state file, etc) 748 */ initialize()749 private void initialize() { 750 synchronized (mLock) { 751 loadConfigurationLocked(); 752 loadBaseStateLocked(); 753 } 754 } 755 756 /** 757 * Load the configuration from Settings. 758 */ loadConfigurationLocked()759 private void loadConfigurationLocked() { 760 updateConfigurationLocked(injectShortcutManagerConstants()); 761 } 762 763 /** 764 * Load the configuration from Settings. 765 */ 766 @VisibleForTesting updateConfigurationLocked(String config)767 boolean updateConfigurationLocked(String config) { 768 boolean result = true; 769 770 final KeyValueListParser parser = new KeyValueListParser(','); 771 try { 772 parser.setString(config); 773 } catch (IllegalArgumentException e) { 774 // Failed to parse the settings string, log this and move on 775 // with defaults. 776 Slog.e(TAG, "Bad shortcut manager settings", e); 777 result = false; 778 } 779 780 mSaveDelayMillis = Math.max(0, (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS, 781 DEFAULT_SAVE_DELAY_MS)); 782 783 mResetInterval = Math.max(1, parser.getLong( 784 ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC) 785 * 1000L); 786 787 mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong( 788 ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL)); 789 790 mMaxShortcuts = Math.max(0, (int) parser.getLong( 791 ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY)); 792 793 final int iconDimensionDp = Math.max(1, injectIsLowRamDevice() 794 ? (int) parser.getLong( 795 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM, 796 DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP) 797 : (int) parser.getLong( 798 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP, 799 DEFAULT_MAX_ICON_DIMENSION_DP)); 800 801 mMaxIconDimension = injectDipToPixel(iconDimensionDp); 802 803 mIconPersistFormat = CompressFormat.valueOf( 804 parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT)); 805 806 mIconPersistQuality = (int) parser.getLong( 807 ConfigConstants.KEY_ICON_QUALITY, 808 DEFAULT_ICON_PERSIST_QUALITY); 809 810 return result; 811 } 812 813 @VisibleForTesting injectShortcutManagerConstants()814 String injectShortcutManagerConstants() { 815 return android.provider.Settings.Global.getString( 816 mContext.getContentResolver(), 817 android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS); 818 } 819 820 @VisibleForTesting injectDipToPixel(int dip)821 int injectDipToPixel(int dip) { 822 return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, 823 mContext.getResources().getDisplayMetrics()); 824 } 825 826 // === Persisting === 827 828 @Nullable parseStringAttribute(TypedXmlPullParser parser, String attribute)829 static String parseStringAttribute(TypedXmlPullParser parser, String attribute) { 830 return parser.getAttributeValue(null, attribute); 831 } 832 parseBooleanAttribute(TypedXmlPullParser parser, String attribute)833 static boolean parseBooleanAttribute(TypedXmlPullParser parser, String attribute) { 834 return parseLongAttribute(parser, attribute) == 1; 835 } 836 parseBooleanAttribute(TypedXmlPullParser parser, String attribute, boolean def)837 static boolean parseBooleanAttribute(TypedXmlPullParser parser, String attribute, boolean def) { 838 return parseLongAttribute(parser, attribute, (def ? 1 : 0)) == 1; 839 } 840 parseIntAttribute(TypedXmlPullParser parser, String attribute)841 static int parseIntAttribute(TypedXmlPullParser parser, String attribute) { 842 return (int) parseLongAttribute(parser, attribute); 843 } 844 parseIntAttribute(TypedXmlPullParser parser, String attribute, int def)845 static int parseIntAttribute(TypedXmlPullParser parser, String attribute, int def) { 846 return (int) parseLongAttribute(parser, attribute, def); 847 } 848 parseLongAttribute(TypedXmlPullParser parser, String attribute)849 static long parseLongAttribute(TypedXmlPullParser parser, String attribute) { 850 return parseLongAttribute(parser, attribute, 0); 851 } 852 parseLongAttribute(TypedXmlPullParser parser, String attribute, long def)853 static long parseLongAttribute(TypedXmlPullParser parser, String attribute, long def) { 854 final String value = parseStringAttribute(parser, attribute); 855 if (TextUtils.isEmpty(value)) { 856 return def; 857 } 858 try { 859 return Long.parseLong(value); 860 } catch (NumberFormatException e) { 861 Slog.e(TAG, "Error parsing long " + value); 862 return def; 863 } 864 } 865 866 @Nullable parseComponentNameAttribute(TypedXmlPullParser parser, String attribute)867 static ComponentName parseComponentNameAttribute(TypedXmlPullParser parser, String attribute) { 868 final String value = parseStringAttribute(parser, attribute); 869 if (TextUtils.isEmpty(value)) { 870 return null; 871 } 872 return ComponentName.unflattenFromString(value); 873 } 874 875 @Nullable parseIntentAttributeNoDefault(TypedXmlPullParser parser, String attribute)876 static Intent parseIntentAttributeNoDefault(TypedXmlPullParser parser, String attribute) { 877 final String value = parseStringAttribute(parser, attribute); 878 Intent parsed = null; 879 if (!TextUtils.isEmpty(value)) { 880 try { 881 parsed = Intent.parseUri(value, /* flags =*/ 0); 882 } catch (URISyntaxException e) { 883 Slog.e(TAG, "Error parsing intent", e); 884 } 885 } 886 return parsed; 887 } 888 889 @Nullable parseIntentAttribute(TypedXmlPullParser parser, String attribute)890 static Intent parseIntentAttribute(TypedXmlPullParser parser, String attribute) { 891 Intent parsed = parseIntentAttributeNoDefault(parser, attribute); 892 if (parsed == null) { 893 // Default intent. 894 parsed = new Intent(Intent.ACTION_VIEW); 895 } 896 return parsed; 897 } 898 writeTagValue(TypedXmlSerializer out, String tag, String value)899 static void writeTagValue(TypedXmlSerializer out, String tag, String value) throws IOException { 900 if (TextUtils.isEmpty(value)) return; 901 902 out.startTag(null, tag); 903 out.attribute(null, ATTR_VALUE, value); 904 out.endTag(null, tag); 905 } 906 writeTagValue(TypedXmlSerializer out, String tag, long value)907 static void writeTagValue(TypedXmlSerializer out, String tag, long value) throws IOException { 908 writeTagValue(out, tag, Long.toString(value)); 909 } 910 writeTagValue(TypedXmlSerializer out, String tag, ComponentName name)911 static void writeTagValue(TypedXmlSerializer out, String tag, ComponentName name) 912 throws IOException { 913 if (name == null) return; 914 writeTagValue(out, tag, name.flattenToString()); 915 } 916 writeTagExtra(TypedXmlSerializer out, String tag, PersistableBundle bundle)917 static void writeTagExtra(TypedXmlSerializer out, String tag, PersistableBundle bundle) 918 throws IOException, XmlPullParserException { 919 if (bundle == null) return; 920 921 out.startTag(null, tag); 922 bundle.saveToXml(out); 923 out.endTag(null, tag); 924 } 925 writeAttr(TypedXmlSerializer out, String name, CharSequence value)926 static void writeAttr(TypedXmlSerializer out, String name, CharSequence value) 927 throws IOException { 928 if (TextUtils.isEmpty(value)) return; 929 930 out.attribute(null, name, value.toString()); 931 } 932 writeAttr(TypedXmlSerializer out, String name, long value)933 static void writeAttr(TypedXmlSerializer out, String name, long value) throws IOException { 934 writeAttr(out, name, String.valueOf(value)); 935 } 936 writeAttr(TypedXmlSerializer out, String name, boolean value)937 static void writeAttr(TypedXmlSerializer out, String name, boolean value) throws IOException { 938 if (value) { 939 writeAttr(out, name, "1"); 940 } else { 941 writeAttr(out, name, "0"); 942 } 943 } 944 writeAttr(TypedXmlSerializer out, String name, ComponentName comp)945 static void writeAttr(TypedXmlSerializer out, String name, ComponentName comp) 946 throws IOException { 947 if (comp == null) return; 948 writeAttr(out, name, comp.flattenToString()); 949 } 950 writeAttr(TypedXmlSerializer out, String name, Intent intent)951 static void writeAttr(TypedXmlSerializer out, String name, Intent intent) throws IOException { 952 if (intent == null) return; 953 954 writeAttr(out, name, intent.toUri(/* flags =*/ 0)); 955 } 956 957 @GuardedBy("mLock") 958 @VisibleForTesting saveBaseStateLocked()959 void saveBaseStateLocked() { 960 final AtomicFile file = getBaseStateFile(); 961 if (DEBUG || DEBUG_REBOOT) { 962 Slog.d(TAG, "Saving to " + file.getBaseFile()); 963 } 964 965 FileOutputStream outs = null; 966 try { 967 outs = file.startWrite(); 968 969 // Write to XML 970 TypedXmlSerializer out = Xml.resolveSerializer(outs); 971 out.startDocument(null, true); 972 out.startTag(null, TAG_ROOT); 973 974 // Body. 975 writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime); 976 977 // Epilogue. 978 out.endTag(null, TAG_ROOT); 979 out.endDocument(); 980 981 // Close. 982 file.finishWrite(outs); 983 } catch (IOException e) { 984 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); 985 file.failWrite(outs); 986 } 987 } 988 989 @GuardedBy("mLock") loadBaseStateLocked()990 private void loadBaseStateLocked() { 991 mRawLastResetTime = 0; 992 993 final AtomicFile file = getBaseStateFile(); 994 if (DEBUG || DEBUG_REBOOT) { 995 Slog.d(TAG, "Loading from " + file.getBaseFile()); 996 } 997 try (FileInputStream in = file.openRead()) { 998 TypedXmlPullParser parser = Xml.resolvePullParser(in); 999 1000 int type; 1001 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 1002 if (type != XmlPullParser.START_TAG) { 1003 continue; 1004 } 1005 final int depth = parser.getDepth(); 1006 // Check the root tag 1007 final String tag = parser.getName(); 1008 if (depth == 1) { 1009 if (!TAG_ROOT.equals(tag)) { 1010 Slog.e(TAG, "Invalid root tag: " + tag); 1011 return; 1012 } 1013 continue; 1014 } 1015 // Assume depth == 2 1016 switch (tag) { 1017 case TAG_LAST_RESET_TIME: 1018 mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE); 1019 break; 1020 default: 1021 Slog.e(TAG, "Invalid tag: " + tag); 1022 break; 1023 } 1024 } 1025 } catch (FileNotFoundException e) { 1026 // Use the default 1027 } catch (IOException | XmlPullParserException e) { 1028 Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); 1029 1030 mRawLastResetTime = 0; 1031 } 1032 // Adjust the last reset time. 1033 getLastResetTimeLocked(); 1034 } 1035 1036 @VisibleForTesting getUserFile(@serIdInt int userId)1037 final File getUserFile(@UserIdInt int userId) { 1038 return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); 1039 } 1040 1041 @GuardedBy("mLock") saveUserLocked(@serIdInt int userId)1042 private void saveUserLocked(@UserIdInt int userId) { 1043 final File path = getUserFile(userId); 1044 if (DEBUG || DEBUG_REBOOT) { 1045 Slog.d(TAG, "Saving to " + path); 1046 } 1047 1048 mShortcutBitmapSaver.waitForAllSavesLocked(); 1049 1050 path.getParentFile().mkdirs(); 1051 final AtomicFile file = new AtomicFile(path); 1052 FileOutputStream os = null; 1053 try { 1054 os = file.startWrite(); 1055 1056 saveUserInternalLocked(userId, os, /* forBackup= */ false); 1057 1058 file.finishWrite(os); 1059 1060 // Remove all dangling bitmap files. 1061 cleanupDanglingBitmapDirectoriesLocked(userId); 1062 } catch (XmlPullParserException | IOException e) { 1063 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); 1064 file.failWrite(os); 1065 } 1066 1067 getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger); 1068 } 1069 1070 @GuardedBy("mLock") saveUserInternalLocked(@serIdInt int userId, OutputStream os, boolean forBackup)1071 private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os, 1072 boolean forBackup) throws IOException, XmlPullParserException { 1073 1074 // Write to XML 1075 final TypedXmlSerializer out; 1076 if (forBackup) { 1077 out = Xml.newFastSerializer(); 1078 out.setOutput(os, StandardCharsets.UTF_8.name()); 1079 } else { 1080 out = Xml.resolveSerializer(os); 1081 } 1082 out.startDocument(null, true); 1083 1084 getUserShortcutsLocked(userId).saveToXml(out, forBackup); 1085 1086 out.endDocument(); 1087 1088 os.flush(); 1089 } 1090 throwForInvalidTag(int depth, String tag)1091 static IOException throwForInvalidTag(int depth, String tag) throws IOException { 1092 throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth)); 1093 } 1094 warnForInvalidTag(int depth, String tag)1095 static void warnForInvalidTag(int depth, String tag) throws IOException { 1096 Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth)); 1097 } 1098 1099 @Nullable loadUserLocked(@serIdInt int userId)1100 private ShortcutUser loadUserLocked(@UserIdInt int userId) { 1101 final File path = getUserFile(userId); 1102 if (DEBUG || DEBUG_REBOOT) { 1103 Slog.d(TAG, "Loading from " + path); 1104 } 1105 final AtomicFile file = new AtomicFile(path); 1106 1107 final FileInputStream in; 1108 try { 1109 in = file.openRead(); 1110 } catch (FileNotFoundException e) { 1111 if (DEBUG || DEBUG_REBOOT) { 1112 Slog.d(TAG, "Not found " + path); 1113 } 1114 return null; 1115 } 1116 try { 1117 final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false); 1118 return ret; 1119 } catch (IOException | XmlPullParserException | InvalidFileFormatException e) { 1120 Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); 1121 return null; 1122 } finally { 1123 IoUtils.closeQuietly(in); 1124 } 1125 } 1126 loadUserInternal(@serIdInt int userId, InputStream is, boolean fromBackup)1127 private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, 1128 boolean fromBackup) throws XmlPullParserException, IOException, 1129 InvalidFileFormatException { 1130 1131 ShortcutUser ret = null; 1132 TypedXmlPullParser parser; 1133 if (fromBackup) { 1134 parser = Xml.newFastPullParser(); 1135 parser.setInput(is, StandardCharsets.UTF_8.name()); 1136 } else { 1137 parser = Xml.resolvePullParser(is); 1138 } 1139 1140 int type; 1141 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 1142 if (type != XmlPullParser.START_TAG) { 1143 continue; 1144 } 1145 final int depth = parser.getDepth(); 1146 1147 final String tag = parser.getName(); 1148 if (DEBUG_LOAD || DEBUG_REBOOT) { 1149 Slog.d(TAG, String.format("depth=%d type=%d name=%s", 1150 depth, type, tag)); 1151 } 1152 if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) { 1153 ret = ShortcutUser.loadFromXml(this, parser, userId, fromBackup); 1154 continue; 1155 } 1156 throwForInvalidTag(depth, tag); 1157 } 1158 return ret; 1159 } 1160 scheduleSaveBaseState()1161 private void scheduleSaveBaseState() { 1162 scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state. 1163 } 1164 scheduleSaveUser(@serIdInt int userId)1165 void scheduleSaveUser(@UserIdInt int userId) { 1166 scheduleSaveInner(userId); 1167 } 1168 1169 // In order to re-schedule, we need to reuse the same instance, so keep it in final. 1170 private final Runnable mSaveDirtyInfoRunner = this::saveDirtyInfo; 1171 scheduleSaveInner(@serIdInt int userId)1172 private void scheduleSaveInner(@UserIdInt int userId) { 1173 if (DEBUG || DEBUG_REBOOT) { 1174 Slog.d(TAG, "Scheduling to save for " + userId); 1175 } 1176 synchronized (mLock) { 1177 if (!mDirtyUserIds.contains(userId)) { 1178 mDirtyUserIds.add(userId); 1179 } 1180 } 1181 // If already scheduled, remove that and re-schedule in N seconds. 1182 mHandler.removeCallbacks(mSaveDirtyInfoRunner); 1183 mHandler.postDelayed(mSaveDirtyInfoRunner, mSaveDelayMillis); 1184 } 1185 1186 @VisibleForTesting saveDirtyInfo()1187 void saveDirtyInfo() { 1188 if (DEBUG || DEBUG_REBOOT) { 1189 Slog.d(TAG, "saveDirtyInfo"); 1190 } 1191 if (mShutdown.get()) { 1192 return; 1193 } 1194 try { 1195 synchronized (mLock) { 1196 for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) { 1197 final int userId = mDirtyUserIds.get(i); 1198 if (userId == UserHandle.USER_NULL) { // USER_NULL for base state. 1199 saveBaseStateLocked(); 1200 } else { 1201 saveUserLocked(userId); 1202 } 1203 } 1204 mDirtyUserIds.clear(); 1205 } 1206 } catch (Exception e) { 1207 wtf("Exception in saveDirtyInfo", e); 1208 } 1209 } 1210 1211 /** Return the last reset time. */ 1212 @GuardedBy("mLock") getLastResetTimeLocked()1213 long getLastResetTimeLocked() { 1214 updateTimesLocked(); 1215 return mRawLastResetTime; 1216 } 1217 1218 /** Return the next reset time. */ 1219 @GuardedBy("mLock") getNextResetTimeLocked()1220 long getNextResetTimeLocked() { 1221 updateTimesLocked(); 1222 return mRawLastResetTime + mResetInterval; 1223 } 1224 isClockValid(long time)1225 static boolean isClockValid(long time) { 1226 return time >= 1420070400; // Thu, 01 Jan 2015 00:00:00 GMT 1227 } 1228 1229 /** 1230 * Update the last reset time. 1231 */ 1232 @GuardedBy("mLock") updateTimesLocked()1233 private void updateTimesLocked() { 1234 1235 final long now = injectCurrentTimeMillis(); 1236 1237 final long prevLastResetTime = mRawLastResetTime; 1238 1239 if (mRawLastResetTime == 0) { // first launch. 1240 // TODO Randomize?? 1241 mRawLastResetTime = now; 1242 } else if (now < mRawLastResetTime) { 1243 // Clock rewound. 1244 if (isClockValid(now)) { 1245 Slog.w(TAG, "Clock rewound"); 1246 // TODO Randomize?? 1247 mRawLastResetTime = now; 1248 } 1249 } else { 1250 if ((mRawLastResetTime + mResetInterval) <= now) { 1251 final long offset = mRawLastResetTime % mResetInterval; 1252 mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset; 1253 } 1254 } 1255 if (prevLastResetTime != mRawLastResetTime) { 1256 scheduleSaveBaseState(); 1257 } 1258 } 1259 1260 // Requires mLock held, but "Locked" prefix would look weired so we just say "L". isUserUnlockedL(@serIdInt int userId)1261 protected boolean isUserUnlockedL(@UserIdInt int userId) { 1262 // First, check the local copy. 1263 synchronized (mUnlockedUsers) { 1264 if (mUnlockedUsers.get(userId)) { 1265 return true; 1266 } 1267 } 1268 1269 // If the local copy says the user is locked, check with AM for the actual state, since 1270 // the user might just have been unlocked. 1271 // Note we just don't use isUserUnlockingOrUnlocked() here, because it'll return false 1272 // when the user is STOPPING, which we still want to consider as "unlocked". 1273 return mUserManagerInternal.isUserUnlockingOrUnlocked(userId); 1274 } 1275 1276 // Requires mLock held, but "Locked" prefix would look weired so we jsut say "L". throwIfUserLockedL(@serIdInt int userId)1277 void throwIfUserLockedL(@UserIdInt int userId) { 1278 if (!isUserUnlockedL(userId)) { 1279 throw new IllegalStateException("User " + userId + " is locked or not running"); 1280 } 1281 } 1282 1283 @GuardedBy("mLock") 1284 @NonNull isUserLoadedLocked(@serIdInt int userId)1285 private boolean isUserLoadedLocked(@UserIdInt int userId) { 1286 return mUsers.get(userId) != null; 1287 } 1288 1289 private int mLastLockedUser = -1; 1290 1291 /** Return the per-user state. */ 1292 @GuardedBy("mLock") 1293 @NonNull getUserShortcutsLocked(@serIdInt int userId)1294 ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) { 1295 if (!isUserUnlockedL(userId)) { 1296 // Only do wtf once for each user. (until the user is unlocked) 1297 if (userId != mLastLockedUser) { 1298 wtf("User still locked"); 1299 mLastLockedUser = userId; 1300 } 1301 } else { 1302 mLastLockedUser = -1; 1303 } 1304 1305 ShortcutUser userPackages = mUsers.get(userId); 1306 if (userPackages == null) { 1307 userPackages = loadUserLocked(userId); 1308 if (userPackages == null) { 1309 userPackages = new ShortcutUser(this, userId); 1310 } 1311 mUsers.put(userId, userPackages); 1312 1313 // Also when a user's data is first accessed, scan all packages. 1314 checkPackageChanges(userId); 1315 } 1316 return userPackages; 1317 } 1318 1319 /** Return the non-persistent per-user state. */ 1320 @GuardedBy("mNonPersistentUsersLock") 1321 @NonNull getNonPersistentUserLocked(@serIdInt int userId)1322 ShortcutNonPersistentUser getNonPersistentUserLocked(@UserIdInt int userId) { 1323 ShortcutNonPersistentUser ret = mShortcutNonPersistentUsers.get(userId); 1324 if (ret == null) { 1325 ret = new ShortcutNonPersistentUser(this, userId); 1326 mShortcutNonPersistentUsers.put(userId, ret); 1327 } 1328 return ret; 1329 } 1330 1331 @GuardedBy("mLock") forEachLoadedUserLocked(@onNull Consumer<ShortcutUser> c)1332 void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) { 1333 for (int i = mUsers.size() - 1; i >= 0; i--) { 1334 c.accept(mUsers.valueAt(i)); 1335 } 1336 } 1337 1338 /** 1339 * Return the per-user per-package state. If the caller is a publisher, use 1340 * {@link #getPackageShortcutsForPublisherLocked} instead. 1341 */ 1342 @GuardedBy("mLock") 1343 @NonNull getPackageShortcutsLocked( @onNull String packageName, @UserIdInt int userId)1344 ShortcutPackage getPackageShortcutsLocked( 1345 @NonNull String packageName, @UserIdInt int userId) { 1346 return getUserShortcutsLocked(userId).getPackageShortcuts(packageName); 1347 } 1348 1349 /** Return the per-user per-package state. Use this when the caller is a publisher. */ 1350 @GuardedBy("mLock") 1351 @NonNull getPackageShortcutsForPublisherLocked( @onNull String packageName, @UserIdInt int userId)1352 ShortcutPackage getPackageShortcutsForPublisherLocked( 1353 @NonNull String packageName, @UserIdInt int userId) { 1354 final ShortcutPackage ret = getUserShortcutsLocked(userId).getPackageShortcuts(packageName); 1355 ret.getUser().onCalledByPublisher(packageName); 1356 return ret; 1357 } 1358 1359 @GuardedBy("mLock") 1360 @NonNull getLauncherShortcutsLocked( @onNull String packageName, @UserIdInt int ownerUserId, @UserIdInt int launcherUserId)1361 ShortcutLauncher getLauncherShortcutsLocked( 1362 @NonNull String packageName, @UserIdInt int ownerUserId, 1363 @UserIdInt int launcherUserId) { 1364 return getUserShortcutsLocked(ownerUserId) 1365 .getLauncherShortcuts(packageName, launcherUserId); 1366 } 1367 1368 // === Caller validation === 1369 removeIconLocked(ShortcutInfo shortcut)1370 void removeIconLocked(ShortcutInfo shortcut) { 1371 mShortcutBitmapSaver.removeIcon(shortcut); 1372 } 1373 cleanupBitmapsForPackage(@serIdInt int userId, String packageName)1374 public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) { 1375 final File packagePath = new File(getUserBitmapFilePath(userId), packageName); 1376 if (!packagePath.isDirectory()) { 1377 return; 1378 } 1379 if (!(FileUtils.deleteContents(packagePath) && packagePath.delete())) { 1380 Slog.w(TAG, "Unable to remove directory " + packagePath); 1381 } 1382 } 1383 1384 /** 1385 * Remove dangling bitmap files for a user. 1386 * 1387 * Note this method must be called with the lock held after calling 1388 * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap 1389 * saves are going on. 1390 */ 1391 @GuardedBy("mLock") cleanupDanglingBitmapDirectoriesLocked(@serIdInt int userId)1392 private void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) { 1393 if (DEBUG) { 1394 Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId); 1395 } 1396 final long start = getStatStartTime(); 1397 1398 final ShortcutUser user = getUserShortcutsLocked(userId); 1399 1400 final File bitmapDir = getUserBitmapFilePath(userId); 1401 final File[] children = bitmapDir.listFiles(); 1402 if (children == null) { 1403 return; 1404 } 1405 for (File child : children) { 1406 if (!child.isDirectory()) { 1407 continue; 1408 } 1409 final String packageName = child.getName(); 1410 if (DEBUG) { 1411 Slog.d(TAG, "cleanupDanglingBitmaps: Found directory=" + packageName); 1412 } 1413 if (!user.hasPackage(packageName)) { 1414 if (DEBUG) { 1415 Slog.d(TAG, "Removing dangling bitmap directory: " + packageName); 1416 } 1417 cleanupBitmapsForPackage(userId, packageName); 1418 } else { 1419 cleanupDanglingBitmapFilesLocked(userId, user, packageName, child); 1420 } 1421 } 1422 logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start); 1423 } 1424 1425 /** 1426 * Remove dangling bitmap files for a package. 1427 * 1428 * Note this method must be called with the lock held after calling 1429 * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap 1430 * saves are going on. 1431 */ cleanupDanglingBitmapFilesLocked(@serIdInt int userId, @NonNull ShortcutUser user, @NonNull String packageName, @NonNull File path)1432 private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user, 1433 @NonNull String packageName, @NonNull File path) { 1434 final ArraySet<String> usedFiles = 1435 user.getPackageShortcuts(packageName).getUsedBitmapFiles(); 1436 1437 for (File child : path.listFiles()) { 1438 if (!child.isFile()) { 1439 continue; 1440 } 1441 final String name = child.getName(); 1442 if (!usedFiles.contains(name)) { 1443 if (DEBUG) { 1444 Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath()); 1445 } 1446 child.delete(); 1447 } 1448 } 1449 } 1450 1451 @VisibleForTesting 1452 static class FileOutputStreamWithPath extends FileOutputStream { 1453 private final File mFile; 1454 FileOutputStreamWithPath(File file)1455 public FileOutputStreamWithPath(File file) throws FileNotFoundException { 1456 super(file); 1457 mFile = file; 1458 } 1459 getFile()1460 public File getFile() { 1461 return mFile; 1462 } 1463 } 1464 1465 /** 1466 * Build the cached bitmap filename for a shortcut icon. 1467 * 1468 * The filename will be based on the ID, except certain characters will be escaped. 1469 */ openIconFileForWrite(@serIdInt int userId, ShortcutInfo shortcut)1470 FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut) 1471 throws IOException { 1472 final File packagePath = new File(getUserBitmapFilePath(userId), 1473 shortcut.getPackage()); 1474 if (!packagePath.isDirectory()) { 1475 packagePath.mkdirs(); 1476 if (!packagePath.isDirectory()) { 1477 throw new IOException("Unable to create directory " + packagePath); 1478 } 1479 SELinux.restorecon(packagePath); 1480 } 1481 1482 final String baseName = String.valueOf(injectCurrentTimeMillis()); 1483 for (int suffix = 0; ; suffix++) { 1484 final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png"; 1485 final File file = new File(packagePath, filename); 1486 if (!file.exists()) { 1487 if (DEBUG) { 1488 Slog.d(TAG, "Saving icon to " + file.getAbsolutePath()); 1489 } 1490 return new FileOutputStreamWithPath(file); 1491 } 1492 } 1493 } 1494 saveIconAndFixUpShortcutLocked(ShortcutInfo shortcut)1495 void saveIconAndFixUpShortcutLocked(ShortcutInfo shortcut) { 1496 if (shortcut.hasIconFile() || shortcut.hasIconResource() || shortcut.hasIconUri()) { 1497 return; 1498 } 1499 1500 final long token = injectClearCallingIdentity(); 1501 try { 1502 // Clear icon info on the shortcut. 1503 removeIconLocked(shortcut); 1504 1505 final Icon icon = shortcut.getIcon(); 1506 if (icon == null) { 1507 return; // has no icon 1508 } 1509 int maxIconDimension = mMaxIconDimension; 1510 Bitmap bitmap; 1511 try { 1512 switch (icon.getType()) { 1513 case Icon.TYPE_RESOURCE: { 1514 injectValidateIconResPackage(shortcut, icon); 1515 1516 shortcut.setIconResourceId(icon.getResId()); 1517 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES); 1518 return; 1519 } 1520 case Icon.TYPE_URI: 1521 shortcut.setIconUri(icon.getUriString()); 1522 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_URI); 1523 return; 1524 case Icon.TYPE_URI_ADAPTIVE_BITMAP: 1525 shortcut.setIconUri(icon.getUriString()); 1526 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_URI 1527 | ShortcutInfo.FLAG_ADAPTIVE_BITMAP); 1528 return; 1529 case Icon.TYPE_BITMAP: 1530 bitmap = icon.getBitmap(); // Don't recycle in this case. 1531 break; 1532 case Icon.TYPE_ADAPTIVE_BITMAP: { 1533 bitmap = icon.getBitmap(); // Don't recycle in this case. 1534 maxIconDimension *= (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction()); 1535 break; 1536 } 1537 default: 1538 // This shouldn't happen because we've already validated the icon, but 1539 // just in case. 1540 throw ShortcutInfo.getInvalidIconException(); 1541 } 1542 mShortcutBitmapSaver.saveBitmapLocked(shortcut, 1543 maxIconDimension, mIconPersistFormat, mIconPersistQuality); 1544 } finally { 1545 // Once saved, we won't use the original icon information, so null it out. 1546 shortcut.clearIcon(); 1547 } 1548 } finally { 1549 injectRestoreCallingIdentity(token); 1550 } 1551 } 1552 1553 // Unfortunately we can't do this check in unit tests because we fake creator package names, 1554 // so override in unit tests. 1555 // TODO CTS this case. injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon)1556 void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) { 1557 if (!shortcut.getPackage().equals(icon.getResPackage())) { 1558 throw new IllegalArgumentException( 1559 "Icon resource must reside in shortcut owner package"); 1560 } 1561 } 1562 shrinkBitmap(Bitmap in, int maxSize)1563 static Bitmap shrinkBitmap(Bitmap in, int maxSize) { 1564 // Original width/height. 1565 final int ow = in.getWidth(); 1566 final int oh = in.getHeight(); 1567 if ((ow <= maxSize) && (oh <= maxSize)) { 1568 if (DEBUG) { 1569 Slog.d(TAG, String.format("Icon size %dx%d, no need to shrink", ow, oh)); 1570 } 1571 return in; 1572 } 1573 final int longerDimension = Math.max(ow, oh); 1574 1575 // New width and height. 1576 final int nw = ow * maxSize / longerDimension; 1577 final int nh = oh * maxSize / longerDimension; 1578 if (DEBUG) { 1579 Slog.d(TAG, String.format("Icon size %dx%d, shrinking to %dx%d", 1580 ow, oh, nw, nh)); 1581 } 1582 1583 final Bitmap scaledBitmap = Bitmap.createBitmap(nw, nh, Bitmap.Config.ARGB_8888); 1584 final Canvas c = new Canvas(scaledBitmap); 1585 1586 final RectF dst = new RectF(0, 0, nw, nh); 1587 1588 c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null); 1589 1590 return scaledBitmap; 1591 } 1592 1593 /** 1594 * For a shortcut, update all resource names from resource IDs, and also update all 1595 * resource-based strings. 1596 */ fixUpShortcutResourceNamesAndValues(ShortcutInfo si)1597 void fixUpShortcutResourceNamesAndValues(ShortcutInfo si) { 1598 final Resources publisherRes = injectGetResourcesForApplicationAsUser( 1599 si.getPackage(), si.getUserId()); 1600 if (publisherRes != null) { 1601 final long start = getStatStartTime(); 1602 try { 1603 si.lookupAndFillInResourceNames(publisherRes); 1604 } finally { 1605 logDurationStat(Stats.RESOURCE_NAME_LOOKUP, start); 1606 } 1607 si.resolveResourceStrings(publisherRes); 1608 } 1609 } 1610 1611 // === Caller validation === 1612 isCallerSystem()1613 private boolean isCallerSystem() { 1614 final int callingUid = injectBinderCallingUid(); 1615 return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID); 1616 } 1617 isCallerShell()1618 private boolean isCallerShell() { 1619 final int callingUid = injectBinderCallingUid(); 1620 return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID; 1621 } 1622 enforceSystemOrShell()1623 private void enforceSystemOrShell() { 1624 if (!(isCallerSystem() || isCallerShell())) { 1625 throw new SecurityException("Caller must be system or shell"); 1626 } 1627 } 1628 enforceShell()1629 private void enforceShell() { 1630 if (!isCallerShell()) { 1631 throw new SecurityException("Caller must be shell"); 1632 } 1633 } 1634 enforceSystem()1635 private void enforceSystem() { 1636 if (!isCallerSystem()) { 1637 throw new SecurityException("Caller must be system"); 1638 } 1639 } 1640 enforceResetThrottlingPermission()1641 private void enforceResetThrottlingPermission() { 1642 if (isCallerSystem()) { 1643 return; 1644 } 1645 enforceCallingOrSelfPermission( 1646 android.Manifest.permission.RESET_SHORTCUT_MANAGER_THROTTLING, null); 1647 } 1648 enforceCallingOrSelfPermission( @onNull String permission, @Nullable String message)1649 private void enforceCallingOrSelfPermission( 1650 @NonNull String permission, @Nullable String message) { 1651 if (isCallerSystem()) { 1652 return; 1653 } 1654 injectEnforceCallingPermission(permission, message); 1655 } 1656 1657 /** 1658 * Somehow overriding ServiceContext.enforceCallingPermission() in the unit tests would confuse 1659 * mockito. So instead we extracted it here and override it in the tests. 1660 */ 1661 @VisibleForTesting injectEnforceCallingPermission( @onNull String permission, @Nullable String message)1662 void injectEnforceCallingPermission( 1663 @NonNull String permission, @Nullable String message) { 1664 mContext.enforceCallingPermission(permission, message); 1665 } 1666 verifyCallerUserId(@serIdInt int userId)1667 private void verifyCallerUserId(@UserIdInt int userId) { 1668 if (isCallerSystem()) { 1669 return; // no check 1670 } 1671 1672 final int callingUid = injectBinderCallingUid(); 1673 1674 // Otherwise, make sure the arguments are valid. 1675 if (UserHandle.getUserId(callingUid) != userId) { 1676 throw new SecurityException("Invalid user-ID"); 1677 } 1678 } 1679 verifyCaller(@onNull String packageName, @UserIdInt int userId)1680 private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) { 1681 Preconditions.checkStringNotEmpty(packageName, "packageName"); 1682 1683 if (isCallerSystem()) { 1684 return; // no check 1685 } 1686 1687 final int callingUid = injectBinderCallingUid(); 1688 1689 // Otherwise, make sure the arguments are valid. 1690 if (UserHandle.getUserId(callingUid) != userId) { 1691 throw new SecurityException("Invalid user-ID"); 1692 } 1693 if (injectGetPackageUid(packageName, userId) != callingUid) { 1694 throw new SecurityException("Calling package name mismatch"); 1695 } 1696 Preconditions.checkState(!isEphemeralApp(packageName, userId), 1697 "Ephemeral apps can't use ShortcutManager"); 1698 } 1699 verifyCaller(@onNull String packageName, @UserIdInt int userId, @NonNull AndroidFuture ret)1700 private boolean verifyCaller(@NonNull String packageName, @UserIdInt int userId, 1701 @NonNull AndroidFuture ret) { 1702 try { 1703 verifyCaller(packageName, userId); 1704 } catch (Exception e) { 1705 ret.completeExceptionally(e); 1706 return false; 1707 } 1708 return true; 1709 } 1710 verifyShortcutInfoPackage(String callerPackage, ShortcutInfo si)1711 private void verifyShortcutInfoPackage(String callerPackage, ShortcutInfo si) { 1712 if (si == null) { 1713 return; 1714 } 1715 if (!Objects.equals(callerPackage, si.getPackage())) { 1716 android.util.EventLog.writeEvent(0x534e4554, "109824443", -1, ""); 1717 throw new SecurityException("Shortcut package name mismatch"); 1718 } 1719 } 1720 verifyShortcutInfoPackages( String callerPackage, List<ShortcutInfo> list)1721 private void verifyShortcutInfoPackages( 1722 String callerPackage, List<ShortcutInfo> list) { 1723 final int size = list.size(); 1724 for (int i = 0; i < size; i++) { 1725 verifyShortcutInfoPackage(callerPackage, list.get(i)); 1726 } 1727 } 1728 1729 // Overridden in unit tests to execute r synchronously. injectPostToHandler(Runnable r)1730 void injectPostToHandler(Runnable r) { 1731 mHandler.post(r); 1732 } 1733 injectRunOnNewThread(Runnable r)1734 void injectRunOnNewThread(Runnable r) { 1735 new Thread(r).start(); 1736 } 1737 injectPostToHandlerIfAppSearch(Runnable r)1738 void injectPostToHandlerIfAppSearch(Runnable r) { 1739 // TODO: move to background thread when app search is enabled. 1740 r.run(); 1741 } 1742 1743 /** 1744 * @throws IllegalArgumentException if {@code numShortcuts} is bigger than 1745 * {@link #getMaxActivityShortcuts()}. 1746 */ enforceMaxActivityShortcuts(int numShortcuts)1747 void enforceMaxActivityShortcuts(int numShortcuts) { 1748 if (numShortcuts > mMaxShortcuts) { 1749 throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded"); 1750 } 1751 } 1752 1753 /** 1754 * Return the max number of dynamic + manifest shortcuts for each launcher icon. 1755 */ getMaxActivityShortcuts()1756 int getMaxActivityShortcuts() { 1757 return mMaxShortcuts; 1758 } 1759 1760 /** 1761 * - Sends a notification to LauncherApps 1762 * - Write to file 1763 */ packageShortcutsChanged(@onNull String packageName, @UserIdInt int userId, @Nullable final List<ShortcutInfo> changedShortcuts, @Nullable final List<ShortcutInfo> removedShortcuts)1764 void packageShortcutsChanged(@NonNull String packageName, @UserIdInt int userId, 1765 @Nullable final List<ShortcutInfo> changedShortcuts, 1766 @Nullable final List<ShortcutInfo> removedShortcuts) { 1767 notifyListeners(packageName, userId); 1768 notifyShortcutChangeCallbacks(packageName, userId, changedShortcuts, removedShortcuts); 1769 scheduleSaveUser(userId); 1770 } 1771 notifyListeners(@onNull String packageName, @UserIdInt int userId)1772 private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) { 1773 if (DEBUG) { 1774 Slog.d(TAG, String.format( 1775 "Shortcut changes: package=%s, user=%d", packageName, userId)); 1776 } 1777 injectPostToHandler(() -> { 1778 try { 1779 final ArrayList<ShortcutChangeListener> copy; 1780 synchronized (mLock) { 1781 if (!isUserUnlockedL(userId)) { 1782 return; 1783 } 1784 1785 copy = new ArrayList<>(mListeners); 1786 } 1787 // Note onShortcutChanged() needs to be called with the system service permissions. 1788 for (int i = copy.size() - 1; i >= 0; i--) { 1789 copy.get(i).onShortcutChanged(packageName, userId); 1790 } 1791 } catch (Exception ignore) { 1792 } 1793 }); 1794 } 1795 notifyShortcutChangeCallbacks(@onNull String packageName, @UserIdInt int userId, @Nullable final List<ShortcutInfo> changedShortcuts, @Nullable final List<ShortcutInfo> removedShortcuts)1796 private void notifyShortcutChangeCallbacks(@NonNull String packageName, @UserIdInt int userId, 1797 @Nullable final List<ShortcutInfo> changedShortcuts, 1798 @Nullable final List<ShortcutInfo> removedShortcuts) { 1799 final List<ShortcutInfo> changedList = removeNonKeyFields(changedShortcuts); 1800 final List<ShortcutInfo> removedList = removeNonKeyFields(removedShortcuts); 1801 1802 final UserHandle user = UserHandle.of(userId); 1803 injectPostToHandler(() -> { 1804 try { 1805 final ArrayList<LauncherApps.ShortcutChangeCallback> copy; 1806 synchronized (mLock) { 1807 if (!isUserUnlockedL(userId)) { 1808 return; 1809 } 1810 1811 copy = new ArrayList<>(mShortcutChangeCallbacks); 1812 } 1813 for (int i = copy.size() - 1; i >= 0; i--) { 1814 if (!CollectionUtils.isEmpty(changedList)) { 1815 copy.get(i).onShortcutsAddedOrUpdated(packageName, changedList, user); 1816 } 1817 if (!CollectionUtils.isEmpty(removedList)) { 1818 copy.get(i).onShortcutsRemoved(packageName, removedList, user); 1819 } 1820 } 1821 } catch (Exception ignore) { 1822 } 1823 }); 1824 } 1825 removeNonKeyFields(@ullable List<ShortcutInfo> shortcutInfos)1826 private List<ShortcutInfo> removeNonKeyFields(@Nullable List<ShortcutInfo> shortcutInfos) { 1827 if (CollectionUtils.isEmpty(shortcutInfos)) { 1828 return shortcutInfos; 1829 } 1830 1831 final int size = shortcutInfos.size(); 1832 List<ShortcutInfo> keyFieldOnlyShortcuts = new ArrayList<>(size); 1833 1834 for (int i = 0; i < size; i++) { 1835 final ShortcutInfo si = shortcutInfos.get(i); 1836 if (si.hasKeyFieldsOnly()) { 1837 keyFieldOnlyShortcuts.add(si); 1838 } else { 1839 keyFieldOnlyShortcuts.add(si.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO)); 1840 } 1841 } 1842 return keyFieldOnlyShortcuts; 1843 } 1844 1845 /** 1846 * Clean up / validate an incoming shortcut. 1847 * - Make sure all mandatory fields are set. 1848 * - Make sure the intent's extras are persistable, and them to set 1849 * {@link ShortcutInfo#mIntentPersistableExtrases}. Also clear its extras. 1850 * - Clear flags. 1851 */ fixUpIncomingShortcutInfo(@onNull ShortcutInfo shortcut, boolean forUpdate, boolean forPinRequest)1852 private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate, 1853 boolean forPinRequest) { 1854 if (shortcut.isReturnedByServer()) { 1855 Log.w(TAG, 1856 "Re-publishing ShortcutInfo returned by server is not supported." 1857 + " Some information such as icon may lost from shortcut."); 1858 } 1859 Objects.requireNonNull(shortcut, "Null shortcut detected"); 1860 if (shortcut.getActivity() != null) { 1861 Preconditions.checkState( 1862 shortcut.getPackage().equals(shortcut.getActivity().getPackageName()), 1863 "Cannot publish shortcut: activity " + shortcut.getActivity() + " does not" 1864 + " belong to package " + shortcut.getPackage()); 1865 Preconditions.checkState( 1866 injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()), 1867 "Cannot publish shortcut: activity " + shortcut.getActivity() + " is not" 1868 + " main activity"); 1869 } 1870 1871 if (!forUpdate) { 1872 shortcut.enforceMandatoryFields(/* forPinned= */ forPinRequest); 1873 if (!forPinRequest) { 1874 Preconditions.checkState(shortcut.getActivity() != null, 1875 "Cannot publish shortcut: target activity is not set"); 1876 } 1877 } 1878 if (shortcut.getIcon() != null) { 1879 ShortcutInfo.validateIcon(shortcut.getIcon()); 1880 } 1881 1882 shortcut.replaceFlags(shortcut.getFlags() & ShortcutInfo.FLAG_LONG_LIVED); 1883 } 1884 fixUpIncomingShortcutInfo(@onNull ShortcutInfo shortcut, boolean forUpdate)1885 private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) { 1886 fixUpIncomingShortcutInfo(shortcut, forUpdate, /*forPinRequest=*/ false); 1887 } 1888 validateShortcutForPinRequest(@onNull ShortcutInfo shortcut)1889 public void validateShortcutForPinRequest(@NonNull ShortcutInfo shortcut) { 1890 fixUpIncomingShortcutInfo(shortcut, /* forUpdate= */ false, /*forPinRequest=*/ true); 1891 } 1892 1893 /** 1894 * When a shortcut has no target activity, set the default one from the package. 1895 */ fillInDefaultActivity(List<ShortcutInfo> shortcuts)1896 private void fillInDefaultActivity(List<ShortcutInfo> shortcuts) { 1897 ComponentName defaultActivity = null; 1898 for (int i = shortcuts.size() - 1; i >= 0; i--) { 1899 final ShortcutInfo si = shortcuts.get(i); 1900 if (si.getActivity() == null) { 1901 if (defaultActivity == null) { 1902 defaultActivity = injectGetDefaultMainActivity( 1903 si.getPackage(), si.getUserId()); 1904 Preconditions.checkState(defaultActivity != null, 1905 "Launcher activity not found for package " + si.getPackage()); 1906 } 1907 si.setActivity(defaultActivity); 1908 } 1909 } 1910 } 1911 assignImplicitRanks(List<ShortcutInfo> shortcuts)1912 private void assignImplicitRanks(List<ShortcutInfo> shortcuts) { 1913 for (int i = shortcuts.size() - 1; i >= 0; i--) { 1914 shortcuts.get(i).setImplicitRank(i); 1915 } 1916 } 1917 setReturnedByServer(List<ShortcutInfo> shortcuts)1918 private List<ShortcutInfo> setReturnedByServer(List<ShortcutInfo> shortcuts) { 1919 for (int i = shortcuts.size() - 1; i >= 0; i--) { 1920 shortcuts.get(i).setReturnedByServer(); 1921 } 1922 return shortcuts; 1923 } 1924 1925 // === APIs === 1926 1927 @Override setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, @UserIdInt int userId)1928 public AndroidFuture setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, 1929 @UserIdInt int userId) { 1930 final AndroidFuture<Boolean> ret = new AndroidFuture<>(); 1931 if (!verifyCaller(packageName, userId, ret)) { 1932 return ret; 1933 } 1934 final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( 1935 injectBinderCallingPid(), injectBinderCallingUid()); 1936 injectPostToHandlerIfAppSearch(() -> { 1937 try { 1938 final List<ShortcutInfo> newShortcuts = 1939 (List<ShortcutInfo>) shortcutInfoList.getList(); 1940 verifyShortcutInfoPackages(packageName, newShortcuts); 1941 final int size = newShortcuts.size(); 1942 1943 List<ShortcutInfo> changedShortcuts = null; 1944 List<ShortcutInfo> removedShortcuts = null; 1945 1946 synchronized (mLock) { 1947 throwIfUserLockedL(userId); 1948 1949 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 1950 userId); 1951 1952 ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); 1953 ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); 1954 1955 fillInDefaultActivity(newShortcuts); 1956 1957 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET); 1958 1959 // Throttling. 1960 if (!ps.tryApiCall(unlimited)) { 1961 ret.complete(false); 1962 return; 1963 } 1964 1965 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 1966 ps.clearAllImplicitRanks(); 1967 assignImplicitRanks(newShortcuts); 1968 1969 for (int i = 0; i < size; i++) { 1970 fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false); 1971 } 1972 1973 ArrayList<ShortcutInfo> cachedOrPinned = new ArrayList<>(); 1974 ps.findAll(cachedOrPinned, 1975 AppSearchShortcutInfo.QUERY_IS_VISIBLE_CACHED_OR_PINNED, 1976 (ShortcutInfo si) -> si.isVisibleToPublisher() 1977 && si.isDynamic() && (si.isCached() || si.isPinned()), 1978 ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); 1979 1980 // First, remove all un-pinned and non-cached; dynamic shortcuts 1981 removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); 1982 1983 // Then, add/update all. We need to make sure to take over "pinned" flag. 1984 for (int i = 0; i < size; i++) { 1985 final ShortcutInfo newShortcut = newShortcuts.get(i); 1986 ps.addOrReplaceDynamicShortcut(newShortcut); 1987 } 1988 1989 // Lastly, adjust the ranks. 1990 ps.adjustRanks(); 1991 1992 changedShortcuts = prepareChangedShortcuts( 1993 cachedOrPinned, newShortcuts, removedShortcuts, ps); 1994 } 1995 1996 1997 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 1998 1999 verifyStates(); 2000 2001 ret.complete(true); 2002 } catch (Exception e) { 2003 ret.completeExceptionally(e); 2004 } 2005 }); 2006 return ret; 2007 } 2008 2009 @Override updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, @UserIdInt int userId)2010 public AndroidFuture updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, 2011 @UserIdInt int userId) { 2012 final AndroidFuture<Boolean> ret = new AndroidFuture<>(); 2013 if (!verifyCaller(packageName, userId, ret)) { 2014 return ret; 2015 } 2016 final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( 2017 injectBinderCallingPid(), injectBinderCallingUid()); 2018 injectPostToHandlerIfAppSearch(() -> { 2019 try { 2020 final List<ShortcutInfo> newShortcuts = 2021 (List<ShortcutInfo>) shortcutInfoList.getList(); 2022 verifyShortcutInfoPackages(packageName, newShortcuts); 2023 final int size = newShortcuts.size(); 2024 2025 final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1); 2026 2027 synchronized (mLock) { 2028 throwIfUserLockedL(userId); 2029 2030 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2031 userId); 2032 2033 ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); 2034 ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); 2035 2036 // For update, don't fill in the default activity. Having null activity means 2037 // "don't update the activity" here. 2038 2039 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE); 2040 2041 // Throttling. 2042 if (!ps.tryApiCall(unlimited)) { 2043 ret.complete(false); 2044 return; 2045 } 2046 2047 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 2048 ps.clearAllImplicitRanks(); 2049 assignImplicitRanks(newShortcuts); 2050 2051 for (int i = 0; i < size; i++) { 2052 final ShortcutInfo source = newShortcuts.get(i); 2053 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true); 2054 2055 ps.mutateShortcut(source.getId(), null, target -> { 2056 // Invisible shortcuts can't be updated. 2057 if (target == null || !target.isVisibleToPublisher()) { 2058 return; 2059 } 2060 2061 if (target.isEnabled() != source.isEnabled()) { 2062 Slog.w(TAG, "ShortcutInfo.enabled cannot be changed with" 2063 + " updateShortcuts()"); 2064 } 2065 2066 if (target.isLongLived() != source.isLongLived()) { 2067 Slog.w(TAG, 2068 "ShortcutInfo.longLived cannot be changed with" 2069 + " updateShortcuts()"); 2070 } 2071 2072 // When updating the rank, we need to insert between existing ranks, 2073 // so set this setRankChanged, and also copy the implicit rank fo 2074 // adjustRanks(). 2075 if (source.hasRank()) { 2076 target.setRankChanged(); 2077 target.setImplicitRank(source.getImplicitRank()); 2078 } 2079 2080 final boolean replacingIcon = (source.getIcon() != null); 2081 if (replacingIcon) { 2082 removeIconLocked(target); 2083 } 2084 2085 // Note copyNonNullFieldsFrom() does the "updatable with?" check too. 2086 target.copyNonNullFieldsFrom(source); 2087 target.setTimestamp(injectCurrentTimeMillis()); 2088 2089 if (replacingIcon) { 2090 saveIconAndFixUpShortcutLocked(target); 2091 } 2092 2093 // When we're updating any resource related fields, re-extract the res 2094 // names and the values. 2095 if (replacingIcon || source.hasStringResources()) { 2096 fixUpShortcutResourceNamesAndValues(target); 2097 } 2098 2099 changedShortcuts.add(target); 2100 }); 2101 } 2102 2103 // Lastly, adjust the ranks. 2104 ps.adjustRanks(); 2105 } 2106 packageShortcutsChanged(packageName, userId, 2107 changedShortcuts.isEmpty() ? null : changedShortcuts, null); 2108 2109 verifyStates(); 2110 2111 ret.complete(true); 2112 } catch (Exception e) { 2113 ret.completeExceptionally(e); 2114 } 2115 }); 2116 return ret; 2117 } 2118 2119 @Override addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, @UserIdInt int userId)2120 public AndroidFuture addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, 2121 @UserIdInt int userId) { 2122 final AndroidFuture<Boolean> ret = new AndroidFuture<>(); 2123 if (!verifyCaller(packageName, userId, ret)) { 2124 return ret; 2125 } 2126 final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( 2127 injectBinderCallingPid(), injectBinderCallingUid()); 2128 injectPostToHandlerIfAppSearch(() -> { 2129 try { 2130 final List<ShortcutInfo> newShortcuts = 2131 (List<ShortcutInfo>) shortcutInfoList.getList(); 2132 verifyShortcutInfoPackages(packageName, newShortcuts); 2133 final int size = newShortcuts.size(); 2134 2135 List<ShortcutInfo> changedShortcuts = null; 2136 2137 synchronized (mLock) { 2138 throwIfUserLockedL(userId); 2139 2140 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2141 userId); 2142 2143 ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); 2144 ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); 2145 2146 fillInDefaultActivity(newShortcuts); 2147 2148 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD); 2149 2150 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 2151 ps.clearAllImplicitRanks(); 2152 assignImplicitRanks(newShortcuts); 2153 2154 // Throttling. 2155 if (!ps.tryApiCall(unlimited)) { 2156 ret.complete(false); 2157 return; 2158 } 2159 for (int i = 0; i < size; i++) { 2160 final ShortcutInfo newShortcut = newShortcuts.get(i); 2161 2162 // Validate the shortcut. 2163 fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false); 2164 2165 // When ranks are changing, we need to insert between ranks, so set the 2166 // "rank changed" flag. 2167 newShortcut.setRankChanged(); 2168 2169 // Add it. 2170 ps.addOrReplaceDynamicShortcut(newShortcut); 2171 2172 if (changedShortcuts == null) { 2173 changedShortcuts = new ArrayList<>(1); 2174 } 2175 changedShortcuts.add(newShortcut); 2176 } 2177 2178 // Lastly, adjust the ranks. 2179 ps.adjustRanks(); 2180 } 2181 packageShortcutsChanged(packageName, userId, changedShortcuts, null); 2182 2183 verifyStates(); 2184 2185 ret.complete(true); 2186 } catch (Exception e) { 2187 ret.completeExceptionally(e); 2188 } 2189 }); 2190 return ret; 2191 } 2192 2193 @Override pushDynamicShortcut(String packageName, ShortcutInfo shortcut, @UserIdInt int userId)2194 public AndroidFuture pushDynamicShortcut(String packageName, ShortcutInfo shortcut, 2195 @UserIdInt int userId) { 2196 final AndroidFuture<Void> ret = new AndroidFuture<>(); 2197 if (!verifyCaller(packageName, userId, ret)) { 2198 return ret; 2199 } 2200 injectPostToHandlerIfAppSearch(() -> { 2201 try { 2202 verifyShortcutInfoPackage(packageName, shortcut); 2203 2204 List<ShortcutInfo> changedShortcuts = new ArrayList<>(); 2205 List<ShortcutInfo> removedShortcuts = null; 2206 2207 synchronized (mLock) { 2208 throwIfUserLockedL(userId); 2209 2210 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2211 userId); 2212 2213 ps.ensureNotImmutable(shortcut.getId(), /*ignoreInvisible=*/ true); 2214 fillInDefaultActivity(Arrays.asList(shortcut)); 2215 2216 if (!shortcut.hasRank()) { 2217 shortcut.setRank(0); 2218 } 2219 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 2220 ps.clearAllImplicitRanks(); 2221 shortcut.setImplicitRank(0); 2222 2223 // Validate the shortcut. 2224 fixUpIncomingShortcutInfo(shortcut, /* forUpdate= */ false); 2225 2226 // When ranks are changing, we need to insert between ranks, so set the 2227 // "rank changed" flag. 2228 shortcut.setRankChanged(); 2229 2230 // Push it. 2231 boolean deleted = ps.pushDynamicShortcut(shortcut, changedShortcuts); 2232 2233 if (deleted) { 2234 if (changedShortcuts.isEmpty()) { 2235 ret.complete(null); 2236 return; // Failed to push. 2237 } 2238 removedShortcuts = Collections.singletonList(changedShortcuts.get(0)); 2239 changedShortcuts.clear(); 2240 } 2241 changedShortcuts.add(shortcut); 2242 2243 // Lastly, adjust the ranks. 2244 ps.adjustRanks(); 2245 } 2246 2247 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 2248 2249 reportShortcutUsedInternal(packageName, shortcut.getId(), userId); 2250 2251 verifyStates(); 2252 2253 ret.complete(null); 2254 } catch (Exception e) { 2255 ret.completeExceptionally(e); 2256 } 2257 }); 2258 return ret; 2259 } 2260 2261 @Override requestPinShortcut(String packageName, ShortcutInfo shortcut, IntentSender resultIntent, int userId)2262 public AndroidFuture requestPinShortcut(String packageName, ShortcutInfo shortcut, 2263 IntentSender resultIntent, int userId) { 2264 final AndroidFuture<Boolean> ret = new AndroidFuture<>(); 2265 final int callingPid = injectBinderCallingPid(); 2266 final int callingUid = injectBinderCallingUid(); 2267 injectPostToHandlerIfAppSearch(() -> { 2268 try { 2269 ret.complete( 2270 requestPinItem(packageName, userId, shortcut, null, null, resultIntent, 2271 callingPid, callingUid)); 2272 } catch (Exception e) { 2273 ret.completeExceptionally(e); 2274 } 2275 }); 2276 return ret; 2277 } 2278 2279 @Override createShortcutResultIntent( String packageName, ShortcutInfo shortcut, int userId)2280 public AndroidFuture createShortcutResultIntent( 2281 String packageName, ShortcutInfo shortcut, int userId) throws RemoteException { 2282 final AndroidFuture<Intent> ret = new AndroidFuture<>(); 2283 if (!verifyCaller(packageName, userId, ret)) { 2284 return ret; 2285 } 2286 injectPostToHandlerIfAppSearch(() -> { 2287 try { 2288 Objects.requireNonNull(shortcut); 2289 Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); 2290 verifyShortcutInfoPackage(packageName, shortcut); 2291 final Intent intent; 2292 synchronized (mLock) { 2293 throwIfUserLockedL(userId); 2294 2295 // Send request to the launcher, if supported. 2296 intent = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, 2297 userId); 2298 } 2299 2300 verifyStates(); 2301 ret.complete(intent); 2302 } catch (Exception e) { 2303 ret.completeExceptionally(e); 2304 } 2305 }); 2306 return ret; 2307 } 2308 2309 /** 2310 * Handles {@link #requestPinShortcut} and {@link ShortcutServiceInternal#requestPinAppWidget}. 2311 * After validating the caller, it passes the request to {@link #mShortcutRequestPinProcessor}. 2312 * Either {@param shortcut} or {@param appWidget} should be non-null. 2313 */ requestPinItem(String callingPackage, int userId, ShortcutInfo shortcut, AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent)2314 private boolean requestPinItem(String callingPackage, int userId, ShortcutInfo shortcut, 2315 AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent) { 2316 return requestPinItem(callingPackage, userId, shortcut, appWidget, extras, resultIntent, 2317 injectBinderCallingPid(), injectBinderCallingUid()); 2318 } 2319 requestPinItem(String callingPackage, int userId, ShortcutInfo shortcut, AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent, int callingPid, int callingUid)2320 private boolean requestPinItem(String callingPackage, int userId, ShortcutInfo shortcut, 2321 AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent, 2322 int callingPid, int callingUid) { 2323 verifyCaller(callingPackage, userId); 2324 if (shortcut == null || !injectHasAccessShortcutsPermission( 2325 callingPid, callingUid)) { 2326 // Verify if caller is the shortcut owner, only if caller doesn't have ACCESS_SHORTCUTS. 2327 verifyShortcutInfoPackage(callingPackage, shortcut); 2328 } 2329 2330 final boolean ret; 2331 synchronized (mLock) { 2332 throwIfUserLockedL(userId); 2333 2334 Preconditions.checkState(isUidForegroundLocked(callingUid), 2335 "Calling application must have a foreground activity or a foreground service"); 2336 2337 // If it's a pin shortcut request, and there's already a shortcut with the same ID 2338 // that's not visible to the caller (i.e. restore-blocked; meaning it's pinned by 2339 // someone already), then we just replace the existing one with this new one, 2340 // and then proceed the rest of the process. 2341 if (shortcut != null) { 2342 final String shortcutPackage = shortcut.getPackage(); 2343 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked( 2344 shortcutPackage, userId); 2345 final String id = shortcut.getId(); 2346 if (ps.isShortcutExistsAndInvisibleToPublisher(id)) { 2347 2348 ps.updateInvisibleShortcutForPinRequestWith(shortcut); 2349 2350 packageShortcutsChanged(shortcutPackage, userId, 2351 Collections.singletonList(shortcut), null); 2352 } 2353 } 2354 2355 // Send request to the launcher, if supported. 2356 ret = mShortcutRequestPinProcessor.requestPinItemLocked(shortcut, appWidget, extras, 2357 userId, resultIntent); 2358 } 2359 2360 verifyStates(); 2361 2362 return ret; 2363 } 2364 2365 @Override disableShortcuts(String packageName, List shortcutIds, CharSequence disabledMessage, int disabledMessageResId, @UserIdInt int userId)2366 public AndroidFuture disableShortcuts(String packageName, List shortcutIds, 2367 CharSequence disabledMessage, int disabledMessageResId, @UserIdInt int userId) { 2368 final AndroidFuture<Void> ret = new AndroidFuture<>(); 2369 if (!verifyCaller(packageName, userId, ret)) { 2370 return ret; 2371 } 2372 injectPostToHandlerIfAppSearch(() -> { 2373 try { 2374 Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); 2375 List<ShortcutInfo> changedShortcuts = null; 2376 List<ShortcutInfo> removedShortcuts = null; 2377 2378 synchronized (mLock) { 2379 throwIfUserLockedL(userId); 2380 2381 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2382 userId); 2383 2384 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, 2385 /*ignoreInvisible=*/ true); 2386 2387 final String disabledMessageString = 2388 (disabledMessage == null) ? null : disabledMessage.toString(); 2389 2390 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 2391 final String id = Preconditions.checkStringNotEmpty( 2392 (String) shortcutIds.get(i)); 2393 if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { 2394 continue; 2395 } 2396 2397 final ShortcutInfo deleted = ps.disableWithId(id, 2398 disabledMessageString, disabledMessageResId, 2399 /* overrideImmutable=*/ false, /*ignoreInvisible=*/ true, 2400 ShortcutInfo.DISABLED_REASON_BY_APP); 2401 2402 if (deleted == null) { 2403 if (changedShortcuts == null) { 2404 changedShortcuts = new ArrayList<>(1); 2405 } 2406 changedShortcuts.add(ps.findShortcutById(id)); 2407 } else { 2408 if (removedShortcuts == null) { 2409 removedShortcuts = new ArrayList<>(1); 2410 } 2411 removedShortcuts.add(deleted); 2412 } 2413 } 2414 2415 // We may have removed dynamic shortcuts which may have left a gap, 2416 // so adjust the ranks. 2417 ps.adjustRanks(); 2418 } 2419 2420 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 2421 2422 verifyStates(); 2423 2424 ret.complete(null); 2425 } catch (Exception e) { 2426 ret.completeExceptionally(e); 2427 } 2428 }); 2429 return ret; 2430 } 2431 2432 @Override enableShortcuts( String packageName, List shortcutIds, @UserIdInt int userId)2433 public AndroidFuture enableShortcuts( 2434 String packageName, List shortcutIds, @UserIdInt int userId) { 2435 final AndroidFuture<Void> ret = new AndroidFuture<>(); 2436 if (!verifyCaller(packageName, userId, ret)) { 2437 return ret; 2438 } 2439 injectPostToHandlerIfAppSearch(() -> { 2440 try { 2441 Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); 2442 List<ShortcutInfo> changedShortcuts = null; 2443 2444 synchronized (mLock) { 2445 throwIfUserLockedL(userId); 2446 2447 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2448 userId); 2449 2450 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, 2451 /*ignoreInvisible=*/ true); 2452 2453 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 2454 final String id = Preconditions.checkStringNotEmpty( 2455 (String) shortcutIds.get(i)); 2456 if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { 2457 continue; 2458 } 2459 ps.enableWithId(id); 2460 2461 if (changedShortcuts == null) { 2462 changedShortcuts = new ArrayList<>(1); 2463 } 2464 changedShortcuts.add(ps.findShortcutById(id)); 2465 } 2466 } 2467 2468 packageShortcutsChanged(packageName, userId, changedShortcuts, null); 2469 2470 verifyStates(); 2471 2472 ret.complete(null); 2473 } catch (Exception e) { 2474 ret.completeExceptionally(e); 2475 } 2476 }); 2477 return ret; 2478 } 2479 2480 @Override removeDynamicShortcuts(String packageName, List shortcutIds, @UserIdInt int userId)2481 public AndroidFuture removeDynamicShortcuts(String packageName, List shortcutIds, 2482 @UserIdInt int userId) { 2483 final AndroidFuture<Void> ret = new AndroidFuture<>(); 2484 if (!verifyCaller(packageName, userId, ret)) { 2485 return ret; 2486 } 2487 injectPostToHandlerIfAppSearch(() -> { 2488 try { 2489 Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); 2490 List<ShortcutInfo> changedShortcuts = null; 2491 List<ShortcutInfo> removedShortcuts = null; 2492 2493 synchronized (mLock) { 2494 throwIfUserLockedL(userId); 2495 2496 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2497 userId); 2498 2499 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, 2500 /*ignoreInvisible=*/ true); 2501 2502 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 2503 final String id = Preconditions.checkStringNotEmpty( 2504 (String) shortcutIds.get(i)); 2505 if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { 2506 continue; 2507 } 2508 2509 ShortcutInfo removed = ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ 2510 true); 2511 if (removed == null) { 2512 if (changedShortcuts == null) { 2513 changedShortcuts = new ArrayList<>(1); 2514 } 2515 changedShortcuts.add(ps.findShortcutById(id)); 2516 } else { 2517 if (removedShortcuts == null) { 2518 removedShortcuts = new ArrayList<>(1); 2519 } 2520 removedShortcuts.add(removed); 2521 } 2522 } 2523 2524 // We may have removed dynamic shortcuts which may have left a gap, 2525 // so adjust the ranks. 2526 ps.adjustRanks(); 2527 } 2528 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 2529 2530 verifyStates(); 2531 2532 ret.complete(null); 2533 } catch (Exception e) { 2534 ret.completeExceptionally(e); 2535 } 2536 }); 2537 return ret; 2538 } 2539 2540 @Override removeAllDynamicShortcuts(String packageName, @UserIdInt int userId)2541 public AndroidFuture removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) { 2542 final AndroidFuture<Void> ret = new AndroidFuture<>(); 2543 if (!verifyCaller(packageName, userId, ret)) { 2544 return ret; 2545 } 2546 injectPostToHandlerIfAppSearch(() -> { 2547 try { 2548 List<ShortcutInfo> changedShortcuts = new ArrayList<>(); 2549 List<ShortcutInfo> removedShortcuts = null; 2550 2551 synchronized (mLock) { 2552 throwIfUserLockedL(userId); 2553 2554 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2555 userId); 2556 2557 // Dynamic shortcuts that are either cached or pinned will not get deleted. 2558 ps.findAll(changedShortcuts, 2559 AppSearchShortcutInfo.QUERY_IS_VISIBLE_CACHED_OR_PINNED, 2560 (ShortcutInfo si) -> si.isVisibleToPublisher() 2561 && si.isDynamic() && (si.isCached() || si.isPinned()), 2562 ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); 2563 2564 removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); 2565 changedShortcuts = prepareChangedShortcuts( 2566 changedShortcuts, null, removedShortcuts, ps); 2567 } 2568 2569 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 2570 2571 verifyStates(); 2572 2573 ret.complete(null); 2574 } catch (Exception e) { 2575 ret.completeExceptionally(e); 2576 } 2577 }); 2578 return ret; 2579 } 2580 2581 @Override removeLongLivedShortcuts(String packageName, List shortcutIds, @UserIdInt int userId)2582 public AndroidFuture removeLongLivedShortcuts(String packageName, List shortcutIds, 2583 @UserIdInt int userId) { 2584 final AndroidFuture<Void> ret = new AndroidFuture<>(); 2585 if (!verifyCaller(packageName, userId, ret)) { 2586 return ret; 2587 } 2588 injectPostToHandlerIfAppSearch(() -> { 2589 try { 2590 Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); 2591 List<ShortcutInfo> changedShortcuts = null; 2592 List<ShortcutInfo> removedShortcuts = null; 2593 2594 synchronized (mLock) { 2595 throwIfUserLockedL(userId); 2596 2597 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2598 userId); 2599 2600 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, 2601 /*ignoreInvisible=*/ true); 2602 2603 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 2604 final String id = Preconditions.checkStringNotEmpty( 2605 (String) shortcutIds.get(i)); 2606 if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { 2607 continue; 2608 } 2609 2610 ShortcutInfo removed = ps.deleteLongLivedWithId(id, /*ignoreInvisible=*/ 2611 true); 2612 if (removed != null) { 2613 if (removedShortcuts == null) { 2614 removedShortcuts = new ArrayList<>(1); 2615 } 2616 removedShortcuts.add(removed); 2617 } else { 2618 if (changedShortcuts == null) { 2619 changedShortcuts = new ArrayList<>(1); 2620 } 2621 changedShortcuts.add(ps.findShortcutById(id)); 2622 } 2623 } 2624 2625 // We may have removed dynamic shortcuts which may have left a gap, 2626 // so adjust the ranks. 2627 ps.adjustRanks(); 2628 } 2629 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 2630 2631 verifyStates(); 2632 2633 ret.complete(null); 2634 } catch (Exception e) { 2635 ret.completeExceptionally(e); 2636 } 2637 }); 2638 return ret; 2639 } 2640 2641 @Override getShortcuts(String packageName, @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId)2642 public AndroidFuture<ParceledListSlice> getShortcuts(String packageName, 2643 @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId) { 2644 final AndroidFuture<ParceledListSlice> ret = new AndroidFuture<>(); 2645 if (!verifyCaller(packageName, userId, ret)) { 2646 return ret; 2647 } 2648 injectPostToHandlerIfAppSearch(() -> { 2649 try { 2650 synchronized (mLock) { 2651 throwIfUserLockedL(userId); 2652 2653 final boolean matchDynamic = 2654 (matchFlags & ShortcutManager.FLAG_MATCH_DYNAMIC) != 0; 2655 final boolean matchPinned = 2656 (matchFlags & ShortcutManager.FLAG_MATCH_PINNED) != 0; 2657 final boolean matchManifest = 2658 (matchFlags & ShortcutManager.FLAG_MATCH_MANIFEST) != 0; 2659 final boolean matchCached = 2660 (matchFlags & ShortcutManager.FLAG_MATCH_CACHED) != 0; 2661 2662 final int shortcutFlags = (matchDynamic ? ShortcutInfo.FLAG_DYNAMIC : 0) 2663 | (matchPinned ? ShortcutInfo.FLAG_PINNED : 0) 2664 | (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0) 2665 | (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0); 2666 2667 final String query = AppSearchShortcutInfo.QUERY_IS_VISIBLE_TO_PUBLISHER + " " 2668 + createQuery(matchDynamic, matchPinned, matchManifest, matchCached); 2669 2670 ret.complete(getShortcutsWithQueryLocked( 2671 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, query, 2672 (ShortcutInfo si) -> 2673 si.isVisibleToPublisher() 2674 && (si.getFlags() & shortcutFlags) != 0)); 2675 } 2676 } catch (Exception e) { 2677 ret.completeExceptionally(e); 2678 } 2679 }); 2680 return ret; 2681 } 2682 2683 @Override getShareTargets( String packageName, IntentFilter filter, @UserIdInt int userId)2684 public AndroidFuture<ParceledListSlice> getShareTargets( 2685 String packageName, IntentFilter filter, @UserIdInt int userId) { 2686 final AndroidFuture<ParceledListSlice> ret = new AndroidFuture<>(); 2687 try { 2688 Preconditions.checkStringNotEmpty(packageName, "packageName"); 2689 Objects.requireNonNull(filter, "intentFilter"); 2690 2691 verifyCaller(packageName, userId); 2692 enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS, 2693 "getShareTargets"); 2694 } catch (Exception e) { 2695 ret.completeExceptionally(e); 2696 return ret; 2697 } 2698 injectPostToHandlerIfAppSearch(() -> { 2699 try { 2700 synchronized (mLock) { 2701 throwIfUserLockedL(userId); 2702 2703 final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = 2704 new ArrayList<>(); 2705 2706 final ShortcutUser user = getUserShortcutsLocked(userId); 2707 user.forAllPackages( 2708 p -> shortcutInfoList.addAll(p.getMatchingShareTargets(filter))); 2709 2710 ret.complete(new ParceledListSlice<>(shortcutInfoList)); 2711 } 2712 } catch (Exception e) { 2713 ret.completeExceptionally(e); 2714 } 2715 }); 2716 return ret; 2717 } 2718 2719 @Override hasShareTargets(String packageName, String packageToCheck, @UserIdInt int userId)2720 public boolean hasShareTargets(String packageName, String packageToCheck, 2721 @UserIdInt int userId) { 2722 verifyCaller(packageName, userId); 2723 enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS, 2724 "hasShareTargets"); 2725 2726 synchronized (mLock) { 2727 throwIfUserLockedL(userId); 2728 2729 return getPackageShortcutsLocked(packageToCheck, userId).hasShareTargets(); 2730 } 2731 } 2732 isSharingShortcut(int callingUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId, @NonNull IntentFilter filter)2733 public boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage, 2734 @NonNull String packageName, @NonNull String shortcutId, int userId, 2735 @NonNull IntentFilter filter) { 2736 verifyCaller(callingPackage, callingUserId); 2737 enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS, 2738 "isSharingShortcut"); 2739 2740 synchronized (mLock) { 2741 throwIfUserLockedL(userId); 2742 throwIfUserLockedL(callingUserId); 2743 2744 final List<ShortcutManager.ShareShortcutInfo> matchedTargets = 2745 getPackageShortcutsLocked(packageName, userId) 2746 .getMatchingShareTargets(filter); 2747 final int matchedSize = matchedTargets.size(); 2748 for (int i = 0; i < matchedSize; i++) { 2749 if (matchedTargets.get(i).getShortcutInfo().getId().equals(shortcutId)) { 2750 return true; 2751 } 2752 } 2753 } 2754 return false; 2755 } 2756 2757 @GuardedBy("mLock") getShortcutsWithQueryLocked(@onNull String packageName, @UserIdInt int userId, int cloneFlags, @NonNull final String query, @NonNull Predicate<ShortcutInfo> filter)2758 private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName, 2759 @UserIdInt int userId, int cloneFlags, @NonNull final String query, 2760 @NonNull Predicate<ShortcutInfo> filter) { 2761 2762 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 2763 2764 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2765 ps.findAll(ret, query, filter, cloneFlags); 2766 return new ParceledListSlice<>(setReturnedByServer(ret)); 2767 } 2768 2769 @Override getMaxShortcutCountPerActivity(String packageName, @UserIdInt int userId)2770 public int getMaxShortcutCountPerActivity(String packageName, @UserIdInt int userId) 2771 throws RemoteException { 2772 verifyCaller(packageName, userId); 2773 2774 return mMaxShortcuts; 2775 } 2776 2777 @Override getRemainingCallCount(String packageName, @UserIdInt int userId)2778 public int getRemainingCallCount(String packageName, @UserIdInt int userId) { 2779 verifyCaller(packageName, userId); 2780 2781 final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( 2782 injectBinderCallingPid(), injectBinderCallingUid()); 2783 2784 synchronized (mLock) { 2785 throwIfUserLockedL(userId); 2786 2787 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2788 return mMaxUpdatesPerInterval - ps.getApiCallCount(unlimited); 2789 } 2790 } 2791 2792 @Override getRateLimitResetTime(String packageName, @UserIdInt int userId)2793 public long getRateLimitResetTime(String packageName, @UserIdInt int userId) { 2794 verifyCaller(packageName, userId); 2795 2796 synchronized (mLock) { 2797 throwIfUserLockedL(userId); 2798 2799 return getNextResetTimeLocked(); 2800 } 2801 } 2802 2803 @Override getIconMaxDimensions(String packageName, int userId)2804 public int getIconMaxDimensions(String packageName, int userId) { 2805 verifyCaller(packageName, userId); 2806 2807 synchronized (mLock) { 2808 return mMaxIconDimension; 2809 } 2810 } 2811 2812 @Override reportShortcutUsed(String packageName, String shortcutId, int userId)2813 public AndroidFuture reportShortcutUsed(String packageName, String shortcutId, int userId) { 2814 final AndroidFuture<Boolean> ret = new AndroidFuture<>(); 2815 if (!verifyCaller(packageName, userId, ret)) { 2816 return ret; 2817 } 2818 injectPostToHandlerIfAppSearch(() -> { 2819 try { 2820 Objects.requireNonNull(shortcutId); 2821 2822 if (DEBUG) { 2823 Slog.d(TAG, String.format( 2824 "reportShortcutUsed: Shortcut %s package %s used on user %d", 2825 shortcutId, packageName, userId)); 2826 } 2827 2828 synchronized (mLock) { 2829 throwIfUserLockedL(userId); 2830 2831 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2832 userId); 2833 2834 if (ps.findShortcutById(shortcutId) == null) { 2835 Log.w(TAG, String.format( 2836 "reportShortcutUsed: package %s doesn't have shortcut %s", 2837 packageName, shortcutId)); 2838 ret.complete(false); 2839 return; 2840 } 2841 } 2842 2843 reportShortcutUsedInternal(packageName, shortcutId, userId); 2844 ret.complete(true); 2845 } catch (Exception e) { 2846 ret.completeExceptionally(e); 2847 } 2848 }); 2849 return ret; 2850 } 2851 reportShortcutUsedInternal(String packageName, String shortcutId, int userId)2852 private void reportShortcutUsedInternal(String packageName, String shortcutId, int userId) { 2853 final long token = injectClearCallingIdentity(); 2854 try { 2855 mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId); 2856 } finally { 2857 injectRestoreCallingIdentity(token); 2858 } 2859 } 2860 2861 @Override isRequestPinItemSupported(int callingUserId, int requestType)2862 public boolean isRequestPinItemSupported(int callingUserId, int requestType) { 2863 verifyCallerUserId(callingUserId); 2864 2865 final long token = injectClearCallingIdentity(); 2866 try { 2867 return mShortcutRequestPinProcessor 2868 .isRequestPinItemSupported(callingUserId, requestType); 2869 } finally { 2870 injectRestoreCallingIdentity(token); 2871 } 2872 } 2873 2874 /** 2875 * Reset all throttling, for developer options and command line. Only system/shell can call 2876 * it. 2877 */ 2878 @Override resetThrottling()2879 public void resetThrottling() { 2880 enforceSystemOrShell(); 2881 2882 resetThrottlingInner(getCallingUserId()); 2883 } 2884 resetThrottlingInner(@serIdInt int userId)2885 void resetThrottlingInner(@UserIdInt int userId) { 2886 synchronized (mLock) { 2887 if (!isUserUnlockedL(userId)) { 2888 Log.w(TAG, "User " + userId + " is locked or not running"); 2889 return; 2890 } 2891 2892 getUserShortcutsLocked(userId).resetThrottling(); 2893 } 2894 scheduleSaveUser(userId); 2895 Slog.i(TAG, "ShortcutManager: throttling counter reset for user " + userId); 2896 } 2897 resetAllThrottlingInner()2898 void resetAllThrottlingInner() { 2899 synchronized (mLock) { 2900 mRawLastResetTime = injectCurrentTimeMillis(); 2901 } 2902 scheduleSaveBaseState(); 2903 Slog.i(TAG, "ShortcutManager: throttling counter reset for all users"); 2904 } 2905 2906 @Override onApplicationActive(String packageName, int userId)2907 public AndroidFuture onApplicationActive(String packageName, int userId) { 2908 final AndroidFuture<Void> ret = new AndroidFuture<>(); 2909 if (DEBUG) { 2910 Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId); 2911 } 2912 try { 2913 enforceResetThrottlingPermission(); 2914 } catch (Exception e) { 2915 ret.completeExceptionally(e); 2916 return ret; 2917 } 2918 injectPostToHandlerIfAppSearch(() -> { 2919 try { 2920 synchronized (mLock) { 2921 if (!isUserUnlockedL(userId)) { 2922 // This is called by system UI, so no need to throw. Just ignore. 2923 ret.complete(null); 2924 return; 2925 } 2926 2927 getPackageShortcutsLocked(packageName, userId) 2928 .resetRateLimitingForCommandLineNoSaving(); 2929 saveUserLocked(userId); 2930 } 2931 ret.complete(null); 2932 } catch (Exception e) { 2933 ret.completeExceptionally(e); 2934 } 2935 }); 2936 return ret; 2937 } 2938 2939 // We override this method in unit tests to do a simpler check. hasShortcutHostPermission(@onNull String callingPackage, int userId, int callingPid, int callingUid)2940 boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId, 2941 int callingPid, int callingUid) { 2942 if (canSeeAnyPinnedShortcut(callingPackage, userId, callingPid, callingUid)) { 2943 return true; 2944 } 2945 final long start = getStatStartTime(); 2946 try { 2947 return hasShortcutHostPermissionInner(callingPackage, userId); 2948 } finally { 2949 logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start); 2950 } 2951 } 2952 canSeeAnyPinnedShortcut(@onNull String callingPackage, int userId, int callingPid, int callingUid)2953 boolean canSeeAnyPinnedShortcut(@NonNull String callingPackage, int userId, 2954 int callingPid, int callingUid) { 2955 if (injectHasAccessShortcutsPermission(callingPid, callingUid)) { 2956 return true; 2957 } 2958 synchronized (mNonPersistentUsersLock) { 2959 return getNonPersistentUserLocked(userId).hasHostPackage(callingPackage); 2960 } 2961 } 2962 2963 /** 2964 * Returns true if the caller has the "ACCESS_SHORTCUTS" permission. 2965 */ 2966 @VisibleForTesting injectHasAccessShortcutsPermission(int callingPid, int callingUid)2967 boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) { 2968 return mContext.checkPermission(android.Manifest.permission.ACCESS_SHORTCUTS, 2969 callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; 2970 } 2971 2972 /** 2973 * Returns true if the caller has the "UNLIMITED_SHORTCUTS_API_CALLS" permission. 2974 */ 2975 @VisibleForTesting injectHasUnlimitedShortcutsApiCallsPermission(int callingPid, int callingUid)2976 boolean injectHasUnlimitedShortcutsApiCallsPermission(int callingPid, int callingUid) { 2977 return mContext.checkPermission(permission.UNLIMITED_SHORTCUTS_API_CALLS, 2978 callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; 2979 } 2980 2981 // This method is extracted so we can directly call this method from unit tests, 2982 // even when hasShortcutPermission() is overridden. 2983 @VisibleForTesting hasShortcutHostPermissionInner(@onNull String packageName, int userId)2984 boolean hasShortcutHostPermissionInner(@NonNull String packageName, int userId) { 2985 synchronized (mLock) { 2986 throwIfUserLockedL(userId); 2987 2988 final String defaultLauncher = getDefaultLauncher(userId); 2989 2990 if (defaultLauncher != null) { 2991 if (DEBUG) { 2992 Slog.v(TAG, "Detected launcher: " + defaultLauncher + " user: " + userId); 2993 } 2994 return defaultLauncher.equals(packageName); 2995 } else { 2996 return false; 2997 } 2998 } 2999 } 3000 3001 @Nullable getDefaultLauncher(@serIdInt int userId)3002 String getDefaultLauncher(@UserIdInt int userId) { 3003 final long start = getStatStartTime(); 3004 final long token = injectClearCallingIdentity(); 3005 try { 3006 synchronized (mLock) { 3007 throwIfUserLockedL(userId); 3008 3009 final ShortcutUser user = getUserShortcutsLocked(userId); 3010 String cachedLauncher = user.getCachedLauncher(); 3011 if (cachedLauncher != null) { 3012 return cachedLauncher; 3013 } 3014 3015 // Default launcher from role manager. 3016 final long startGetHomeRoleHoldersAsUser = getStatStartTime(); 3017 final String defaultLauncher = injectGetHomeRoleHolderAsUser( 3018 getParentOrSelfUserId(userId)); 3019 logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeRoleHoldersAsUser); 3020 3021 if (defaultLauncher != null) { 3022 if (DEBUG) { 3023 Slog.v(TAG, "Default launcher from RoleManager: " + defaultLauncher 3024 + " user: " + userId); 3025 } 3026 user.setCachedLauncher(defaultLauncher); 3027 } else { 3028 Slog.e(TAG, "Default launcher not found." + " user: " + userId); 3029 } 3030 3031 return defaultLauncher; 3032 } 3033 } finally { 3034 injectRestoreCallingIdentity(token); 3035 logDurationStat(Stats.GET_DEFAULT_LAUNCHER, start); 3036 } 3037 } 3038 setShortcutHostPackage(@onNull String type, @Nullable String packageName, int userId)3039 public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName, 3040 int userId) { 3041 synchronized (mNonPersistentUsersLock) { 3042 getNonPersistentUserLocked(userId).setShortcutHostPackage(type, packageName); 3043 } 3044 } 3045 3046 // === House keeping === 3047 cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId, boolean appStillExists)3048 private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId, 3049 boolean appStillExists) { 3050 synchronized (mLock) { 3051 forEachLoadedUserLocked(user -> 3052 cleanUpPackageLocked(packageName, user.getUserId(), packageUserId, 3053 appStillExists)); 3054 } 3055 } 3056 createQuery(final boolean matchDynamic, final boolean matchPinned, final boolean matchManifest, final boolean matchCached)3057 private String createQuery(final boolean matchDynamic, final boolean matchPinned, 3058 final boolean matchManifest, final boolean matchCached) { 3059 3060 final List<String> queries = new ArrayList<>(1); 3061 if (matchDynamic) { 3062 queries.add(AppSearchShortcutInfo.QUERY_IS_DYNAMIC); 3063 } 3064 if (matchPinned) { 3065 queries.add(AppSearchShortcutInfo.QUERY_IS_PINNED); 3066 } 3067 if (matchManifest) { 3068 queries.add(AppSearchShortcutInfo.QUERY_IS_MANIFEST); 3069 } 3070 if (matchCached) { 3071 queries.add(AppSearchShortcutInfo.QUERY_IS_CACHED); 3072 } 3073 if (queries.isEmpty()) { 3074 return ""; 3075 } 3076 return "(" + String.join(" OR ", queries) + ")"; 3077 } 3078 3079 /** 3080 * Remove all the information associated with a package. This will really remove all the 3081 * information, including the restore information (i.e. it'll remove packages even if they're 3082 * shadow). 3083 * 3084 * This is called when an app is uninstalled, or an app gets "clear data"ed. 3085 */ 3086 @GuardedBy("mLock") 3087 @VisibleForTesting cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId, boolean appStillExists)3088 void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId, 3089 boolean appStillExists) { 3090 final boolean wasUserLoaded = isUserLoadedLocked(owningUserId); 3091 3092 final ShortcutUser user = getUserShortcutsLocked(owningUserId); 3093 boolean doNotify = false; 3094 3095 // First, remove the package from the package list (if the package is a publisher). 3096 if (packageUserId == owningUserId) { 3097 if (user.removePackage(packageName) != null) { 3098 doNotify = true; 3099 } 3100 } 3101 3102 // Also remove from the launcher list (if the package is a launcher). 3103 user.removeLauncher(packageUserId, packageName); 3104 3105 // Then remove pinned shortcuts from all launchers. 3106 user.forAllLaunchers(l -> l.cleanUpPackage(packageName, packageUserId)); 3107 3108 // Now there may be orphan shortcuts because we removed pinned shortcuts at the previous 3109 // step. Remove them too. 3110 user.forAllPackages(p -> p.refreshPinnedFlags()); 3111 3112 scheduleSaveUser(owningUserId); 3113 3114 if (doNotify) { 3115 notifyListeners(packageName, owningUserId); 3116 } 3117 3118 // If the app still exists (i.e. data cleared), we need to re-publish manifest shortcuts. 3119 if (appStillExists && (packageUserId == owningUserId)) { 3120 // This will do the notification and save when needed, so do it after the above 3121 // notifyListeners. 3122 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 3123 } 3124 3125 if (!wasUserLoaded) { 3126 // Note this will execute the scheduled save. 3127 unloadUserLocked(owningUserId); 3128 } 3129 } 3130 3131 /** 3132 * Entry point from {@link LauncherApps}. 3133 */ 3134 private class LocalService extends ShortcutServiceInternal { 3135 3136 @Override getShortcuts(int launcherUserId, @NonNull String callingPackage, long changedSince, @Nullable String packageName, @Nullable List<String> shortcutIds, @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName, int queryFlags, int userId, int callingPid, int callingUid)3137 public List<ShortcutInfo> getShortcuts(int launcherUserId, 3138 @NonNull String callingPackage, long changedSince, 3139 @Nullable String packageName, @Nullable List<String> shortcutIds, 3140 @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName, 3141 int queryFlags, int userId, int callingPid, int callingUid) { 3142 if (DEBUG_REBOOT) { 3143 Slog.d(TAG, "Getting shortcuts for launcher= " + callingPackage 3144 + "user=" + userId + " pkg=" + packageName); 3145 } 3146 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 3147 3148 int flags = ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER; 3149 if ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0) { 3150 flags = ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO; 3151 } else if ((queryFlags & ShortcutQuery.FLAG_GET_PERSONS_DATA) != 0) { 3152 flags &= ~ShortcutInfo.CLONE_REMOVE_PERSON; 3153 } 3154 final int cloneFlag = flags; 3155 3156 if (packageName == null) { 3157 shortcutIds = null; // LauncherAppsService already threw for it though. 3158 } 3159 3160 synchronized (mLock) { 3161 throwIfUserLockedL(userId); 3162 throwIfUserLockedL(launcherUserId); 3163 3164 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3165 .attemptToRestoreIfNeededAndSave(); 3166 3167 if (packageName != null) { 3168 getShortcutsInnerLocked(launcherUserId, 3169 callingPackage, packageName, shortcutIds, locusIds, changedSince, 3170 componentName, queryFlags, userId, ret, cloneFlag, 3171 callingPid, callingUid); 3172 } else { 3173 final List<String> shortcutIdsF = shortcutIds; 3174 final List<LocusId> locusIdsF = locusIds; 3175 getUserShortcutsLocked(userId).forAllPackages(p -> { 3176 getShortcutsInnerLocked(launcherUserId, 3177 callingPackage, p.getPackageName(), shortcutIdsF, locusIdsF, 3178 changedSince, componentName, queryFlags, userId, ret, cloneFlag, 3179 callingPid, callingUid); 3180 }); 3181 } 3182 } 3183 return setReturnedByServer(ret); 3184 } 3185 3186 @GuardedBy("ShortcutService.this.mLock") getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage, @Nullable String packageName, @Nullable List<String> shortcutIds, @Nullable List<LocusId> locusIds, long changedSince, @Nullable ComponentName componentName, int queryFlags, int userId, ArrayList<ShortcutInfo> ret, int cloneFlag, int callingPid, int callingUid)3187 private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage, 3188 @Nullable String packageName, @Nullable List<String> shortcutIds, 3189 @Nullable List<LocusId> locusIds, long changedSince, 3190 @Nullable ComponentName componentName, int queryFlags, 3191 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag, 3192 int callingPid, int callingUid) { 3193 final ArraySet<String> ids = shortcutIds == null ? null 3194 : new ArraySet<>(shortcutIds); 3195 3196 final ShortcutUser user = getUserShortcutsLocked(userId); 3197 final ShortcutPackage p = user.getPackageShortcutsIfExists(packageName); 3198 if (p == null) { 3199 return; // No need to instantiate ShortcutPackage. 3200 } 3201 3202 final boolean canAccessAllShortcuts = 3203 canSeeAnyPinnedShortcut(callingPackage, launcherUserId, callingPid, callingUid); 3204 3205 final boolean getPinnedByAnyLauncher = 3206 canAccessAllShortcuts && 3207 ((queryFlags & ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER) != 0); 3208 queryFlags |= (getPinnedByAnyLauncher ? ShortcutQuery.FLAG_MATCH_PINNED : 0); 3209 3210 final boolean matchPinnedOnly = 3211 ((queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0) 3212 && ((queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) == 0) 3213 && ((queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) == 0) 3214 && ((queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) == 0); 3215 3216 final Predicate<ShortcutInfo> filter = getFilterFromQuery(ids, locusIds, changedSince, 3217 componentName, queryFlags, getPinnedByAnyLauncher); 3218 if (matchPinnedOnly) { 3219 p.findAllPinned(ret, filter, cloneFlag, callingPackage, launcherUserId, 3220 getPinnedByAnyLauncher); 3221 } else if (ids != null && !ids.isEmpty()) { 3222 p.findAllByIds(ret, ids, filter, cloneFlag, callingPackage, launcherUserId, 3223 getPinnedByAnyLauncher); 3224 } else { 3225 final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0; 3226 final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0; 3227 final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0; 3228 final boolean matchCached = (queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) != 0; 3229 p.findAll(ret, createQuery(matchDynamic, matchPinned, matchManifest, matchCached), 3230 filter, cloneFlag, callingPackage, launcherUserId, getPinnedByAnyLauncher); 3231 } 3232 } 3233 getFilterFromQuery(@ullable ArraySet<String> ids, @Nullable List<LocusId> locusIds, long changedSince, @Nullable ComponentName componentName, int queryFlags, boolean getPinnedByAnyLauncher)3234 private Predicate<ShortcutInfo> getFilterFromQuery(@Nullable ArraySet<String> ids, 3235 @Nullable List<LocusId> locusIds, long changedSince, 3236 @Nullable ComponentName componentName, int queryFlags, 3237 boolean getPinnedByAnyLauncher) { 3238 final ArraySet<LocusId> locIds = locusIds == null ? null 3239 : new ArraySet<>(locusIds); 3240 3241 final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0; 3242 final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0; 3243 final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0; 3244 final boolean matchCached = (queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) != 0; 3245 return si -> { 3246 if (si.getLastChangedTimestamp() < changedSince) { 3247 return false; 3248 } 3249 if (ids != null && !ids.contains(si.getId())) { 3250 return false; 3251 } 3252 if (locIds != null && !locIds.contains(si.getLocusId())) { 3253 return false; 3254 } 3255 if (componentName != null) { 3256 if (si.getActivity() != null 3257 && !si.getActivity().equals(componentName)) { 3258 return false; 3259 } 3260 } 3261 if (matchDynamic && si.isDynamic()) { 3262 return true; 3263 } 3264 if ((matchPinned || getPinnedByAnyLauncher) && si.isPinned()) { 3265 return true; 3266 } 3267 if (matchManifest && si.isDeclaredInManifest()) { 3268 return true; 3269 } 3270 if (matchCached && si.isCached()) { 3271 return true; 3272 } 3273 return false; 3274 }; 3275 } 3276 3277 @Override isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)3278 public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, 3279 @NonNull String packageName, @NonNull String shortcutId, int userId) { 3280 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3281 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 3282 3283 synchronized (mLock) { 3284 throwIfUserLockedL(userId); 3285 throwIfUserLockedL(launcherUserId); 3286 3287 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3288 .attemptToRestoreIfNeededAndSave(); 3289 3290 final ShortcutInfo si = getShortcutInfoLocked( 3291 launcherUserId, callingPackage, packageName, shortcutId, userId, 3292 /*getPinnedByAnyLauncher=*/ false); 3293 return si != null && si.isPinned(); 3294 } 3295 } 3296 3297 @GuardedBy("ShortcutService.this.mLock") getShortcutInfoLocked( int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId, boolean getPinnedByAnyLauncher)3298 private ShortcutInfo getShortcutInfoLocked( 3299 int launcherUserId, @NonNull String callingPackage, 3300 @NonNull String packageName, @NonNull String shortcutId, int userId, 3301 boolean getPinnedByAnyLauncher) { 3302 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3303 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 3304 3305 throwIfUserLockedL(userId); 3306 throwIfUserLockedL(launcherUserId); 3307 3308 final ShortcutPackage p = getUserShortcutsLocked(userId) 3309 .getPackageShortcutsIfExists(packageName); 3310 if (p == null) { 3311 return null; 3312 } 3313 3314 final ArrayList<ShortcutInfo> list = new ArrayList<>(1); 3315 p.findAllByIds(list, Collections.singletonList(shortcutId), 3316 (ShortcutInfo si) -> shortcutId.equals(si.getId()), 3317 /* clone flags=*/ 0, callingPackage, launcherUserId, getPinnedByAnyLauncher); 3318 return list.size() == 0 ? null : list.get(0); 3319 } 3320 3321 @Override pinShortcuts(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull List<String> shortcutIds, int userId)3322 public void pinShortcuts(int launcherUserId, 3323 @NonNull String callingPackage, @NonNull String packageName, 3324 @NonNull List<String> shortcutIds, int userId) { 3325 // Calling permission must be checked by LauncherAppsImpl. 3326 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3327 Objects.requireNonNull(shortcutIds, "shortcutIds"); 3328 3329 List<ShortcutInfo> changedShortcuts = null; 3330 List<ShortcutInfo> removedShortcuts = null; 3331 3332 synchronized (mLock) { 3333 throwIfUserLockedL(userId); 3334 throwIfUserLockedL(launcherUserId); 3335 3336 final ShortcutLauncher launcher = 3337 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId); 3338 launcher.attemptToRestoreIfNeededAndSave(); 3339 3340 final ShortcutPackage sp = getUserShortcutsLocked(userId) 3341 .getPackageShortcutsIfExists(packageName); 3342 if (sp != null) { 3343 // List the shortcuts that are pinned only, these will get removed. 3344 removedShortcuts = new ArrayList<>(); 3345 sp.findAll(removedShortcuts, AppSearchShortcutInfo.QUERY_IS_VISIBLE_PINNED_ONLY, 3346 (ShortcutInfo si) -> si.isVisibleToPublisher() 3347 && si.isPinned() && !si.isCached() && !si.isDynamic() 3348 && !si.isDeclaredInManifest(), 3349 ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO, 3350 callingPackage, launcherUserId, false); 3351 } 3352 // Get list of shortcuts that will get unpinned. 3353 ArraySet<String> oldPinnedIds = launcher.getPinnedShortcutIds(packageName, userId); 3354 3355 launcher.pinShortcuts(userId, packageName, shortcutIds, /*forPinRequest=*/ false); 3356 3357 if (oldPinnedIds != null && removedShortcuts != null) { 3358 for (int i = 0; i < removedShortcuts.size(); i++) { 3359 oldPinnedIds.remove(removedShortcuts.get(i).getId()); 3360 } 3361 } 3362 changedShortcuts = prepareChangedShortcuts( 3363 oldPinnedIds, new ArraySet<>(shortcutIds), removedShortcuts, sp); 3364 } 3365 3366 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 3367 3368 verifyStates(); 3369 } 3370 3371 @Override cacheShortcuts(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull List<String> shortcutIds, int userId, int cacheFlags)3372 public void cacheShortcuts(int launcherUserId, 3373 @NonNull String callingPackage, @NonNull String packageName, 3374 @NonNull List<String> shortcutIds, int userId, int cacheFlags) { 3375 updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds, 3376 userId, cacheFlags, /* doCache= */ true); 3377 } 3378 3379 @Override uncacheShortcuts(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull List<String> shortcutIds, int userId, int cacheFlags)3380 public void uncacheShortcuts(int launcherUserId, 3381 @NonNull String callingPackage, @NonNull String packageName, 3382 @NonNull List<String> shortcutIds, int userId, int cacheFlags) { 3383 updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds, 3384 userId, cacheFlags, /* doCache= */ false); 3385 } 3386 3387 @Override getShareTargets( @onNull String callingPackage, @NonNull IntentFilter intentFilter, int userId)3388 public List<ShortcutManager.ShareShortcutInfo> getShareTargets( 3389 @NonNull String callingPackage, @NonNull IntentFilter intentFilter, int userId) { 3390 final AndroidFuture<ParceledListSlice> future = ShortcutService.this.getShareTargets( 3391 callingPackage, intentFilter, userId); 3392 try { 3393 return future.get().getList(); 3394 } catch (InterruptedException | ExecutionException e) { 3395 throw new RuntimeException(e); 3396 } 3397 } 3398 3399 @Override isSharingShortcut(int callingUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId, @NonNull IntentFilter filter)3400 public boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage, 3401 @NonNull String packageName, @NonNull String shortcutId, int userId, 3402 @NonNull IntentFilter filter) { 3403 Preconditions.checkStringNotEmpty(callingPackage, "callingPackage"); 3404 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3405 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 3406 3407 return ShortcutService.this.isSharingShortcut(callingUserId, callingPackage, 3408 packageName, shortcutId, userId, filter); 3409 } 3410 updateCachedShortcutsInternal(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull List<String> shortcutIds, int userId, int cacheFlags, boolean doCache)3411 private void updateCachedShortcutsInternal(int launcherUserId, 3412 @NonNull String callingPackage, @NonNull String packageName, 3413 @NonNull List<String> shortcutIds, int userId, int cacheFlags, boolean doCache) { 3414 // Calling permission must be checked by LauncherAppsImpl. 3415 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3416 Objects.requireNonNull(shortcutIds, "shortcutIds"); 3417 Preconditions.checkState( 3418 (cacheFlags & ShortcutInfo.FLAG_CACHED_ALL) != 0, "invalid cacheFlags"); 3419 3420 List<ShortcutInfo> changedShortcuts = null; 3421 List<ShortcutInfo> removedShortcuts = null; 3422 3423 synchronized (mLock) { 3424 throwIfUserLockedL(userId); 3425 throwIfUserLockedL(launcherUserId); 3426 3427 final int idSize = shortcutIds.size(); 3428 final ShortcutPackage sp = getUserShortcutsLocked(userId) 3429 .getPackageShortcutsIfExists(packageName); 3430 if (idSize == 0 || sp == null) { 3431 return; 3432 } 3433 3434 for (int i = 0; i < idSize; i++) { 3435 final String id = Preconditions.checkStringNotEmpty(shortcutIds.get(i)); 3436 final ShortcutInfo si = sp.findShortcutById(id); 3437 if (si == null || doCache == si.hasFlags(cacheFlags)) { 3438 continue; 3439 } 3440 3441 if (doCache) { 3442 if (si.isLongLived()) { 3443 sp.mutateShortcut(si.getId(), si, 3444 shortcut -> shortcut.addFlags(cacheFlags)); 3445 if (changedShortcuts == null) { 3446 changedShortcuts = new ArrayList<>(1); 3447 } 3448 changedShortcuts.add(si); 3449 } else { 3450 Log.w(TAG, "Only long lived shortcuts can get cached. Ignoring id " 3451 + si.getId()); 3452 } 3453 } else { 3454 ShortcutInfo removed = null; 3455 sp.mutateShortcut(si.getId(), si, shortcut -> 3456 shortcut.clearFlags(cacheFlags)); 3457 if (!si.isDynamic() && !si.isCached()) { 3458 removed = sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true); 3459 } 3460 if (removed != null) { 3461 if (removedShortcuts == null) { 3462 removedShortcuts = new ArrayList<>(1); 3463 } 3464 removedShortcuts.add(removed); 3465 } else { 3466 if (changedShortcuts == null) { 3467 changedShortcuts = new ArrayList<>(1); 3468 } 3469 changedShortcuts.add(si); 3470 } 3471 } 3472 } 3473 } 3474 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 3475 3476 verifyStates(); 3477 } 3478 3479 @Override createShortcutIntents(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId, int callingPid, int callingUid)3480 public Intent[] createShortcutIntents(int launcherUserId, 3481 @NonNull String callingPackage, 3482 @NonNull String packageName, @NonNull String shortcutId, int userId, 3483 int callingPid, int callingUid) { 3484 // Calling permission must be checked by LauncherAppsImpl. 3485 Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty"); 3486 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty"); 3487 3488 synchronized (mLock) { 3489 throwIfUserLockedL(userId); 3490 throwIfUserLockedL(launcherUserId); 3491 3492 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3493 .attemptToRestoreIfNeededAndSave(); 3494 3495 final boolean getPinnedByAnyLauncher = 3496 canSeeAnyPinnedShortcut(callingPackage, launcherUserId, 3497 callingPid, callingUid); 3498 3499 // Make sure the shortcut is actually visible to the launcher. 3500 final ShortcutInfo si = getShortcutInfoLocked( 3501 launcherUserId, callingPackage, packageName, shortcutId, userId, 3502 getPinnedByAnyLauncher); 3503 // "si == null" should suffice here, but check the flags too just to make sure. 3504 if (si == null || !si.isEnabled() || !(si.isAlive() || getPinnedByAnyLauncher)) { 3505 Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled"); 3506 return null; 3507 } 3508 return si.getIntents(); 3509 } 3510 } 3511 3512 @Override addListener(@onNull ShortcutChangeListener listener)3513 public void addListener(@NonNull ShortcutChangeListener listener) { 3514 synchronized (mLock) { 3515 mListeners.add(Objects.requireNonNull(listener)); 3516 } 3517 } 3518 3519 @Override addShortcutChangeCallback( @onNull LauncherApps.ShortcutChangeCallback callback)3520 public void addShortcutChangeCallback( 3521 @NonNull LauncherApps.ShortcutChangeCallback callback) { 3522 synchronized (mLock) { 3523 mShortcutChangeCallbacks.add(Objects.requireNonNull(callback)); 3524 } 3525 } 3526 3527 @Override getShortcutIconResId(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)3528 public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage, 3529 @NonNull String packageName, @NonNull String shortcutId, int userId) { 3530 Objects.requireNonNull(callingPackage, "callingPackage"); 3531 Objects.requireNonNull(packageName, "packageName"); 3532 Objects.requireNonNull(shortcutId, "shortcutId"); 3533 3534 synchronized (mLock) { 3535 throwIfUserLockedL(userId); 3536 throwIfUserLockedL(launcherUserId); 3537 3538 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3539 .attemptToRestoreIfNeededAndSave(); 3540 3541 final ShortcutPackage p = getUserShortcutsLocked(userId) 3542 .getPackageShortcutsIfExists(packageName); 3543 if (p == null) { 3544 return 0; 3545 } 3546 3547 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 3548 return (shortcutInfo != null && shortcutInfo.hasIconResource()) 3549 ? shortcutInfo.getIconResourceId() : 0; 3550 } 3551 } 3552 3553 @Override 3554 @Nullable getShortcutStartingThemeResName(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)3555 public String getShortcutStartingThemeResName(int launcherUserId, 3556 @NonNull String callingPackage, @NonNull String packageName, 3557 @NonNull String shortcutId, int userId) { 3558 Objects.requireNonNull(callingPackage, "callingPackage"); 3559 Objects.requireNonNull(packageName, "packageName"); 3560 Objects.requireNonNull(shortcutId, "shortcutId"); 3561 3562 synchronized (mLock) { 3563 throwIfUserLockedL(userId); 3564 throwIfUserLockedL(launcherUserId); 3565 3566 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3567 .attemptToRestoreIfNeededAndSave(); 3568 3569 final ShortcutPackage p = getUserShortcutsLocked(userId) 3570 .getPackageShortcutsIfExists(packageName); 3571 if (p == null) { 3572 return null; 3573 } 3574 3575 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 3576 return shortcutInfo != null ? shortcutInfo.getStartingThemeResName() : null; 3577 } 3578 } 3579 3580 @Override getShortcutIconFd(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)3581 public ParcelFileDescriptor getShortcutIconFd(int launcherUserId, 3582 @NonNull String callingPackage, @NonNull String packageName, 3583 @NonNull String shortcutId, int userId) { 3584 Objects.requireNonNull(callingPackage, "callingPackage"); 3585 Objects.requireNonNull(packageName, "packageName"); 3586 Objects.requireNonNull(shortcutId, "shortcutId"); 3587 3588 synchronized (mLock) { 3589 throwIfUserLockedL(userId); 3590 throwIfUserLockedL(launcherUserId); 3591 3592 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3593 .attemptToRestoreIfNeededAndSave(); 3594 3595 final ShortcutPackage p = getUserShortcutsLocked(userId) 3596 .getPackageShortcutsIfExists(packageName); 3597 if (p == null) { 3598 return null; 3599 } 3600 3601 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 3602 if (shortcutInfo == null || !shortcutInfo.hasIconFile()) { 3603 return null; 3604 } 3605 final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo); 3606 if (path == null) { 3607 Slog.w(TAG, "null bitmap detected in getShortcutIconFd()"); 3608 return null; 3609 } 3610 try { 3611 return ParcelFileDescriptor.open( 3612 new File(path), 3613 ParcelFileDescriptor.MODE_READ_ONLY); 3614 } catch (FileNotFoundException e) { 3615 Slog.e(TAG, "Icon file not found: " + path); 3616 return null; 3617 } 3618 } 3619 } 3620 3621 @Override getShortcutIconUri(int launcherUserId, @NonNull String launcherPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)3622 public String getShortcutIconUri(int launcherUserId, @NonNull String launcherPackage, 3623 @NonNull String packageName, @NonNull String shortcutId, int userId) { 3624 Objects.requireNonNull(launcherPackage, "launcherPackage"); 3625 Objects.requireNonNull(packageName, "packageName"); 3626 Objects.requireNonNull(shortcutId, "shortcutId"); 3627 3628 synchronized (mLock) { 3629 throwIfUserLockedL(userId); 3630 throwIfUserLockedL(launcherUserId); 3631 3632 getLauncherShortcutsLocked(launcherPackage, userId, launcherUserId) 3633 .attemptToRestoreIfNeededAndSave(); 3634 3635 final ShortcutPackage p = getUserShortcutsLocked(userId) 3636 .getPackageShortcutsIfExists(packageName); 3637 if (p == null) { 3638 return null; 3639 } 3640 3641 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 3642 if (shortcutInfo == null || !shortcutInfo.hasIconUri()) { 3643 return null; 3644 } 3645 String uri = shortcutInfo.getIconUri(); 3646 if (uri == null) { 3647 Slog.w(TAG, "null uri detected in getShortcutIconUri()"); 3648 return null; 3649 } 3650 3651 final long token = Binder.clearCallingIdentity(); 3652 try { 3653 int packageUid = mPackageManagerInternal.getPackageUid(packageName, 3654 PackageManager.MATCH_DIRECT_BOOT_AUTO, userId); 3655 // Grant read uri permission to the caller on behalf of the shortcut owner. All 3656 // granted permissions are revoked when the default launcher changes, or when 3657 // device is rebooted. 3658 mUriGrantsManager.grantUriPermissionFromOwner(mUriPermissionOwner, packageUid, 3659 launcherPackage, Uri.parse(uri), Intent.FLAG_GRANT_READ_URI_PERMISSION, 3660 userId, launcherUserId); 3661 } catch (Exception e) { 3662 Slog.e(TAG, "Failed to grant uri access to " + launcherPackage + " for " + uri, 3663 e); 3664 uri = null; 3665 } finally { 3666 Binder.restoreCallingIdentity(token); 3667 } 3668 return uri; 3669 } 3670 } 3671 3672 @Override hasShortcutHostPermission(int launcherUserId, @NonNull String callingPackage, int callingPid, int callingUid)3673 public boolean hasShortcutHostPermission(int launcherUserId, 3674 @NonNull String callingPackage, int callingPid, int callingUid) { 3675 return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId, 3676 callingPid, callingUid); 3677 } 3678 3679 @Override setShortcutHostPackage(@onNull String type, @Nullable String packageName, int userId)3680 public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName, 3681 int userId) { 3682 ShortcutService.this.setShortcutHostPackage(type, packageName, userId); 3683 } 3684 3685 @Override requestPinAppWidget(@onNull String callingPackage, @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras, @Nullable IntentSender resultIntent, int userId)3686 public boolean requestPinAppWidget(@NonNull String callingPackage, 3687 @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras, 3688 @Nullable IntentSender resultIntent, int userId) { 3689 Objects.requireNonNull(appWidget); 3690 return requestPinItem(callingPackage, userId, null, appWidget, extras, resultIntent); 3691 } 3692 3693 @Override isRequestPinItemSupported(int callingUserId, int requestType)3694 public boolean isRequestPinItemSupported(int callingUserId, int requestType) { 3695 return ShortcutService.this.isRequestPinItemSupported(callingUserId, requestType); 3696 } 3697 3698 @Override isForegroundDefaultLauncher(@onNull String callingPackage, int callingUid)3699 public boolean isForegroundDefaultLauncher(@NonNull String callingPackage, int callingUid) { 3700 Objects.requireNonNull(callingPackage); 3701 3702 final int userId = UserHandle.getUserId(callingUid); 3703 final String defaultLauncher = getDefaultLauncher(userId); 3704 if (defaultLauncher == null) { 3705 return false; 3706 } 3707 if (!callingPackage.equals(defaultLauncher)) { 3708 return false; 3709 } 3710 synchronized (mLock) { 3711 if (!isUidForegroundLocked(callingUid)) { 3712 return false; 3713 } 3714 } 3715 return true; 3716 } 3717 } 3718 3719 final BroadcastReceiver mReceiver = new BroadcastReceiver() { 3720 @Override 3721 public void onReceive(Context context, Intent intent) { 3722 if (!mBootCompleted.get()) { 3723 return; // Boot not completed, ignore the broadcast. 3724 } 3725 try { 3726 if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) { 3727 handleLocaleChanged(); 3728 } 3729 } catch (Exception e) { 3730 wtf("Exception in mReceiver.onReceive", e); 3731 } 3732 } 3733 }; 3734 3735 void handleLocaleChanged() { 3736 if (DEBUG) { 3737 Slog.d(TAG, "handleLocaleChanged"); 3738 } 3739 scheduleSaveBaseState(); 3740 3741 synchronized (mLock) { 3742 final long token = injectClearCallingIdentity(); 3743 try { 3744 forEachLoadedUserLocked(user -> user.detectLocaleChange()); 3745 } finally { 3746 injectRestoreCallingIdentity(token); 3747 } 3748 } 3749 } 3750 3751 /** 3752 * Package event callbacks. 3753 */ 3754 @VisibleForTesting 3755 final BroadcastReceiver mPackageMonitor = new BroadcastReceiver() { 3756 @Override 3757 public void onReceive(Context context, Intent intent) { 3758 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); 3759 if (userId == UserHandle.USER_NULL) { 3760 Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent); 3761 return; 3762 } 3763 3764 final String action = intent.getAction(); 3765 3766 // This is normally called on Handler, so clearCallingIdentity() isn't needed, 3767 // but we still check it in unit tests. 3768 final long token = injectClearCallingIdentity(); 3769 try { 3770 synchronized (mLock) { 3771 if (!isUserUnlockedL(userId)) { 3772 if (DEBUG) { 3773 Slog.d(TAG, "Ignoring package broadcast " + action 3774 + " for locked/stopped user " + userId); 3775 } 3776 return; 3777 } 3778 } 3779 3780 final Uri intentUri = intent.getData(); 3781 final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart() 3782 : null; 3783 if (packageName == null) { 3784 Slog.w(TAG, "Intent broadcast does not contain package name: " + intent); 3785 return; 3786 } 3787 3788 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 3789 3790 switch (action) { 3791 case Intent.ACTION_PACKAGE_ADDED: 3792 if (replacing) { 3793 handlePackageUpdateFinished(packageName, userId); 3794 } else { 3795 handlePackageAdded(packageName, userId); 3796 } 3797 break; 3798 case Intent.ACTION_PACKAGE_REMOVED: 3799 if (!replacing) { 3800 handlePackageRemoved(packageName, userId); 3801 } 3802 break; 3803 case Intent.ACTION_PACKAGE_CHANGED: 3804 handlePackageChanged(packageName, userId); 3805 3806 break; 3807 case Intent.ACTION_PACKAGE_DATA_CLEARED: 3808 handlePackageDataCleared(packageName, userId); 3809 break; 3810 } 3811 } catch (Exception e) { 3812 wtf("Exception in mPackageMonitor.onReceive", e); 3813 } finally { 3814 injectRestoreCallingIdentity(token); 3815 } 3816 } 3817 }; 3818 3819 private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() { 3820 @Override 3821 public void onReceive(Context context, Intent intent) { 3822 // Since it cleans up the shortcut directory and rewrite the ShortcutPackageItems 3823 // in odrder during saveToXml(), it could lead to shortcuts missing when shutdown. 3824 // We need it so that it can finish up saving before shutdown. 3825 synchronized (mLock) { 3826 if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) { 3827 mHandler.removeCallbacks(mSaveDirtyInfoRunner); 3828 saveDirtyInfo(); 3829 } 3830 mShutdown.set(true); 3831 } 3832 } 3833 }; 3834 3835 /** 3836 * Called when a user is unlocked. 3837 * - Check all known packages still exist, and otherwise perform cleanup. 3838 * - If a package still exists, check the version code. If it's been updated, may need to 3839 * update timestamps of its shortcuts. 3840 */ 3841 @VisibleForTesting 3842 void checkPackageChanges(@UserIdInt int ownerUserId) { 3843 if (DEBUG || DEBUG_REBOOT) { 3844 Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId); 3845 } 3846 if (injectIsSafeModeEnabled()) { 3847 Slog.i(TAG, "Safe mode, skipping checkPackageChanges()"); 3848 return; 3849 } 3850 3851 final long start = getStatStartTime(); 3852 try { 3853 final ArrayList<PackageWithUser> gonePackages = new ArrayList<>(); 3854 3855 synchronized (mLock) { 3856 final ShortcutUser user = getUserShortcutsLocked(ownerUserId); 3857 3858 // Find packages that have been uninstalled. 3859 user.forAllPackageItems(spi -> { 3860 if (spi.getPackageInfo().isShadow()) { 3861 return; // Don't delete shadow information. 3862 } 3863 if (!isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) { 3864 if (DEBUG) { 3865 Slog.d(TAG, "Uninstalled: " + spi.getPackageName() 3866 + " user " + spi.getPackageUserId()); 3867 } 3868 gonePackages.add(PackageWithUser.of(spi)); 3869 } 3870 }); 3871 if (gonePackages.size() > 0) { 3872 for (int i = gonePackages.size() - 1; i >= 0; i--) { 3873 final PackageWithUser pu = gonePackages.get(i); 3874 cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId, 3875 /* appStillExists = */ false); 3876 } 3877 } 3878 3879 rescanUpdatedPackagesLocked(ownerUserId, user.getLastAppScanTime()); 3880 } 3881 } finally { 3882 logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start); 3883 } 3884 verifyStates(); 3885 } 3886 3887 @GuardedBy("mLock") 3888 private void rescanUpdatedPackagesLocked(@UserIdInt int userId, long lastScanTime) { 3889 if (DEBUG_REBOOT) { 3890 Slog.d(TAG, "rescan updated package user=" + userId + " last scanned=" + lastScanTime); 3891 } 3892 final ShortcutUser user = getUserShortcutsLocked(userId); 3893 3894 // Note after each OTA, we'll need to rescan all system apps, as their lastUpdateTime 3895 // is not reliable. 3896 final long now = injectCurrentTimeMillis(); 3897 final boolean afterOta = 3898 !injectBuildFingerprint().equals(user.getLastAppScanOsFingerprint()); 3899 3900 // Then for each installed app, publish manifest shortcuts when needed. 3901 forUpdatedPackages(userId, lastScanTime, afterOta, ai -> { 3902 user.attemptToRestoreIfNeededAndSave(this, ai.packageName, userId); 3903 3904 user.rescanPackageIfNeeded(ai.packageName, /* forceRescan= */ true); 3905 }); 3906 3907 // Write the time just before the scan, because there may be apps that have just 3908 // been updated, and we want to catch them in the next time. 3909 user.setLastAppScanTime(now); 3910 user.setLastAppScanOsFingerprint(injectBuildFingerprint()); 3911 scheduleSaveUser(userId); 3912 } 3913 3914 private void handlePackageAdded(String packageName, @UserIdInt int userId) { 3915 if (DEBUG || DEBUG_REBOOT) { 3916 Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId)); 3917 } 3918 synchronized (mLock) { 3919 final ShortcutUser user = getUserShortcutsLocked(userId); 3920 user.attemptToRestoreIfNeededAndSave(this, packageName, userId); 3921 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 3922 } 3923 verifyStates(); 3924 } 3925 3926 private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) { 3927 if (DEBUG || DEBUG_REBOOT) { 3928 Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d", 3929 packageName, userId)); 3930 } 3931 synchronized (mLock) { 3932 final ShortcutUser user = getUserShortcutsLocked(userId); 3933 user.attemptToRestoreIfNeededAndSave(this, packageName, userId); 3934 3935 if (isPackageInstalled(packageName, userId)) { 3936 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 3937 } 3938 } 3939 verifyStates(); 3940 } 3941 3942 private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) { 3943 if (DEBUG || DEBUG_REBOOT) { 3944 Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, 3945 packageUserId)); 3946 } 3947 cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ false); 3948 3949 verifyStates(); 3950 } 3951 3952 private void handlePackageDataCleared(String packageName, int packageUserId) { 3953 if (DEBUG || DEBUG_REBOOT) { 3954 Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName, 3955 packageUserId)); 3956 } 3957 cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ true); 3958 3959 verifyStates(); 3960 } 3961 3962 private void handlePackageChanged(String packageName, int packageUserId) { 3963 if (!isPackageInstalled(packageName, packageUserId)) { 3964 // Probably disabled, which is the same thing as uninstalled. 3965 handlePackageRemoved(packageName, packageUserId); 3966 return; 3967 } 3968 if (DEBUG || DEBUG_REBOOT) { 3969 Slog.d(TAG, String.format("handlePackageChanged: %s user=%d", packageName, 3970 packageUserId)); 3971 } 3972 3973 // Activities may be disabled or enabled. Just rescan the package. 3974 synchronized (mLock) { 3975 final ShortcutUser user = getUserShortcutsLocked(packageUserId); 3976 3977 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 3978 } 3979 3980 verifyStates(); 3981 } 3982 3983 // === PackageManager interaction === 3984 3985 /** 3986 * Returns {@link PackageInfo} unless it's uninstalled or disabled. 3987 */ 3988 @Nullable 3989 final PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) { 3990 return getPackageInfo(packageName, userId, true); 3991 } 3992 3993 /** 3994 * Returns {@link PackageInfo} unless it's uninstalled or disabled. 3995 */ 3996 @Nullable 3997 final PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) { 3998 return getPackageInfo(packageName, userId, false); 3999 } 4000 4001 int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) { 4002 final long token = injectClearCallingIdentity(); 4003 try { 4004 return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS, userId); 4005 } catch (RemoteException e) { 4006 // Shouldn't happen. 4007 Slog.wtf(TAG, "RemoteException", e); 4008 return -1; 4009 } finally { 4010 injectRestoreCallingIdentity(token); 4011 } 4012 } 4013 4014 /** 4015 * Returns {@link PackageInfo} unless it's uninstalled or disabled. 4016 */ 4017 @Nullable 4018 @VisibleForTesting 4019 final PackageInfo getPackageInfo(String packageName, @UserIdInt int userId, 4020 boolean getSignatures) { 4021 return isInstalledOrNull(injectPackageInfoWithUninstalled( 4022 packageName, userId, getSignatures)); 4023 } 4024 4025 /** 4026 * Do not use directly; this returns uninstalled packages too. 4027 */ 4028 @Nullable 4029 @VisibleForTesting 4030 PackageInfo injectPackageInfoWithUninstalled(String packageName, @UserIdInt int userId, 4031 boolean getSignatures) { 4032 final long start = getStatStartTime(); 4033 final long token = injectClearCallingIdentity(); 4034 try { 4035 return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS 4036 | (getSignatures ? PackageManager.GET_SIGNING_CERTIFICATES : 0), userId); 4037 } catch (RemoteException e) { 4038 // Shouldn't happen. 4039 Slog.wtf(TAG, "RemoteException", e); 4040 return null; 4041 } finally { 4042 injectRestoreCallingIdentity(token); 4043 4044 logDurationStat( 4045 (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO), 4046 start); 4047 } 4048 } 4049 4050 /** 4051 * Returns {@link ApplicationInfo} unless it's uninstalled or disabled. 4052 */ 4053 @Nullable 4054 @VisibleForTesting 4055 final ApplicationInfo getApplicationInfo(String packageName, @UserIdInt int userId) { 4056 return isInstalledOrNull(injectApplicationInfoWithUninstalled(packageName, userId)); 4057 } 4058 4059 /** 4060 * Do not use directly; this returns uninstalled packages too. 4061 */ 4062 @Nullable 4063 @VisibleForTesting 4064 ApplicationInfo injectApplicationInfoWithUninstalled( 4065 String packageName, @UserIdInt int userId) { 4066 final long start = getStatStartTime(); 4067 final long token = injectClearCallingIdentity(); 4068 try { 4069 return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId); 4070 } catch (RemoteException e) { 4071 // Shouldn't happen. 4072 Slog.wtf(TAG, "RemoteException", e); 4073 return null; 4074 } finally { 4075 injectRestoreCallingIdentity(token); 4076 4077 logDurationStat(Stats.GET_APPLICATION_INFO, start); 4078 } 4079 } 4080 4081 /** 4082 * Returns {@link ActivityInfo} with its metadata unless it's uninstalled or disabled. 4083 */ 4084 @Nullable 4085 final ActivityInfo getActivityInfoWithMetadata(ComponentName activity, @UserIdInt int userId) { 4086 return isInstalledOrNull(injectGetActivityInfoWithMetadataWithUninstalled( 4087 activity, userId)); 4088 } 4089 4090 /** 4091 * Do not use directly; this returns uninstalled packages too. 4092 */ 4093 @Nullable 4094 @VisibleForTesting 4095 ActivityInfo injectGetActivityInfoWithMetadataWithUninstalled( 4096 ComponentName activity, @UserIdInt int userId) { 4097 final long start = getStatStartTime(); 4098 final long token = injectClearCallingIdentity(); 4099 try { 4100 return mIPackageManager.getActivityInfo(activity, 4101 PACKAGE_MATCH_FLAGS | PackageManager.GET_META_DATA, userId); 4102 } catch (RemoteException e) { 4103 // Shouldn't happen. 4104 Slog.wtf(TAG, "RemoteException", e); 4105 return null; 4106 } finally { 4107 injectRestoreCallingIdentity(token); 4108 4109 logDurationStat(Stats.GET_ACTIVITY_WITH_METADATA, start); 4110 } 4111 } 4112 4113 /** 4114 * Return all installed and enabled packages. 4115 */ 4116 @NonNull 4117 @VisibleForTesting 4118 final List<PackageInfo> getInstalledPackages(@UserIdInt int userId) { 4119 final long start = getStatStartTime(); 4120 final long token = injectClearCallingIdentity(); 4121 try { 4122 final List<PackageInfo> all = injectGetPackagesWithUninstalled(userId); 4123 4124 all.removeIf(PACKAGE_NOT_INSTALLED); 4125 4126 return all; 4127 } catch (RemoteException e) { 4128 // Shouldn't happen. 4129 Slog.wtf(TAG, "RemoteException", e); 4130 return null; 4131 } finally { 4132 injectRestoreCallingIdentity(token); 4133 4134 logDurationStat(Stats.GET_INSTALLED_PACKAGES, start); 4135 } 4136 } 4137 4138 /** 4139 * Do not use directly; this returns uninstalled packages too. 4140 */ 4141 @NonNull 4142 @VisibleForTesting 4143 List<PackageInfo> injectGetPackagesWithUninstalled(@UserIdInt int userId) 4144 throws RemoteException { 4145 final ParceledListSlice<PackageInfo> parceledList = 4146 mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId); 4147 if (parceledList == null) { 4148 return Collections.emptyList(); 4149 } 4150 return parceledList.getList(); 4151 } 4152 4153 private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime, boolean afterOta, 4154 Consumer<ApplicationInfo> callback) { 4155 if (DEBUG || DEBUG_REBOOT) { 4156 Slog.d(TAG, "forUpdatedPackages for user " + userId + ", lastScanTime=" + lastScanTime 4157 + " afterOta=" + afterOta); 4158 } 4159 final List<PackageInfo> list = getInstalledPackages(userId); 4160 for (int i = list.size() - 1; i >= 0; i--) { 4161 final PackageInfo pi = list.get(i); 4162 4163 // If the package has been updated since the last scan time, then scan it. 4164 // Also if it's right after an OTA, always re-scan all apps anyway, since the 4165 // shortcut parser might have changed. 4166 if (afterOta || (pi.lastUpdateTime >= lastScanTime)) { 4167 if (DEBUG || DEBUG_REBOOT) { 4168 Slog.d(TAG, "Found updated package " + pi.packageName 4169 + " updateTime=" + pi.lastUpdateTime); 4170 } 4171 callback.accept(pi.applicationInfo); 4172 } 4173 } 4174 } 4175 4176 private boolean isApplicationFlagSet(@NonNull String packageName, int userId, int flags) { 4177 final ApplicationInfo ai = injectApplicationInfoWithUninstalled(packageName, userId); 4178 return (ai != null) && ((ai.flags & flags) == flags); 4179 } 4180 4181 // Due to b/38267327, ActivityInfo.enabled may not reflect the current state of the component 4182 // and we need to check the enabled state via PackageManager.getComponentEnabledSetting. 4183 private boolean isEnabled(@Nullable ActivityInfo ai, int userId) { 4184 if (ai == null) { 4185 return false; 4186 } 4187 4188 int enabledFlag; 4189 final long token = injectClearCallingIdentity(); 4190 try { 4191 enabledFlag = mIPackageManager.getComponentEnabledSetting( 4192 ai.getComponentName(), userId); 4193 } catch (RemoteException e) { 4194 // Shouldn't happen. 4195 Slog.wtf(TAG, "RemoteException", e); 4196 return false; 4197 } finally { 4198 injectRestoreCallingIdentity(token); 4199 } 4200 4201 if ((enabledFlag == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && ai.enabled) 4202 || enabledFlag == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { 4203 return true; 4204 } 4205 return false; 4206 } 4207 4208 private static boolean isSystem(@Nullable ActivityInfo ai) { 4209 return (ai != null) && isSystem(ai.applicationInfo); 4210 } 4211 4212 private static boolean isSystem(@Nullable ApplicationInfo ai) { 4213 return (ai != null) && (ai.flags & SYSTEM_APP_MASK) != 0; 4214 } 4215 4216 private static boolean isInstalled(@Nullable ApplicationInfo ai) { 4217 return (ai != null) && ai.enabled && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0; 4218 } 4219 4220 private static boolean isEphemeralApp(@Nullable ApplicationInfo ai) { 4221 return (ai != null) && ai.isInstantApp(); 4222 } 4223 4224 private static boolean isInstalled(@Nullable PackageInfo pi) { 4225 return (pi != null) && isInstalled(pi.applicationInfo); 4226 } 4227 4228 private static boolean isInstalled(@Nullable ActivityInfo ai) { 4229 return (ai != null) && isInstalled(ai.applicationInfo); 4230 } 4231 4232 private static ApplicationInfo isInstalledOrNull(ApplicationInfo ai) { 4233 return isInstalled(ai) ? ai : null; 4234 } 4235 4236 private static PackageInfo isInstalledOrNull(PackageInfo pi) { 4237 return isInstalled(pi) ? pi : null; 4238 } 4239 4240 private static ActivityInfo isInstalledOrNull(ActivityInfo ai) { 4241 return isInstalled(ai) ? ai : null; 4242 } 4243 4244 boolean isPackageInstalled(String packageName, int userId) { 4245 return getApplicationInfo(packageName, userId) != null; 4246 } 4247 4248 boolean isEphemeralApp(String packageName, int userId) { 4249 return isEphemeralApp(getApplicationInfo(packageName, userId)); 4250 } 4251 4252 @Nullable 4253 XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) { 4254 return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key); 4255 } 4256 4257 @Nullable 4258 Resources injectGetResourcesForApplicationAsUser(String packageName, int userId) { 4259 final long start = getStatStartTime(); 4260 final long token = injectClearCallingIdentity(); 4261 try { 4262 return mContext.createContextAsUser(UserHandle.of(userId), /* flags */ 0) 4263 .getPackageManager().getResourcesForApplication(packageName); 4264 } catch (NameNotFoundException e) { 4265 Slog.e(TAG, "Resources of package " + packageName + " for user " + userId 4266 + " not found"); 4267 return null; 4268 } finally { 4269 injectRestoreCallingIdentity(token); 4270 4271 logDurationStat(Stats.GET_APPLICATION_RESOURCES, start); 4272 } 4273 } 4274 4275 private Intent getMainActivityIntent() { 4276 final Intent intent = new Intent(Intent.ACTION_MAIN); 4277 intent.addCategory(LAUNCHER_INTENT_CATEGORY); 4278 return intent; 4279 } 4280 4281 /** 4282 * Same as queryIntentActivitiesAsUser, except it makes sure the package is installed, 4283 * and only returns exported activities. 4284 */ 4285 @NonNull 4286 @VisibleForTesting 4287 List<ResolveInfo> queryActivities(@NonNull Intent baseIntent, 4288 @NonNull String packageName, @Nullable ComponentName activity, int userId) { 4289 4290 baseIntent.setPackage(Objects.requireNonNull(packageName)); 4291 if (activity != null) { 4292 baseIntent.setComponent(activity); 4293 } 4294 return queryActivities(baseIntent, userId, /* exportedOnly =*/ true); 4295 } 4296 4297 @NonNull 4298 List<ResolveInfo> queryActivities(@NonNull Intent intent, int userId, 4299 boolean exportedOnly) { 4300 final List<ResolveInfo> resolved; 4301 final long token = injectClearCallingIdentity(); 4302 try { 4303 resolved = mContext.getPackageManager().queryIntentActivitiesAsUser(intent, 4304 PACKAGE_MATCH_FLAGS | PackageManager.MATCH_DISABLED_COMPONENTS, userId); 4305 } finally { 4306 injectRestoreCallingIdentity(token); 4307 } 4308 if (resolved == null || resolved.size() == 0) { 4309 return EMPTY_RESOLVE_INFO; 4310 } 4311 // Make sure the package is installed. 4312 resolved.removeIf(ACTIVITY_NOT_INSTALLED); 4313 resolved.removeIf((ri) -> { 4314 final ActivityInfo ai = ri.activityInfo; 4315 return !isSystem(ai) && !isEnabled(ai, userId); 4316 }); 4317 if (exportedOnly) { 4318 resolved.removeIf(ACTIVITY_NOT_EXPORTED); 4319 } 4320 return resolved; 4321 } 4322 4323 /** 4324 * Return the main activity that is exported and, for non-system apps, enabled. If multiple 4325 * activities are found, return the first one. 4326 */ 4327 @Nullable 4328 ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) { 4329 final long start = getStatStartTime(); 4330 try { 4331 final List<ResolveInfo> resolved = 4332 queryActivities(getMainActivityIntent(), packageName, null, userId); 4333 return resolved.size() == 0 ? null : resolved.get(0).activityInfo.getComponentName(); 4334 } finally { 4335 logDurationStat(Stats.GET_LAUNCHER_ACTIVITY, start); 4336 } 4337 } 4338 4339 /** 4340 * Return whether an activity is main, exported and, for non-system apps, enabled. 4341 */ 4342 boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) { 4343 final long start = getStatStartTime(); 4344 try { 4345 if (activity == null) { 4346 wtf("null activity detected"); 4347 return false; 4348 } 4349 if (DUMMY_MAIN_ACTIVITY.equals(activity.getClassName())) { 4350 return true; 4351 } 4352 final List<ResolveInfo> resolved = queryActivities( 4353 getMainActivityIntent(), activity.getPackageName(), activity, userId); 4354 return resolved.size() > 0; 4355 } finally { 4356 logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start); 4357 } 4358 } 4359 4360 /** 4361 * Create a placeholder "main activity" component name which is used to create a dynamic shortcut 4362 * with no main activity temporarily. 4363 */ 4364 @NonNull 4365 ComponentName getDummyMainActivity(@NonNull String packageName) { 4366 return new ComponentName(packageName, DUMMY_MAIN_ACTIVITY); 4367 } 4368 4369 boolean isDummyMainActivity(@Nullable ComponentName name) { 4370 return name != null && DUMMY_MAIN_ACTIVITY.equals(name.getClassName()); 4371 } 4372 4373 /** 4374 * Return all the main activities that are exported and, for non-system apps, enabled, from a 4375 * package. 4376 */ 4377 @NonNull 4378 List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) { 4379 final long start = getStatStartTime(); 4380 try { 4381 return queryActivities(getMainActivityIntent(), packageName, null, userId); 4382 } finally { 4383 logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start); 4384 } 4385 } 4386 4387 /** 4388 * Return whether an activity is enabled and exported. 4389 */ 4390 @VisibleForTesting 4391 boolean injectIsActivityEnabledAndExported( 4392 @NonNull ComponentName activity, @UserIdInt int userId) { 4393 final long start = getStatStartTime(); 4394 try { 4395 return queryActivities(new Intent(), activity.getPackageName(), activity, userId) 4396 .size() > 0; 4397 } finally { 4398 logDurationStat(Stats.IS_ACTIVITY_ENABLED, start); 4399 } 4400 } 4401 4402 /** 4403 * Get the {@link LauncherApps#ACTION_CONFIRM_PIN_SHORTCUT} or 4404 * {@link LauncherApps#ACTION_CONFIRM_PIN_APPWIDGET} activity in a given package depending on 4405 * the requestType. 4406 */ 4407 @Nullable 4408 ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName, 4409 int launcherUserId, int requestType) { 4410 Objects.requireNonNull(launcherPackageName); 4411 String action = requestType == LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ? 4412 LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT : 4413 LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET; 4414 4415 final Intent confirmIntent = new Intent(action).setPackage(launcherPackageName); 4416 final List<ResolveInfo> candidates = queryActivities( 4417 confirmIntent, launcherUserId, /* exportedOnly =*/ false); 4418 for (ResolveInfo ri : candidates) { 4419 return ri.activityInfo.getComponentName(); 4420 } 4421 return null; 4422 } 4423 4424 boolean injectIsSafeModeEnabled() { 4425 final long token = injectClearCallingIdentity(); 4426 try { 4427 return IWindowManager.Stub 4428 .asInterface(ServiceManager.getService(Context.WINDOW_SERVICE)) 4429 .isSafeModeEnabled(); 4430 } catch (RemoteException e) { 4431 return false; // Shouldn't happen though. 4432 } finally { 4433 injectRestoreCallingIdentity(token); 4434 } 4435 } 4436 4437 /** 4438 * If {@code userId} is of a managed profile, return the parent user ID. Otherwise return 4439 * itself. 4440 */ 4441 int getParentOrSelfUserId(int userId) { 4442 return mUserManagerInternal.getProfileParentId(userId); 4443 } 4444 4445 void injectSendIntentSender(IntentSender intentSender, Intent extras) { 4446 if (intentSender == null) { 4447 return; 4448 } 4449 try { 4450 intentSender.sendIntent(mContext, /* code= */ 0, extras, 4451 /* onFinished=*/ null, /* handler= */ null); 4452 } catch (SendIntentException e) { 4453 Slog.w(TAG, "sendIntent failed().", e); 4454 } 4455 } 4456 4457 // === Backup & restore === 4458 4459 boolean shouldBackupApp(String packageName, int userId) { 4460 return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP); 4461 } 4462 4463 static boolean shouldBackupApp(PackageInfo pi) { 4464 return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0; 4465 } 4466 4467 @Override 4468 public byte[] getBackupPayload(@UserIdInt int userId) { 4469 enforceSystem(); 4470 if (DEBUG) { 4471 Slog.d(TAG, "Backing up user " + userId); 4472 } 4473 synchronized (mLock) { 4474 if (!isUserUnlockedL(userId)) { 4475 wtf("Can't backup: user " + userId + " is locked or not running"); 4476 return null; 4477 } 4478 4479 final ShortcutUser user = getUserShortcutsLocked(userId); 4480 if (user == null) { 4481 wtf("Can't backup: user not found: id=" + userId); 4482 return null; 4483 } 4484 4485 // Update the signatures for all packages. 4486 user.forAllPackageItems(spi -> spi.refreshPackageSignatureAndSave()); 4487 4488 // Rescan all apps; this will also update the version codes and "allow-backup". 4489 user.forAllPackages(pkg -> pkg.rescanPackageIfNeeded( 4490 /*isNewApp=*/ false, /*forceRescan=*/ true)); 4491 4492 // Set the version code for the launchers. 4493 user.forAllLaunchers(launcher -> launcher.ensurePackageInfo()); 4494 4495 // Save to the filesystem. 4496 scheduleSaveUser(userId); 4497 saveDirtyInfo(); 4498 4499 // Note, in case of backup, we don't have to wait on bitmap saving, because we don't 4500 // back up bitmaps anyway. 4501 4502 // Then create the backup payload. 4503 final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024); 4504 try { 4505 saveUserInternalLocked(userId, os, /* forBackup */ true); 4506 } catch (XmlPullParserException | IOException e) { 4507 // Shouldn't happen. 4508 Slog.w(TAG, "Backup failed.", e); 4509 return null; 4510 } 4511 byte[] payload = os.toByteArray(); 4512 mShortcutDumpFiles.save("backup-1-payload.txt", payload); 4513 return payload; 4514 } 4515 } 4516 4517 @Override 4518 public AndroidFuture applyRestore(byte[] payload, @UserIdInt int userId) { 4519 final AndroidFuture<Void> ret = new AndroidFuture<>(); 4520 try { 4521 enforceSystem(); 4522 } catch (Exception e) { 4523 ret.completeExceptionally(e); 4524 return ret; 4525 } 4526 injectPostToHandler(() -> { 4527 try { 4528 if (DEBUG || DEBUG_REBOOT) { 4529 Slog.d(TAG, "Restoring user " + userId); 4530 } 4531 synchronized (mLock) { 4532 if (!isUserUnlockedL(userId)) { 4533 wtf("Can't restore: user " + userId + " is locked or not running"); 4534 ret.complete(null); 4535 return; 4536 } 4537 4538 // Note we print the file timestamps in dumpsys too, but also printing the 4539 // timestamp in the files anyway. 4540 mShortcutDumpFiles.save("restore-0-start.txt", pw -> { 4541 pw.print("Start time: "); 4542 dumpCurrentTime(pw); 4543 pw.println(); 4544 }); 4545 mShortcutDumpFiles.save("restore-1-payload.xml", payload); 4546 4547 // Actually do restore. 4548 final ShortcutUser restored; 4549 final ByteArrayInputStream is = new ByteArrayInputStream(payload); 4550 try { 4551 restored = loadUserInternal(userId, is, /* fromBackup */ true); 4552 } catch (XmlPullParserException | IOException | InvalidFileFormatException e) { 4553 Slog.w(TAG, "Restoration failed.", e); 4554 ret.complete(null); 4555 return; 4556 } 4557 mShortcutDumpFiles.save("restore-2.txt", this::dumpInner); 4558 4559 getUserShortcutsLocked(userId).mergeRestoredFile(restored); 4560 4561 mShortcutDumpFiles.save("restore-3.txt", this::dumpInner); 4562 4563 // Rescan all packages to re-publish manifest shortcuts and do other checks. 4564 rescanUpdatedPackagesLocked(userId, 4565 0 // lastScanTime = 0; rescan all packages. 4566 ); 4567 4568 mShortcutDumpFiles.save("restore-4.txt", this::dumpInner); 4569 4570 mShortcutDumpFiles.save("restore-5-finish.txt", pw -> { 4571 pw.print("Finish time: "); 4572 dumpCurrentTime(pw); 4573 pw.println(); 4574 }); 4575 4576 saveUserLocked(userId); 4577 } 4578 ret.complete(null); 4579 } catch (Exception e) { 4580 ret.completeExceptionally(e); 4581 } 4582 }); 4583 return ret; 4584 } 4585 4586 // === Dump === 4587 4588 @Override 4589 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 4590 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; 4591 dumpNoCheck(fd, pw, args); 4592 } 4593 4594 @VisibleForTesting 4595 void dumpNoCheck(FileDescriptor fd, PrintWriter pw, String[] args) { 4596 final DumpFilter filter = parseDumpArgs(args); 4597 4598 if (filter.shouldDumpCheckIn()) { 4599 // Other flags are not supported for checkin. 4600 dumpCheckin(pw, filter.shouldCheckInClear()); 4601 } else { 4602 if (filter.shouldDumpMain()) { 4603 dumpInner(pw, filter); 4604 pw.println(); 4605 } 4606 if (filter.shouldDumpUid()) { 4607 dumpUid(pw); 4608 pw.println(); 4609 } 4610 if (filter.shouldDumpFiles()) { 4611 dumpDumpFiles(pw); 4612 pw.println(); 4613 } 4614 } 4615 } 4616 4617 private static DumpFilter parseDumpArgs(String[] args) { 4618 final DumpFilter filter = new DumpFilter(); 4619 if (args == null) { 4620 return filter; 4621 } 4622 4623 int argIndex = 0; 4624 while (argIndex < args.length) { 4625 final String arg = args[argIndex++]; 4626 4627 if ("-c".equals(arg)) { 4628 filter.setDumpCheckIn(true); 4629 continue; 4630 } 4631 if ("--checkin".equals(arg)) { 4632 filter.setDumpCheckIn(true); 4633 filter.setCheckInClear(true); 4634 continue; 4635 } 4636 if ("-a".equals(arg) || "--all".equals(arg)) { 4637 filter.setDumpUid(true); 4638 filter.setDumpFiles(true); 4639 continue; 4640 } 4641 if ("-u".equals(arg) || "--uid".equals(arg)) { 4642 filter.setDumpUid(true); 4643 continue; 4644 } 4645 if ("-f".equals(arg) || "--files".equals(arg)) { 4646 filter.setDumpFiles(true); 4647 continue; 4648 } 4649 if ("-n".equals(arg) || "--no-main".equals(arg)) { 4650 filter.setDumpMain(false); 4651 continue; 4652 } 4653 if ("--user".equals(arg)) { 4654 if (argIndex >= args.length) { 4655 throw new IllegalArgumentException("Missing user ID for --user"); 4656 } 4657 try { 4658 filter.addUser(Integer.parseInt(args[argIndex++])); 4659 } catch (NumberFormatException e) { 4660 throw new IllegalArgumentException("Invalid user ID", e); 4661 } 4662 continue; 4663 } 4664 if ("-p".equals(arg) || "--package".equals(arg)) { 4665 if (argIndex >= args.length) { 4666 throw new IllegalArgumentException("Missing package name for --package"); 4667 } 4668 filter.addPackageRegex(args[argIndex++]); 4669 filter.setDumpDetails(false); 4670 continue; 4671 } 4672 if (arg.startsWith("-")) { 4673 throw new IllegalArgumentException("Unknown option " + arg); 4674 } 4675 break; 4676 } 4677 while (argIndex < args.length) { 4678 filter.addPackage(args[argIndex++]); 4679 } 4680 return filter; 4681 } 4682 4683 static class DumpFilter { 4684 private boolean mDumpCheckIn = false; 4685 private boolean mCheckInClear = false; 4686 4687 private boolean mDumpMain = true; 4688 private boolean mDumpUid = false; 4689 private boolean mDumpFiles = false; 4690 4691 private boolean mDumpDetails = true; 4692 private List<Pattern> mPackagePatterns = new ArrayList<>(); 4693 private List<Integer> mUsers = new ArrayList<>(); 4694 4695 void addPackageRegex(String regex) { 4696 mPackagePatterns.add(Pattern.compile(regex)); 4697 } 4698 4699 public void addPackage(String packageName) { 4700 addPackageRegex(Pattern.quote(packageName)); 4701 } 4702 4703 void addUser(int userId) { 4704 mUsers.add(userId); 4705 } 4706 4707 boolean isPackageMatch(String packageName) { 4708 if (mPackagePatterns.size() == 0) { 4709 return true; 4710 } 4711 for (int i = 0; i < mPackagePatterns.size(); i++) { 4712 if (mPackagePatterns.get(i).matcher(packageName).find()) { 4713 return true; 4714 } 4715 } 4716 return false; 4717 } 4718 4719 boolean isUserMatch(int userId) { 4720 if (mUsers.size() == 0) { 4721 return true; 4722 } 4723 for (int i = 0; i < mUsers.size(); i++) { 4724 if (mUsers.get(i) == userId) { 4725 return true; 4726 } 4727 } 4728 return false; 4729 } 4730 4731 public boolean shouldDumpCheckIn() { 4732 return mDumpCheckIn; 4733 } 4734 4735 public void setDumpCheckIn(boolean dumpCheckIn) { 4736 mDumpCheckIn = dumpCheckIn; 4737 } 4738 4739 public boolean shouldCheckInClear() { 4740 return mCheckInClear; 4741 } 4742 4743 public void setCheckInClear(boolean checkInClear) { 4744 mCheckInClear = checkInClear; 4745 } 4746 4747 public boolean shouldDumpMain() { 4748 return mDumpMain; 4749 } 4750 4751 public void setDumpMain(boolean dumpMain) { 4752 mDumpMain = dumpMain; 4753 } 4754 4755 public boolean shouldDumpUid() { 4756 return mDumpUid; 4757 } 4758 4759 public void setDumpUid(boolean dumpUid) { 4760 mDumpUid = dumpUid; 4761 } 4762 4763 public boolean shouldDumpFiles() { 4764 return mDumpFiles; 4765 } 4766 4767 public void setDumpFiles(boolean dumpFiles) { 4768 mDumpFiles = dumpFiles; 4769 } 4770 4771 public boolean shouldDumpDetails() { 4772 return mDumpDetails; 4773 } 4774 4775 public void setDumpDetails(boolean dumpDetails) { 4776 mDumpDetails = dumpDetails; 4777 } 4778 } 4779 4780 private void dumpInner(PrintWriter pw) { 4781 dumpInner(pw, new DumpFilter()); 4782 } 4783 4784 private void dumpInner(PrintWriter pw, DumpFilter filter) { 4785 synchronized (mLock) { 4786 if (filter.shouldDumpDetails()) { 4787 final long now = injectCurrentTimeMillis(); 4788 pw.print("Now: ["); 4789 pw.print(now); 4790 pw.print("] "); 4791 pw.print(formatTime(now)); 4792 4793 pw.print(" Raw last reset: ["); 4794 pw.print(mRawLastResetTime); 4795 pw.print("] "); 4796 pw.print(formatTime(mRawLastResetTime)); 4797 4798 final long last = getLastResetTimeLocked(); 4799 pw.print(" Last reset: ["); 4800 pw.print(last); 4801 pw.print("] "); 4802 pw.print(formatTime(last)); 4803 4804 final long next = getNextResetTimeLocked(); 4805 pw.print(" Next reset: ["); 4806 pw.print(next); 4807 pw.print("] "); 4808 pw.print(formatTime(next)); 4809 pw.println(); 4810 pw.println(); 4811 4812 pw.print(" Config:"); 4813 pw.print(" Max icon dim: "); 4814 pw.println(mMaxIconDimension); 4815 pw.print(" Icon format: "); 4816 pw.println(mIconPersistFormat); 4817 pw.print(" Icon quality: "); 4818 pw.println(mIconPersistQuality); 4819 pw.print(" saveDelayMillis: "); 4820 pw.println(mSaveDelayMillis); 4821 pw.print(" resetInterval: "); 4822 pw.println(mResetInterval); 4823 pw.print(" maxUpdatesPerInterval: "); 4824 pw.println(mMaxUpdatesPerInterval); 4825 pw.print(" maxShortcutsPerActivity: "); 4826 pw.println(mMaxShortcuts); 4827 pw.println(); 4828 4829 mStatLogger.dump(pw, " "); 4830 4831 pw.println(); 4832 pw.print(" #Failures: "); 4833 pw.println(mWtfCount); 4834 4835 if (mLastWtfStacktrace != null) { 4836 pw.print(" Last failure stack trace: "); 4837 pw.println(Log.getStackTraceString(mLastWtfStacktrace)); 4838 } 4839 4840 pw.println(); 4841 mShortcutBitmapSaver.dumpLocked(pw, " "); 4842 4843 pw.println(); 4844 } 4845 4846 for (int i = 0; i < mUsers.size(); i++) { 4847 final ShortcutUser user = mUsers.valueAt(i); 4848 if (filter.isUserMatch(user.getUserId())) { 4849 user.dump(pw, " ", filter); 4850 pw.println(); 4851 } 4852 } 4853 4854 for (int i = 0; i < mShortcutNonPersistentUsers.size(); i++) { 4855 final ShortcutNonPersistentUser user = mShortcutNonPersistentUsers.valueAt(i); 4856 if (filter.isUserMatch(user.getUserId())) { 4857 user.dump(pw, " ", filter); 4858 pw.println(); 4859 } 4860 } 4861 } 4862 } 4863 4864 private void dumpUid(PrintWriter pw) { 4865 synchronized (mLock) { 4866 pw.println("** SHORTCUT MANAGER UID STATES (dumpsys shortcut -n -u)"); 4867 4868 for (int i = 0; i < mUidState.size(); i++) { 4869 final int uid = mUidState.keyAt(i); 4870 final int state = mUidState.valueAt(i); 4871 pw.print(" UID="); 4872 pw.print(uid); 4873 pw.print(" state="); 4874 pw.print(state); 4875 if (isProcessStateForeground(state)) { 4876 pw.print(" [FG]"); 4877 } 4878 pw.print(" last FG="); 4879 pw.print(mUidLastForegroundElapsedTime.get(uid)); 4880 pw.println(); 4881 } 4882 } 4883 } 4884 4885 static String formatTime(long time) { 4886 return TimeMigrationUtils.formatMillisWithFixedFormat(time); 4887 } 4888 4889 private void dumpCurrentTime(PrintWriter pw) { 4890 pw.print(formatTime(injectCurrentTimeMillis())); 4891 } 4892 4893 /** 4894 * Dumpsys for checkin. 4895 * 4896 * @param clear if true, clear the history information. Some other system services have this 4897 * behavior but shortcut service doesn't for now. 4898 */ 4899 private void dumpCheckin(PrintWriter pw, boolean clear) { 4900 synchronized (mLock) { 4901 try { 4902 final JSONArray users = new JSONArray(); 4903 4904 for (int i = 0; i < mUsers.size(); i++) { 4905 users.put(mUsers.valueAt(i).dumpCheckin(clear)); 4906 } 4907 4908 final JSONObject result = new JSONObject(); 4909 4910 result.put(KEY_SHORTCUT, users); 4911 result.put(KEY_LOW_RAM, injectIsLowRamDevice()); 4912 result.put(KEY_ICON_SIZE, mMaxIconDimension); 4913 4914 pw.println(result.toString(1)); 4915 } catch (JSONException e) { 4916 Slog.e(TAG, "Unable to write in json", e); 4917 } 4918 } 4919 } 4920 4921 private void dumpDumpFiles(PrintWriter pw) { 4922 synchronized (mLock) { 4923 pw.println("** SHORTCUT MANAGER FILES (dumpsys shortcut -n -f)"); 4924 mShortcutDumpFiles.dumpAll(pw); 4925 } 4926 } 4927 4928 // === Shell support === 4929 4930 @Override 4931 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 4932 String[] args, ShellCallback callback, ResultReceiver resultReceiver) { 4933 4934 enforceShell(); 4935 4936 final long token = injectClearCallingIdentity(); 4937 try { 4938 final int status = (new MyShellCommand()).exec(this, in, out, err, args, callback, 4939 resultReceiver); 4940 resultReceiver.send(status, null); 4941 } finally { 4942 injectRestoreCallingIdentity(token); 4943 } 4944 } 4945 4946 static class CommandException extends Exception { 4947 public CommandException(String message) { 4948 super(message); 4949 } 4950 } 4951 4952 /** 4953 * Handle "adb shell cmd". 4954 */ 4955 private class MyShellCommand extends ShellCommand { 4956 4957 private int mUserId = UserHandle.USER_SYSTEM; 4958 4959 private int mShortcutMatchFlags = ShortcutManager.FLAG_MATCH_CACHED 4960 | ShortcutManager.FLAG_MATCH_DYNAMIC | ShortcutManager.FLAG_MATCH_MANIFEST 4961 | ShortcutManager.FLAG_MATCH_PINNED; 4962 4963 private void parseOptionsLocked(boolean takeUser) 4964 throws CommandException { 4965 String opt; 4966 while ((opt = getNextOption()) != null) { 4967 switch (opt) { 4968 case "--user": 4969 if (takeUser) { 4970 mUserId = UserHandle.parseUserArg(getNextArgRequired()); 4971 if (!isUserUnlockedL(mUserId)) { 4972 throw new CommandException( 4973 "User " + mUserId + " is not running or locked"); 4974 } 4975 break; 4976 } 4977 // fallthrough 4978 case "--flags": 4979 mShortcutMatchFlags = Integer.parseInt(getNextArgRequired()); 4980 break; 4981 default: 4982 throw new CommandException("Unknown option: " + opt); 4983 } 4984 } 4985 } 4986 4987 @Override 4988 public int onCommand(String cmd) { 4989 if (cmd == null) { 4990 return handleDefaultCommands(cmd); 4991 } 4992 final PrintWriter pw = getOutPrintWriter(); 4993 try { 4994 switch (cmd) { 4995 case "reset-throttling": 4996 handleResetThrottling(); 4997 break; 4998 case "reset-all-throttling": 4999 handleResetAllThrottling(); 5000 break; 5001 case "override-config": 5002 handleOverrideConfig(); 5003 break; 5004 case "reset-config": 5005 handleResetConfig(); 5006 break; 5007 case "get-default-launcher": 5008 handleGetDefaultLauncher(); 5009 break; 5010 case "unload-user": 5011 handleUnloadUser(); 5012 break; 5013 case "clear-shortcuts": 5014 handleClearShortcuts(); 5015 break; 5016 case "get-shortcuts": 5017 handleGetShortcuts(); 5018 break; 5019 case "verify-states": // hidden command to verify various internal states. 5020 handleVerifyStates(); 5021 break; 5022 case "has-shortcut-access": 5023 handleHasShortcutAccess(); 5024 break; 5025 default: 5026 return handleDefaultCommands(cmd); 5027 } 5028 } catch (CommandException e) { 5029 pw.println("Error: " + e.getMessage()); 5030 return 1; 5031 } 5032 pw.println("Success"); 5033 return 0; 5034 } 5035 5036 @Override 5037 public void onHelp() { 5038 final PrintWriter pw = getOutPrintWriter(); 5039 pw.println("Usage: cmd shortcut COMMAND [options ...]"); 5040 pw.println(); 5041 pw.println("cmd shortcut reset-throttling [--user USER_ID]"); 5042 pw.println(" Reset throttling for all packages and users"); 5043 pw.println(); 5044 pw.println("cmd shortcut reset-all-throttling"); 5045 pw.println(" Reset the throttling state for all users"); 5046 pw.println(); 5047 pw.println("cmd shortcut override-config CONFIG"); 5048 pw.println(" Override the configuration for testing (will last until reboot)"); 5049 pw.println(); 5050 pw.println("cmd shortcut reset-config"); 5051 pw.println(" Reset the configuration set with \"update-config\""); 5052 pw.println(); 5053 pw.println("[Deprecated] cmd shortcut get-default-launcher [--user USER_ID]"); 5054 pw.println(" Show the default launcher"); 5055 pw.println(" Note: This command is deprecated. Callers should query the default" 5056 + " launcher from RoleManager instead."); 5057 pw.println(); 5058 pw.println("cmd shortcut unload-user [--user USER_ID]"); 5059 pw.println(" Unload a user from the memory"); 5060 pw.println(" (This should not affect any observable behavior)"); 5061 pw.println(); 5062 pw.println("cmd shortcut clear-shortcuts [--user USER_ID] PACKAGE"); 5063 pw.println(" Remove all shortcuts from a package, including pinned shortcuts"); 5064 pw.println(); 5065 pw.println("cmd shortcut get-shortcuts [--user USER_ID] [--flags FLAGS] PACKAGE"); 5066 pw.println(" Show the shortcuts for a package that match the given flags"); 5067 pw.println(); 5068 pw.println("cmd shortcut has-shortcut-access [--user USER_ID] PACKAGE"); 5069 pw.println(" Prints \"true\" if the package can access shortcuts," 5070 + " \"false\" otherwise"); 5071 pw.println(); 5072 } 5073 5074 private void handleResetThrottling() throws CommandException { 5075 synchronized (mLock) { 5076 parseOptionsLocked(/* takeUser =*/ true); 5077 5078 Slog.i(TAG, "cmd: handleResetThrottling: user=" + mUserId); 5079 5080 resetThrottlingInner(mUserId); 5081 } 5082 } 5083 5084 private void handleResetAllThrottling() { 5085 Slog.i(TAG, "cmd: handleResetAllThrottling"); 5086 5087 resetAllThrottlingInner(); 5088 } 5089 5090 private void handleOverrideConfig() throws CommandException { 5091 final String config = getNextArgRequired(); 5092 5093 Slog.i(TAG, "cmd: handleOverrideConfig: " + config); 5094 5095 synchronized (mLock) { 5096 if (!updateConfigurationLocked(config)) { 5097 throw new CommandException("override-config failed. See logcat for details."); 5098 } 5099 } 5100 } 5101 5102 private void handleResetConfig() { 5103 Slog.i(TAG, "cmd: handleResetConfig"); 5104 5105 synchronized (mLock) { 5106 loadConfigurationLocked(); 5107 } 5108 } 5109 5110 // This method is used by various cts modules to get the current default launcher. Tests 5111 // should query this information directly from RoleManager instead. Keeping the old behavior 5112 // by returning the result from package manager. 5113 private void handleGetDefaultLauncher() throws CommandException { 5114 synchronized (mLock) { 5115 parseOptionsLocked(/* takeUser =*/ true); 5116 5117 final String defaultLauncher = getDefaultLauncher(mUserId); 5118 if (defaultLauncher == null) { 5119 throw new CommandException( 5120 "Failed to get the default launcher for user " + mUserId); 5121 } 5122 5123 // Get the class name of the component from PM to keep the old behaviour. 5124 final List<ResolveInfo> allHomeCandidates = new ArrayList<>(); 5125 mPackageManagerInternal.getHomeActivitiesAsUser(allHomeCandidates, 5126 getParentOrSelfUserId(mUserId)); 5127 for (ResolveInfo ri : allHomeCandidates) { 5128 final ComponentInfo ci = ri.getComponentInfo(); 5129 if (ci.packageName.equals(defaultLauncher)) { 5130 getOutPrintWriter().println("Launcher: " + ci.getComponentName()); 5131 break; 5132 } 5133 } 5134 } 5135 } 5136 5137 private void handleUnloadUser() throws CommandException { 5138 synchronized (mLock) { 5139 parseOptionsLocked(/* takeUser =*/ true); 5140 5141 Slog.i(TAG, "cmd: handleUnloadUser: user=" + mUserId); 5142 5143 ShortcutService.this.handleStopUser(mUserId); 5144 } 5145 } 5146 5147 private void handleClearShortcuts() throws CommandException { 5148 synchronized (mLock) { 5149 parseOptionsLocked(/* takeUser =*/ true); 5150 final String packageName = getNextArgRequired(); 5151 5152 Slog.i(TAG, "cmd: handleClearShortcuts: user" + mUserId + ", " + packageName); 5153 5154 ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId, 5155 /* appStillExists = */ true); 5156 } 5157 } 5158 5159 private void handleGetShortcuts() throws CommandException { 5160 synchronized (mLock) { 5161 parseOptionsLocked(/* takeUser =*/ true); 5162 final String packageName = getNextArgRequired(); 5163 5164 Slog.i(TAG, "cmd: handleGetShortcuts: user=" + mUserId + ", flags=" 5165 + mShortcutMatchFlags + ", package=" + packageName); 5166 5167 final ShortcutUser user = ShortcutService.this.getUserShortcutsLocked(mUserId); 5168 final ShortcutPackage p = user.getPackageShortcutsIfExists(packageName); 5169 if (p == null) { 5170 return; 5171 } 5172 5173 p.dumpShortcuts(getOutPrintWriter(), mShortcutMatchFlags); 5174 } 5175 } 5176 5177 private void handleVerifyStates() throws CommandException { 5178 try { 5179 verifyStatesForce(); // This will throw when there's an issue. 5180 } catch (Throwable th) { 5181 throw new CommandException(th.getMessage() + "\n" + Log.getStackTraceString(th)); 5182 } 5183 } 5184 5185 private void handleHasShortcutAccess() throws CommandException { 5186 synchronized (mLock) { 5187 parseOptionsLocked(/* takeUser =*/ true); 5188 final String packageName = getNextArgRequired(); 5189 5190 boolean shortcutAccess = hasShortcutHostPermissionInner(packageName, mUserId); 5191 getOutPrintWriter().println(Boolean.toString(shortcutAccess)); 5192 } 5193 } 5194 } 5195 5196 // === Unit test support === 5197 5198 // Injection point. 5199 @VisibleForTesting 5200 long injectCurrentTimeMillis() { 5201 return System.currentTimeMillis(); 5202 } 5203 5204 @VisibleForTesting 5205 long injectElapsedRealtime() { 5206 return SystemClock.elapsedRealtime(); 5207 } 5208 5209 @VisibleForTesting 5210 long injectUptimeMillis() { 5211 return SystemClock.uptimeMillis(); 5212 } 5213 5214 // Injection point. 5215 @VisibleForTesting 5216 int injectBinderCallingUid() { 5217 return getCallingUid(); 5218 } 5219 5220 @VisibleForTesting 5221 int injectBinderCallingPid() { 5222 return getCallingPid(); 5223 } 5224 5225 private int getCallingUserId() { 5226 return UserHandle.getUserId(injectBinderCallingUid()); 5227 } 5228 5229 // Injection point. 5230 @VisibleForTesting 5231 long injectClearCallingIdentity() { 5232 return Binder.clearCallingIdentity(); 5233 } 5234 5235 // Injection point. 5236 @VisibleForTesting 5237 void injectRestoreCallingIdentity(long token) { 5238 Binder.restoreCallingIdentity(token); 5239 } 5240 5241 // Injection point. 5242 String injectBuildFingerprint() { 5243 return Build.FINGERPRINT; 5244 } 5245 5246 final void wtf(String message) { 5247 wtf(message, /* exception= */ null); 5248 } 5249 5250 // Injection point. 5251 void wtf(String message, Throwable e) { 5252 if (e == null) { 5253 e = new RuntimeException("Stacktrace"); 5254 } 5255 synchronized (mLock) { 5256 mWtfCount++; 5257 mLastWtfStacktrace = new Exception("Last failure was logged here:"); 5258 } 5259 Slog.wtf(TAG, message, e); 5260 } 5261 5262 @VisibleForTesting 5263 File injectSystemDataPath() { 5264 return Environment.getDataSystemDirectory(); 5265 } 5266 5267 @VisibleForTesting 5268 File injectUserDataPath(@UserIdInt int userId) { 5269 return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER); 5270 } 5271 5272 public File getDumpPath() { 5273 return new File(injectUserDataPath(UserHandle.USER_SYSTEM), DIRECTORY_DUMP); 5274 } 5275 5276 @VisibleForTesting 5277 boolean injectIsLowRamDevice() { 5278 return ActivityManager.isLowRamDeviceStatic(); 5279 } 5280 5281 @VisibleForTesting 5282 void injectRegisterUidObserver(IUidObserver observer, int which) { 5283 try { 5284 ActivityManager.getService().registerUidObserver(observer, which, 5285 ActivityManager.PROCESS_STATE_UNKNOWN, null); 5286 } catch (RemoteException shouldntHappen) { 5287 } 5288 } 5289 5290 @VisibleForTesting 5291 void injectRegisterRoleHoldersListener(OnRoleHoldersChangedListener listener) { 5292 mRoleManager.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(), listener, 5293 UserHandle.ALL); 5294 } 5295 5296 @VisibleForTesting 5297 String injectGetHomeRoleHolderAsUser(int userId) { 5298 List<String> roleHolders = mRoleManager.getRoleHoldersAsUser( 5299 RoleManager.ROLE_HOME, UserHandle.of(userId)); 5300 return roleHolders.isEmpty() ? null : roleHolders.get(0); 5301 } 5302 5303 File getUserBitmapFilePath(@UserIdInt int userId) { 5304 return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS); 5305 } 5306 5307 @VisibleForTesting 5308 SparseArray<ShortcutUser> getShortcutsForTest() { 5309 return mUsers; 5310 } 5311 5312 @VisibleForTesting 5313 int getMaxShortcutsForTest() { 5314 return mMaxShortcuts; 5315 } 5316 5317 @VisibleForTesting 5318 int getMaxUpdatesPerIntervalForTest() { 5319 return mMaxUpdatesPerInterval; 5320 } 5321 5322 @VisibleForTesting 5323 long getResetIntervalForTest() { 5324 return mResetInterval; 5325 } 5326 5327 @VisibleForTesting 5328 int getMaxIconDimensionForTest() { 5329 return mMaxIconDimension; 5330 } 5331 5332 @VisibleForTesting 5333 CompressFormat getIconPersistFormatForTest() { 5334 return mIconPersistFormat; 5335 } 5336 5337 @VisibleForTesting 5338 int getIconPersistQualityForTest() { 5339 return mIconPersistQuality; 5340 } 5341 5342 @VisibleForTesting 5343 ShortcutPackage getPackageShortcutForTest(String packageName, int userId) { 5344 synchronized (mLock) { 5345 final ShortcutUser user = mUsers.get(userId); 5346 if (user == null) return null; 5347 5348 return user.getAllPackagesForTest().get(packageName); 5349 } 5350 } 5351 5352 @VisibleForTesting 5353 ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) { 5354 synchronized (mLock) { 5355 final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId); 5356 if (pkg == null) return null; 5357 5358 return pkg.findShortcutById(shortcutId); 5359 } 5360 } 5361 5362 @VisibleForTesting 5363 void updatePackageShortcutForTest(String packageName, String shortcutId, int userId, 5364 Consumer<ShortcutInfo> cb) { 5365 synchronized (mLock) { 5366 final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId); 5367 if (pkg == null) return; 5368 5369 pkg.mutateShortcut(shortcutId, null, cb); 5370 } 5371 } 5372 5373 @VisibleForTesting 5374 ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) { 5375 synchronized (mLock) { 5376 final ShortcutUser user = mUsers.get(userId); 5377 if (user == null) return null; 5378 5379 return user.getAllLaunchersForTest().get(PackageWithUser.of(userId, packageName)); 5380 } 5381 } 5382 5383 @VisibleForTesting 5384 ShortcutRequestPinProcessor getShortcutRequestPinProcessorForTest() { 5385 return mShortcutRequestPinProcessor; 5386 } 5387 5388 /** 5389 * Control whether {@link #verifyStates} should be performed. We always perform it during unit 5390 * tests. 5391 */ 5392 @VisibleForTesting 5393 boolean injectShouldPerformVerification() { 5394 return DEBUG; 5395 } 5396 5397 /** 5398 * Check various internal states and throws if there's any inconsistency. 5399 * This is normally only enabled during unit tests. 5400 */ 5401 final void verifyStates() { 5402 if (injectShouldPerformVerification()) { 5403 verifyStatesInner(); 5404 } 5405 } 5406 5407 private final void verifyStatesForce() { 5408 verifyStatesInner(); 5409 } 5410 5411 private void verifyStatesInner() { 5412 synchronized (mLock) { 5413 forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates)); 5414 } 5415 } 5416 5417 @VisibleForTesting 5418 void waitForBitmapSavesForTest() { 5419 synchronized (mLock) { 5420 mShortcutBitmapSaver.waitForAllSavesLocked(); 5421 } 5422 } 5423 5424 /** 5425 * This helper method does the following 3 tasks: 5426 * 5427 * 1- Combines the |changed| and |updated| shortcut lists, while removing duplicates. 5428 * 2- If a shortcut is deleted and added at once in the same operation, removes it from the 5429 * |removed| list. 5430 * 3- Reloads the final list to get the latest flags. 5431 */ 5432 private List<ShortcutInfo> prepareChangedShortcuts(ArraySet<String> changedIds, 5433 ArraySet<String> newIds, List<ShortcutInfo> deletedList, final ShortcutPackage ps) { 5434 if (ps == null) { 5435 // This can happen when package restore is not finished yet. 5436 return null; 5437 } 5438 if (CollectionUtils.isEmpty(changedIds) && CollectionUtils.isEmpty(newIds)) { 5439 return null; 5440 } 5441 5442 ArraySet<String> resultIds = new ArraySet<>(); 5443 if (!CollectionUtils.isEmpty(changedIds)) { 5444 resultIds.addAll(changedIds); 5445 } 5446 if (!CollectionUtils.isEmpty(newIds)) { 5447 resultIds.addAll(newIds); 5448 } 5449 5450 if (!CollectionUtils.isEmpty(deletedList)) { 5451 deletedList.removeIf((ShortcutInfo si) -> resultIds.contains(si.getId())); 5452 } 5453 5454 List<ShortcutInfo> result = new ArrayList<>(); 5455 ps.findAllByIds(result, resultIds, (ShortcutInfo si) -> resultIds.contains(si.getId()), 5456 ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); 5457 return result; 5458 } 5459 5460 private List<ShortcutInfo> prepareChangedShortcuts(List<ShortcutInfo> changedList, 5461 List<ShortcutInfo> newList, List<ShortcutInfo> deletedList, final ShortcutPackage ps) { 5462 ArraySet<String> changedIds = new ArraySet<>(); 5463 addShortcutIdsToSet(changedIds, changedList); 5464 5465 ArraySet<String> newIds = new ArraySet<>(); 5466 addShortcutIdsToSet(newIds, newList); 5467 5468 return prepareChangedShortcuts(changedIds, newIds, deletedList, ps); 5469 } 5470 5471 private void addShortcutIdsToSet(ArraySet<String> ids, List<ShortcutInfo> shortcuts) { 5472 if (CollectionUtils.isEmpty(shortcuts)) { 5473 return; 5474 } 5475 final int size = shortcuts.size(); 5476 for (int i = 0; i < size; i++) { 5477 ids.add(shortcuts.get(i).getId()); 5478 } 5479 } 5480 } 5481