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 verifyCaller(@onNull String packageName, @UserIdInt int userId)1667 private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) { 1668 Preconditions.checkStringNotEmpty(packageName, "packageName"); 1669 1670 if (isCallerSystem()) { 1671 return; // no check 1672 } 1673 1674 final int callingUid = injectBinderCallingUid(); 1675 1676 // Otherwise, make sure the arguments are valid. 1677 if (UserHandle.getUserId(callingUid) != userId) { 1678 throw new SecurityException("Invalid user-ID"); 1679 } 1680 if (injectGetPackageUid(packageName, userId) != callingUid) { 1681 throw new SecurityException("Calling package name mismatch"); 1682 } 1683 Preconditions.checkState(!isEphemeralApp(packageName, userId), 1684 "Ephemeral apps can't use ShortcutManager"); 1685 } 1686 verifyCaller(@onNull String packageName, @UserIdInt int userId, @NonNull AndroidFuture ret)1687 private boolean verifyCaller(@NonNull String packageName, @UserIdInt int userId, 1688 @NonNull AndroidFuture ret) { 1689 try { 1690 verifyCaller(packageName, userId); 1691 } catch (Exception e) { 1692 ret.completeExceptionally(e); 1693 return false; 1694 } 1695 return true; 1696 } 1697 verifyShortcutInfoPackage(String callerPackage, ShortcutInfo si)1698 private void verifyShortcutInfoPackage(String callerPackage, ShortcutInfo si) { 1699 if (si == null) { 1700 return; 1701 } 1702 if (!Objects.equals(callerPackage, si.getPackage())) { 1703 android.util.EventLog.writeEvent(0x534e4554, "109824443", -1, ""); 1704 throw new SecurityException("Shortcut package name mismatch"); 1705 } 1706 } 1707 verifyShortcutInfoPackages( String callerPackage, List<ShortcutInfo> list)1708 private void verifyShortcutInfoPackages( 1709 String callerPackage, List<ShortcutInfo> list) { 1710 final int size = list.size(); 1711 for (int i = 0; i < size; i++) { 1712 verifyShortcutInfoPackage(callerPackage, list.get(i)); 1713 } 1714 } 1715 1716 // Overridden in unit tests to execute r synchronously. injectPostToHandler(Runnable r)1717 void injectPostToHandler(Runnable r) { 1718 mHandler.post(r); 1719 } 1720 injectRunOnNewThread(Runnable r)1721 void injectRunOnNewThread(Runnable r) { 1722 new Thread(r).start(); 1723 } 1724 injectPostToHandlerIfAppSearch(Runnable r)1725 void injectPostToHandlerIfAppSearch(Runnable r) { 1726 // TODO: move to background thread when app search is enabled. 1727 r.run(); 1728 } 1729 1730 /** 1731 * @throws IllegalArgumentException if {@code numShortcuts} is bigger than 1732 * {@link #getMaxActivityShortcuts()}. 1733 */ enforceMaxActivityShortcuts(int numShortcuts)1734 void enforceMaxActivityShortcuts(int numShortcuts) { 1735 if (numShortcuts > mMaxShortcuts) { 1736 throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded"); 1737 } 1738 } 1739 1740 /** 1741 * Return the max number of dynamic + manifest shortcuts for each launcher icon. 1742 */ getMaxActivityShortcuts()1743 int getMaxActivityShortcuts() { 1744 return mMaxShortcuts; 1745 } 1746 1747 /** 1748 * - Sends a notification to LauncherApps 1749 * - Write to file 1750 */ packageShortcutsChanged(@onNull String packageName, @UserIdInt int userId, @Nullable final List<ShortcutInfo> changedShortcuts, @Nullable final List<ShortcutInfo> removedShortcuts)1751 void packageShortcutsChanged(@NonNull String packageName, @UserIdInt int userId, 1752 @Nullable final List<ShortcutInfo> changedShortcuts, 1753 @Nullable final List<ShortcutInfo> removedShortcuts) { 1754 notifyListeners(packageName, userId); 1755 notifyShortcutChangeCallbacks(packageName, userId, changedShortcuts, removedShortcuts); 1756 scheduleSaveUser(userId); 1757 } 1758 notifyListeners(@onNull String packageName, @UserIdInt int userId)1759 private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) { 1760 if (DEBUG) { 1761 Slog.d(TAG, String.format( 1762 "Shortcut changes: package=%s, user=%d", packageName, userId)); 1763 } 1764 injectPostToHandler(() -> { 1765 try { 1766 final ArrayList<ShortcutChangeListener> copy; 1767 synchronized (mLock) { 1768 if (!isUserUnlockedL(userId)) { 1769 return; 1770 } 1771 1772 copy = new ArrayList<>(mListeners); 1773 } 1774 // Note onShortcutChanged() needs to be called with the system service permissions. 1775 for (int i = copy.size() - 1; i >= 0; i--) { 1776 copy.get(i).onShortcutChanged(packageName, userId); 1777 } 1778 } catch (Exception ignore) { 1779 } 1780 }); 1781 } 1782 notifyShortcutChangeCallbacks(@onNull String packageName, @UserIdInt int userId, @Nullable final List<ShortcutInfo> changedShortcuts, @Nullable final List<ShortcutInfo> removedShortcuts)1783 private void notifyShortcutChangeCallbacks(@NonNull String packageName, @UserIdInt int userId, 1784 @Nullable final List<ShortcutInfo> changedShortcuts, 1785 @Nullable final List<ShortcutInfo> removedShortcuts) { 1786 final List<ShortcutInfo> changedList = removeNonKeyFields(changedShortcuts); 1787 final List<ShortcutInfo> removedList = removeNonKeyFields(removedShortcuts); 1788 1789 final UserHandle user = UserHandle.of(userId); 1790 injectPostToHandler(() -> { 1791 try { 1792 final ArrayList<LauncherApps.ShortcutChangeCallback> copy; 1793 synchronized (mLock) { 1794 if (!isUserUnlockedL(userId)) { 1795 return; 1796 } 1797 1798 copy = new ArrayList<>(mShortcutChangeCallbacks); 1799 } 1800 for (int i = copy.size() - 1; i >= 0; i--) { 1801 if (!CollectionUtils.isEmpty(changedList)) { 1802 copy.get(i).onShortcutsAddedOrUpdated(packageName, changedList, user); 1803 } 1804 if (!CollectionUtils.isEmpty(removedList)) { 1805 copy.get(i).onShortcutsRemoved(packageName, removedList, user); 1806 } 1807 } 1808 } catch (Exception ignore) { 1809 } 1810 }); 1811 } 1812 removeNonKeyFields(@ullable List<ShortcutInfo> shortcutInfos)1813 private List<ShortcutInfo> removeNonKeyFields(@Nullable List<ShortcutInfo> shortcutInfos) { 1814 if (CollectionUtils.isEmpty(shortcutInfos)) { 1815 return shortcutInfos; 1816 } 1817 1818 final int size = shortcutInfos.size(); 1819 List<ShortcutInfo> keyFieldOnlyShortcuts = new ArrayList<>(size); 1820 1821 for (int i = 0; i < size; i++) { 1822 final ShortcutInfo si = shortcutInfos.get(i); 1823 if (si.hasKeyFieldsOnly()) { 1824 keyFieldOnlyShortcuts.add(si); 1825 } else { 1826 keyFieldOnlyShortcuts.add(si.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO)); 1827 } 1828 } 1829 return keyFieldOnlyShortcuts; 1830 } 1831 1832 /** 1833 * Clean up / validate an incoming shortcut. 1834 * - Make sure all mandatory fields are set. 1835 * - Make sure the intent's extras are persistable, and them to set 1836 * {@link ShortcutInfo#mIntentPersistableExtrases}. Also clear its extras. 1837 * - Clear flags. 1838 */ fixUpIncomingShortcutInfo(@onNull ShortcutInfo shortcut, boolean forUpdate, boolean forPinRequest)1839 private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate, 1840 boolean forPinRequest) { 1841 if (shortcut.isReturnedByServer()) { 1842 Log.w(TAG, 1843 "Re-publishing ShortcutInfo returned by server is not supported." 1844 + " Some information such as icon may lost from shortcut."); 1845 } 1846 Objects.requireNonNull(shortcut, "Null shortcut detected"); 1847 if (shortcut.getActivity() != null) { 1848 Preconditions.checkState( 1849 shortcut.getPackage().equals(shortcut.getActivity().getPackageName()), 1850 "Cannot publish shortcut: activity " + shortcut.getActivity() + " does not" 1851 + " belong to package " + shortcut.getPackage()); 1852 Preconditions.checkState( 1853 injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()), 1854 "Cannot publish shortcut: activity " + shortcut.getActivity() + " is not" 1855 + " main activity"); 1856 } 1857 1858 if (!forUpdate) { 1859 shortcut.enforceMandatoryFields(/* forPinned= */ forPinRequest); 1860 if (!forPinRequest) { 1861 Preconditions.checkState(shortcut.getActivity() != null, 1862 "Cannot publish shortcut: target activity is not set"); 1863 } 1864 } 1865 if (shortcut.getIcon() != null) { 1866 ShortcutInfo.validateIcon(shortcut.getIcon()); 1867 } 1868 1869 shortcut.replaceFlags(shortcut.getFlags() & ShortcutInfo.FLAG_LONG_LIVED); 1870 } 1871 fixUpIncomingShortcutInfo(@onNull ShortcutInfo shortcut, boolean forUpdate)1872 private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) { 1873 fixUpIncomingShortcutInfo(shortcut, forUpdate, /*forPinRequest=*/ false); 1874 } 1875 validateShortcutForPinRequest(@onNull ShortcutInfo shortcut)1876 public void validateShortcutForPinRequest(@NonNull ShortcutInfo shortcut) { 1877 fixUpIncomingShortcutInfo(shortcut, /* forUpdate= */ false, /*forPinRequest=*/ true); 1878 } 1879 1880 /** 1881 * When a shortcut has no target activity, set the default one from the package. 1882 */ fillInDefaultActivity(List<ShortcutInfo> shortcuts)1883 private void fillInDefaultActivity(List<ShortcutInfo> shortcuts) { 1884 ComponentName defaultActivity = null; 1885 for (int i = shortcuts.size() - 1; i >= 0; i--) { 1886 final ShortcutInfo si = shortcuts.get(i); 1887 if (si.getActivity() == null) { 1888 if (defaultActivity == null) { 1889 defaultActivity = injectGetDefaultMainActivity( 1890 si.getPackage(), si.getUserId()); 1891 Preconditions.checkState(defaultActivity != null, 1892 "Launcher activity not found for package " + si.getPackage()); 1893 } 1894 si.setActivity(defaultActivity); 1895 } 1896 } 1897 } 1898 assignImplicitRanks(List<ShortcutInfo> shortcuts)1899 private void assignImplicitRanks(List<ShortcutInfo> shortcuts) { 1900 for (int i = shortcuts.size() - 1; i >= 0; i--) { 1901 shortcuts.get(i).setImplicitRank(i); 1902 } 1903 } 1904 setReturnedByServer(List<ShortcutInfo> shortcuts)1905 private List<ShortcutInfo> setReturnedByServer(List<ShortcutInfo> shortcuts) { 1906 for (int i = shortcuts.size() - 1; i >= 0; i--) { 1907 shortcuts.get(i).setReturnedByServer(); 1908 } 1909 return shortcuts; 1910 } 1911 1912 // === APIs === 1913 1914 @Override setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, @UserIdInt int userId)1915 public AndroidFuture setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, 1916 @UserIdInt int userId) { 1917 final AndroidFuture<Boolean> ret = new AndroidFuture<>(); 1918 if (!verifyCaller(packageName, userId, ret)) { 1919 return ret; 1920 } 1921 final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( 1922 injectBinderCallingPid(), injectBinderCallingUid()); 1923 injectPostToHandlerIfAppSearch(() -> { 1924 try { 1925 final List<ShortcutInfo> newShortcuts = 1926 (List<ShortcutInfo>) shortcutInfoList.getList(); 1927 verifyShortcutInfoPackages(packageName, newShortcuts); 1928 final int size = newShortcuts.size(); 1929 1930 List<ShortcutInfo> changedShortcuts = null; 1931 List<ShortcutInfo> removedShortcuts = null; 1932 1933 synchronized (mLock) { 1934 throwIfUserLockedL(userId); 1935 1936 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 1937 userId); 1938 1939 ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); 1940 ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); 1941 1942 fillInDefaultActivity(newShortcuts); 1943 1944 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET); 1945 1946 // Throttling. 1947 if (!ps.tryApiCall(unlimited)) { 1948 ret.complete(false); 1949 return; 1950 } 1951 1952 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 1953 ps.clearAllImplicitRanks(); 1954 assignImplicitRanks(newShortcuts); 1955 1956 for (int i = 0; i < size; i++) { 1957 fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false); 1958 } 1959 1960 ArrayList<ShortcutInfo> cachedOrPinned = new ArrayList<>(); 1961 ps.findAll(cachedOrPinned, 1962 AppSearchShortcutInfo.QUERY_IS_VISIBLE_CACHED_OR_PINNED, 1963 (ShortcutInfo si) -> si.isVisibleToPublisher() 1964 && si.isDynamic() && (si.isCached() || si.isPinned()), 1965 ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); 1966 1967 // First, remove all un-pinned and non-cached; dynamic shortcuts 1968 removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); 1969 1970 // Then, add/update all. We need to make sure to take over "pinned" flag. 1971 for (int i = 0; i < size; i++) { 1972 final ShortcutInfo newShortcut = newShortcuts.get(i); 1973 ps.addOrReplaceDynamicShortcut(newShortcut); 1974 } 1975 1976 // Lastly, adjust the ranks. 1977 ps.adjustRanks(); 1978 1979 changedShortcuts = prepareChangedShortcuts( 1980 cachedOrPinned, newShortcuts, removedShortcuts, ps); 1981 } 1982 1983 1984 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 1985 1986 verifyStates(); 1987 1988 ret.complete(true); 1989 } catch (Exception e) { 1990 ret.completeExceptionally(e); 1991 } 1992 }); 1993 return ret; 1994 } 1995 1996 @Override updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, @UserIdInt int userId)1997 public AndroidFuture updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, 1998 @UserIdInt int userId) { 1999 final AndroidFuture<Boolean> ret = new AndroidFuture<>(); 2000 if (!verifyCaller(packageName, userId, ret)) { 2001 return ret; 2002 } 2003 final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( 2004 injectBinderCallingPid(), injectBinderCallingUid()); 2005 injectPostToHandlerIfAppSearch(() -> { 2006 try { 2007 final List<ShortcutInfo> newShortcuts = 2008 (List<ShortcutInfo>) shortcutInfoList.getList(); 2009 verifyShortcutInfoPackages(packageName, newShortcuts); 2010 final int size = newShortcuts.size(); 2011 2012 final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1); 2013 2014 synchronized (mLock) { 2015 throwIfUserLockedL(userId); 2016 2017 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2018 userId); 2019 2020 ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); 2021 ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); 2022 2023 // For update, don't fill in the default activity. Having null activity means 2024 // "don't update the activity" here. 2025 2026 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE); 2027 2028 // Throttling. 2029 if (!ps.tryApiCall(unlimited)) { 2030 ret.complete(false); 2031 return; 2032 } 2033 2034 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 2035 ps.clearAllImplicitRanks(); 2036 assignImplicitRanks(newShortcuts); 2037 2038 for (int i = 0; i < size; i++) { 2039 final ShortcutInfo source = newShortcuts.get(i); 2040 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true); 2041 2042 ps.mutateShortcut(source.getId(), null, target -> { 2043 // Invisible shortcuts can't be updated. 2044 if (target == null || !target.isVisibleToPublisher()) { 2045 return; 2046 } 2047 2048 if (target.isEnabled() != source.isEnabled()) { 2049 Slog.w(TAG, "ShortcutInfo.enabled cannot be changed with" 2050 + " updateShortcuts()"); 2051 } 2052 2053 if (target.isLongLived() != source.isLongLived()) { 2054 Slog.w(TAG, 2055 "ShortcutInfo.longLived cannot be changed with" 2056 + " updateShortcuts()"); 2057 } 2058 2059 // When updating the rank, we need to insert between existing ranks, 2060 // so set this setRankChanged, and also copy the implicit rank fo 2061 // adjustRanks(). 2062 if (source.hasRank()) { 2063 target.setRankChanged(); 2064 target.setImplicitRank(source.getImplicitRank()); 2065 } 2066 2067 final boolean replacingIcon = (source.getIcon() != null); 2068 if (replacingIcon) { 2069 removeIconLocked(target); 2070 } 2071 2072 // Note copyNonNullFieldsFrom() does the "updatable with?" check too. 2073 target.copyNonNullFieldsFrom(source); 2074 target.setTimestamp(injectCurrentTimeMillis()); 2075 2076 if (replacingIcon) { 2077 saveIconAndFixUpShortcutLocked(target); 2078 } 2079 2080 // When we're updating any resource related fields, re-extract the res 2081 // names and the values. 2082 if (replacingIcon || source.hasStringResources()) { 2083 fixUpShortcutResourceNamesAndValues(target); 2084 } 2085 2086 changedShortcuts.add(target); 2087 }); 2088 } 2089 2090 // Lastly, adjust the ranks. 2091 ps.adjustRanks(); 2092 } 2093 packageShortcutsChanged(packageName, userId, 2094 changedShortcuts.isEmpty() ? null : changedShortcuts, null); 2095 2096 verifyStates(); 2097 2098 ret.complete(true); 2099 } catch (Exception e) { 2100 ret.completeExceptionally(e); 2101 } 2102 }); 2103 return ret; 2104 } 2105 2106 @Override addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, @UserIdInt int userId)2107 public AndroidFuture addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, 2108 @UserIdInt int userId) { 2109 final AndroidFuture<Boolean> ret = new AndroidFuture<>(); 2110 if (!verifyCaller(packageName, userId, ret)) { 2111 return ret; 2112 } 2113 final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( 2114 injectBinderCallingPid(), injectBinderCallingUid()); 2115 injectPostToHandlerIfAppSearch(() -> { 2116 try { 2117 final List<ShortcutInfo> newShortcuts = 2118 (List<ShortcutInfo>) shortcutInfoList.getList(); 2119 verifyShortcutInfoPackages(packageName, newShortcuts); 2120 final int size = newShortcuts.size(); 2121 2122 List<ShortcutInfo> changedShortcuts = null; 2123 2124 synchronized (mLock) { 2125 throwIfUserLockedL(userId); 2126 2127 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2128 userId); 2129 2130 ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); 2131 ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); 2132 2133 fillInDefaultActivity(newShortcuts); 2134 2135 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD); 2136 2137 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 2138 ps.clearAllImplicitRanks(); 2139 assignImplicitRanks(newShortcuts); 2140 2141 // Throttling. 2142 if (!ps.tryApiCall(unlimited)) { 2143 ret.complete(false); 2144 return; 2145 } 2146 for (int i = 0; i < size; i++) { 2147 final ShortcutInfo newShortcut = newShortcuts.get(i); 2148 2149 // Validate the shortcut. 2150 fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false); 2151 2152 // When ranks are changing, we need to insert between ranks, so set the 2153 // "rank changed" flag. 2154 newShortcut.setRankChanged(); 2155 2156 // Add it. 2157 ps.addOrReplaceDynamicShortcut(newShortcut); 2158 2159 if (changedShortcuts == null) { 2160 changedShortcuts = new ArrayList<>(1); 2161 } 2162 changedShortcuts.add(newShortcut); 2163 } 2164 2165 // Lastly, adjust the ranks. 2166 ps.adjustRanks(); 2167 } 2168 packageShortcutsChanged(packageName, userId, changedShortcuts, null); 2169 2170 verifyStates(); 2171 2172 ret.complete(true); 2173 } catch (Exception e) { 2174 ret.completeExceptionally(e); 2175 } 2176 }); 2177 return ret; 2178 } 2179 2180 @Override pushDynamicShortcut(String packageName, ShortcutInfo shortcut, @UserIdInt int userId)2181 public AndroidFuture pushDynamicShortcut(String packageName, ShortcutInfo shortcut, 2182 @UserIdInt int userId) { 2183 final AndroidFuture<Void> ret = new AndroidFuture<>(); 2184 if (!verifyCaller(packageName, userId, ret)) { 2185 return ret; 2186 } 2187 injectPostToHandlerIfAppSearch(() -> { 2188 try { 2189 verifyShortcutInfoPackage(packageName, shortcut); 2190 2191 List<ShortcutInfo> changedShortcuts = new ArrayList<>(); 2192 List<ShortcutInfo> removedShortcuts = null; 2193 2194 synchronized (mLock) { 2195 throwIfUserLockedL(userId); 2196 2197 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2198 userId); 2199 2200 ps.ensureNotImmutable(shortcut.getId(), /*ignoreInvisible=*/ true); 2201 fillInDefaultActivity(Arrays.asList(shortcut)); 2202 2203 if (!shortcut.hasRank()) { 2204 shortcut.setRank(0); 2205 } 2206 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 2207 ps.clearAllImplicitRanks(); 2208 shortcut.setImplicitRank(0); 2209 2210 // Validate the shortcut. 2211 fixUpIncomingShortcutInfo(shortcut, /* forUpdate= */ false); 2212 2213 // When ranks are changing, we need to insert between ranks, so set the 2214 // "rank changed" flag. 2215 shortcut.setRankChanged(); 2216 2217 // Push it. 2218 boolean deleted = ps.pushDynamicShortcut(shortcut, changedShortcuts); 2219 2220 if (deleted) { 2221 if (changedShortcuts.isEmpty()) { 2222 ret.complete(null); 2223 return; // Failed to push. 2224 } 2225 removedShortcuts = Collections.singletonList(changedShortcuts.get(0)); 2226 changedShortcuts.clear(); 2227 } 2228 changedShortcuts.add(shortcut); 2229 2230 // Lastly, adjust the ranks. 2231 ps.adjustRanks(); 2232 } 2233 2234 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 2235 2236 reportShortcutUsedInternal(packageName, shortcut.getId(), userId); 2237 2238 verifyStates(); 2239 2240 ret.complete(null); 2241 } catch (Exception e) { 2242 ret.completeExceptionally(e); 2243 } 2244 }); 2245 return ret; 2246 } 2247 2248 @Override requestPinShortcut(String packageName, ShortcutInfo shortcut, IntentSender resultIntent, int userId)2249 public AndroidFuture requestPinShortcut(String packageName, ShortcutInfo shortcut, 2250 IntentSender resultIntent, int userId) { 2251 final AndroidFuture<Boolean> ret = new AndroidFuture<>(); 2252 final int callingPid = injectBinderCallingPid(); 2253 final int callingUid = injectBinderCallingUid(); 2254 injectPostToHandlerIfAppSearch(() -> { 2255 try { 2256 ret.complete( 2257 requestPinItem(packageName, userId, shortcut, null, null, resultIntent, 2258 callingPid, callingUid)); 2259 } catch (Exception e) { 2260 ret.completeExceptionally(e); 2261 } 2262 }); 2263 return ret; 2264 } 2265 2266 @Override createShortcutResultIntent( String packageName, ShortcutInfo shortcut, int userId)2267 public AndroidFuture createShortcutResultIntent( 2268 String packageName, ShortcutInfo shortcut, int userId) throws RemoteException { 2269 final AndroidFuture<Intent> ret = new AndroidFuture<>(); 2270 if (!verifyCaller(packageName, userId, ret)) { 2271 return ret; 2272 } 2273 injectPostToHandlerIfAppSearch(() -> { 2274 try { 2275 Objects.requireNonNull(shortcut); 2276 Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); 2277 verifyShortcutInfoPackage(packageName, shortcut); 2278 final Intent intent; 2279 synchronized (mLock) { 2280 throwIfUserLockedL(userId); 2281 2282 // Send request to the launcher, if supported. 2283 intent = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, 2284 userId); 2285 } 2286 2287 verifyStates(); 2288 ret.complete(intent); 2289 } catch (Exception e) { 2290 ret.completeExceptionally(e); 2291 } 2292 }); 2293 return ret; 2294 } 2295 2296 /** 2297 * Handles {@link #requestPinShortcut} and {@link ShortcutServiceInternal#requestPinAppWidget}. 2298 * After validating the caller, it passes the request to {@link #mShortcutRequestPinProcessor}. 2299 * Either {@param shortcut} or {@param appWidget} should be non-null. 2300 */ requestPinItem(String callingPackage, int userId, ShortcutInfo shortcut, AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent)2301 private boolean requestPinItem(String callingPackage, int userId, ShortcutInfo shortcut, 2302 AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent) { 2303 return requestPinItem(callingPackage, userId, shortcut, appWidget, extras, resultIntent, 2304 injectBinderCallingPid(), injectBinderCallingUid()); 2305 } 2306 requestPinItem(String callingPackage, int userId, ShortcutInfo shortcut, AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent, int callingPid, int callingUid)2307 private boolean requestPinItem(String callingPackage, int userId, ShortcutInfo shortcut, 2308 AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent, 2309 int callingPid, int callingUid) { 2310 verifyCaller(callingPackage, userId); 2311 if (shortcut == null || !injectHasAccessShortcutsPermission( 2312 callingPid, callingUid)) { 2313 // Verify if caller is the shortcut owner, only if caller doesn't have ACCESS_SHORTCUTS. 2314 verifyShortcutInfoPackage(callingPackage, shortcut); 2315 } 2316 2317 final boolean ret; 2318 synchronized (mLock) { 2319 throwIfUserLockedL(userId); 2320 2321 Preconditions.checkState(isUidForegroundLocked(callingUid), 2322 "Calling application must have a foreground activity or a foreground service"); 2323 2324 // If it's a pin shortcut request, and there's already a shortcut with the same ID 2325 // that's not visible to the caller (i.e. restore-blocked; meaning it's pinned by 2326 // someone already), then we just replace the existing one with this new one, 2327 // and then proceed the rest of the process. 2328 if (shortcut != null) { 2329 final String shortcutPackage = shortcut.getPackage(); 2330 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked( 2331 shortcutPackage, userId); 2332 final String id = shortcut.getId(); 2333 if (ps.isShortcutExistsAndInvisibleToPublisher(id)) { 2334 2335 ps.updateInvisibleShortcutForPinRequestWith(shortcut); 2336 2337 packageShortcutsChanged(shortcutPackage, userId, 2338 Collections.singletonList(shortcut), null); 2339 } 2340 } 2341 2342 // Send request to the launcher, if supported. 2343 ret = mShortcutRequestPinProcessor.requestPinItemLocked(shortcut, appWidget, extras, 2344 userId, resultIntent); 2345 } 2346 2347 verifyStates(); 2348 2349 return ret; 2350 } 2351 2352 @Override disableShortcuts(String packageName, List shortcutIds, CharSequence disabledMessage, int disabledMessageResId, @UserIdInt int userId)2353 public AndroidFuture disableShortcuts(String packageName, List shortcutIds, 2354 CharSequence disabledMessage, int disabledMessageResId, @UserIdInt int userId) { 2355 final AndroidFuture<Void> ret = new AndroidFuture<>(); 2356 if (!verifyCaller(packageName, userId, ret)) { 2357 return ret; 2358 } 2359 injectPostToHandlerIfAppSearch(() -> { 2360 try { 2361 Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); 2362 List<ShortcutInfo> changedShortcuts = null; 2363 List<ShortcutInfo> removedShortcuts = null; 2364 2365 synchronized (mLock) { 2366 throwIfUserLockedL(userId); 2367 2368 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2369 userId); 2370 2371 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, 2372 /*ignoreInvisible=*/ true); 2373 2374 final String disabledMessageString = 2375 (disabledMessage == null) ? null : disabledMessage.toString(); 2376 2377 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 2378 final String id = Preconditions.checkStringNotEmpty( 2379 (String) shortcutIds.get(i)); 2380 if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { 2381 continue; 2382 } 2383 2384 final ShortcutInfo deleted = ps.disableWithId(id, 2385 disabledMessageString, disabledMessageResId, 2386 /* overrideImmutable=*/ false, /*ignoreInvisible=*/ true, 2387 ShortcutInfo.DISABLED_REASON_BY_APP); 2388 2389 if (deleted == null) { 2390 if (changedShortcuts == null) { 2391 changedShortcuts = new ArrayList<>(1); 2392 } 2393 changedShortcuts.add(ps.findShortcutById(id)); 2394 } else { 2395 if (removedShortcuts == null) { 2396 removedShortcuts = new ArrayList<>(1); 2397 } 2398 removedShortcuts.add(deleted); 2399 } 2400 } 2401 2402 // We may have removed dynamic shortcuts which may have left a gap, 2403 // so adjust the ranks. 2404 ps.adjustRanks(); 2405 } 2406 2407 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 2408 2409 verifyStates(); 2410 2411 ret.complete(null); 2412 } catch (Exception e) { 2413 ret.completeExceptionally(e); 2414 } 2415 }); 2416 return ret; 2417 } 2418 2419 @Override enableShortcuts( String packageName, List shortcutIds, @UserIdInt int userId)2420 public AndroidFuture enableShortcuts( 2421 String packageName, List shortcutIds, @UserIdInt int userId) { 2422 final AndroidFuture<Void> ret = new AndroidFuture<>(); 2423 if (!verifyCaller(packageName, userId, ret)) { 2424 return ret; 2425 } 2426 injectPostToHandlerIfAppSearch(() -> { 2427 try { 2428 Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); 2429 List<ShortcutInfo> changedShortcuts = null; 2430 2431 synchronized (mLock) { 2432 throwIfUserLockedL(userId); 2433 2434 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2435 userId); 2436 2437 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, 2438 /*ignoreInvisible=*/ true); 2439 2440 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 2441 final String id = Preconditions.checkStringNotEmpty( 2442 (String) shortcutIds.get(i)); 2443 if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { 2444 continue; 2445 } 2446 ps.enableWithId(id); 2447 2448 if (changedShortcuts == null) { 2449 changedShortcuts = new ArrayList<>(1); 2450 } 2451 changedShortcuts.add(ps.findShortcutById(id)); 2452 } 2453 } 2454 2455 packageShortcutsChanged(packageName, userId, changedShortcuts, null); 2456 2457 verifyStates(); 2458 2459 ret.complete(null); 2460 } catch (Exception e) { 2461 ret.completeExceptionally(e); 2462 } 2463 }); 2464 return ret; 2465 } 2466 2467 @Override removeDynamicShortcuts(String packageName, List shortcutIds, @UserIdInt int userId)2468 public AndroidFuture removeDynamicShortcuts(String packageName, List shortcutIds, 2469 @UserIdInt int userId) { 2470 final AndroidFuture<Void> ret = new AndroidFuture<>(); 2471 if (!verifyCaller(packageName, userId, ret)) { 2472 return ret; 2473 } 2474 injectPostToHandlerIfAppSearch(() -> { 2475 try { 2476 Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); 2477 List<ShortcutInfo> changedShortcuts = null; 2478 List<ShortcutInfo> removedShortcuts = null; 2479 2480 synchronized (mLock) { 2481 throwIfUserLockedL(userId); 2482 2483 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2484 userId); 2485 2486 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, 2487 /*ignoreInvisible=*/ true); 2488 2489 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 2490 final String id = Preconditions.checkStringNotEmpty( 2491 (String) shortcutIds.get(i)); 2492 if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { 2493 continue; 2494 } 2495 2496 ShortcutInfo removed = ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ 2497 true); 2498 if (removed == null) { 2499 if (changedShortcuts == null) { 2500 changedShortcuts = new ArrayList<>(1); 2501 } 2502 changedShortcuts.add(ps.findShortcutById(id)); 2503 } else { 2504 if (removedShortcuts == null) { 2505 removedShortcuts = new ArrayList<>(1); 2506 } 2507 removedShortcuts.add(removed); 2508 } 2509 } 2510 2511 // We may have removed dynamic shortcuts which may have left a gap, 2512 // so adjust the ranks. 2513 ps.adjustRanks(); 2514 } 2515 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 2516 2517 verifyStates(); 2518 2519 ret.complete(null); 2520 } catch (Exception e) { 2521 ret.completeExceptionally(e); 2522 } 2523 }); 2524 return ret; 2525 } 2526 2527 @Override removeAllDynamicShortcuts(String packageName, @UserIdInt int userId)2528 public AndroidFuture removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) { 2529 final AndroidFuture<Void> ret = new AndroidFuture<>(); 2530 if (!verifyCaller(packageName, userId, ret)) { 2531 return ret; 2532 } 2533 injectPostToHandlerIfAppSearch(() -> { 2534 try { 2535 List<ShortcutInfo> changedShortcuts = new ArrayList<>(); 2536 List<ShortcutInfo> removedShortcuts = null; 2537 2538 synchronized (mLock) { 2539 throwIfUserLockedL(userId); 2540 2541 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2542 userId); 2543 2544 // Dynamic shortcuts that are either cached or pinned will not get deleted. 2545 ps.findAll(changedShortcuts, 2546 AppSearchShortcutInfo.QUERY_IS_VISIBLE_CACHED_OR_PINNED, 2547 (ShortcutInfo si) -> si.isVisibleToPublisher() 2548 && si.isDynamic() && (si.isCached() || si.isPinned()), 2549 ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); 2550 2551 removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); 2552 changedShortcuts = prepareChangedShortcuts( 2553 changedShortcuts, null, removedShortcuts, ps); 2554 } 2555 2556 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 2557 2558 verifyStates(); 2559 2560 ret.complete(null); 2561 } catch (Exception e) { 2562 ret.completeExceptionally(e); 2563 } 2564 }); 2565 return ret; 2566 } 2567 2568 @Override removeLongLivedShortcuts(String packageName, List shortcutIds, @UserIdInt int userId)2569 public AndroidFuture removeLongLivedShortcuts(String packageName, List shortcutIds, 2570 @UserIdInt int userId) { 2571 final AndroidFuture<Void> ret = new AndroidFuture<>(); 2572 if (!verifyCaller(packageName, userId, ret)) { 2573 return ret; 2574 } 2575 injectPostToHandlerIfAppSearch(() -> { 2576 try { 2577 Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); 2578 List<ShortcutInfo> changedShortcuts = null; 2579 List<ShortcutInfo> removedShortcuts = null; 2580 2581 synchronized (mLock) { 2582 throwIfUserLockedL(userId); 2583 2584 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2585 userId); 2586 2587 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, 2588 /*ignoreInvisible=*/ true); 2589 2590 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 2591 final String id = Preconditions.checkStringNotEmpty( 2592 (String) shortcutIds.get(i)); 2593 if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { 2594 continue; 2595 } 2596 2597 ShortcutInfo removed = ps.deleteLongLivedWithId(id, /*ignoreInvisible=*/ 2598 true); 2599 if (removed != null) { 2600 if (removedShortcuts == null) { 2601 removedShortcuts = new ArrayList<>(1); 2602 } 2603 removedShortcuts.add(removed); 2604 } else { 2605 if (changedShortcuts == null) { 2606 changedShortcuts = new ArrayList<>(1); 2607 } 2608 changedShortcuts.add(ps.findShortcutById(id)); 2609 } 2610 } 2611 2612 // We may have removed dynamic shortcuts which may have left a gap, 2613 // so adjust the ranks. 2614 ps.adjustRanks(); 2615 } 2616 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 2617 2618 verifyStates(); 2619 2620 ret.complete(null); 2621 } catch (Exception e) { 2622 ret.completeExceptionally(e); 2623 } 2624 }); 2625 return ret; 2626 } 2627 2628 @Override getShortcuts(String packageName, @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId)2629 public AndroidFuture<ParceledListSlice> getShortcuts(String packageName, 2630 @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId) { 2631 final AndroidFuture<ParceledListSlice> ret = new AndroidFuture<>(); 2632 if (!verifyCaller(packageName, userId, ret)) { 2633 return ret; 2634 } 2635 injectPostToHandlerIfAppSearch(() -> { 2636 try { 2637 synchronized (mLock) { 2638 throwIfUserLockedL(userId); 2639 2640 final boolean matchDynamic = 2641 (matchFlags & ShortcutManager.FLAG_MATCH_DYNAMIC) != 0; 2642 final boolean matchPinned = 2643 (matchFlags & ShortcutManager.FLAG_MATCH_PINNED) != 0; 2644 final boolean matchManifest = 2645 (matchFlags & ShortcutManager.FLAG_MATCH_MANIFEST) != 0; 2646 final boolean matchCached = 2647 (matchFlags & ShortcutManager.FLAG_MATCH_CACHED) != 0; 2648 2649 final int shortcutFlags = (matchDynamic ? ShortcutInfo.FLAG_DYNAMIC : 0) 2650 | (matchPinned ? ShortcutInfo.FLAG_PINNED : 0) 2651 | (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0) 2652 | (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0); 2653 2654 final String query = AppSearchShortcutInfo.QUERY_IS_VISIBLE_TO_PUBLISHER + " " 2655 + createQuery(matchDynamic, matchPinned, matchManifest, matchCached); 2656 2657 ret.complete(getShortcutsWithQueryLocked( 2658 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, query, 2659 (ShortcutInfo si) -> 2660 si.isVisibleToPublisher() 2661 && (si.getFlags() & shortcutFlags) != 0)); 2662 } 2663 } catch (Exception e) { 2664 ret.completeExceptionally(e); 2665 } 2666 }); 2667 return ret; 2668 } 2669 2670 @Override getShareTargets( String packageName, IntentFilter filter, @UserIdInt int userId)2671 public AndroidFuture<ParceledListSlice> getShareTargets( 2672 String packageName, IntentFilter filter, @UserIdInt int userId) { 2673 final AndroidFuture<ParceledListSlice> ret = new AndroidFuture<>(); 2674 try { 2675 Preconditions.checkStringNotEmpty(packageName, "packageName"); 2676 Objects.requireNonNull(filter, "intentFilter"); 2677 2678 verifyCaller(packageName, userId); 2679 enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS, 2680 "getShareTargets"); 2681 } catch (Exception e) { 2682 ret.completeExceptionally(e); 2683 return ret; 2684 } 2685 injectPostToHandlerIfAppSearch(() -> { 2686 try { 2687 synchronized (mLock) { 2688 throwIfUserLockedL(userId); 2689 2690 final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = 2691 new ArrayList<>(); 2692 2693 final ShortcutUser user = getUserShortcutsLocked(userId); 2694 user.forAllPackages( 2695 p -> shortcutInfoList.addAll(p.getMatchingShareTargets(filter))); 2696 2697 ret.complete(new ParceledListSlice<>(shortcutInfoList)); 2698 } 2699 } catch (Exception e) { 2700 ret.completeExceptionally(e); 2701 } 2702 }); 2703 return ret; 2704 } 2705 2706 @Override hasShareTargets(String packageName, String packageToCheck, @UserIdInt int userId)2707 public boolean hasShareTargets(String packageName, String packageToCheck, 2708 @UserIdInt int userId) { 2709 verifyCaller(packageName, userId); 2710 enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS, 2711 "hasShareTargets"); 2712 2713 synchronized (mLock) { 2714 throwIfUserLockedL(userId); 2715 2716 return getPackageShortcutsLocked(packageToCheck, userId).hasShareTargets(); 2717 } 2718 } 2719 isSharingShortcut(int callingUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId, @NonNull IntentFilter filter)2720 public boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage, 2721 @NonNull String packageName, @NonNull String shortcutId, int userId, 2722 @NonNull IntentFilter filter) { 2723 verifyCaller(callingPackage, callingUserId); 2724 enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS, 2725 "isSharingShortcut"); 2726 2727 synchronized (mLock) { 2728 throwIfUserLockedL(userId); 2729 throwIfUserLockedL(callingUserId); 2730 2731 final List<ShortcutManager.ShareShortcutInfo> matchedTargets = 2732 getPackageShortcutsLocked(packageName, userId) 2733 .getMatchingShareTargets(filter); 2734 final int matchedSize = matchedTargets.size(); 2735 for (int i = 0; i < matchedSize; i++) { 2736 if (matchedTargets.get(i).getShortcutInfo().getId().equals(shortcutId)) { 2737 return true; 2738 } 2739 } 2740 } 2741 return false; 2742 } 2743 2744 @GuardedBy("mLock") getShortcutsWithQueryLocked(@onNull String packageName, @UserIdInt int userId, int cloneFlags, @NonNull final String query, @NonNull Predicate<ShortcutInfo> filter)2745 private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName, 2746 @UserIdInt int userId, int cloneFlags, @NonNull final String query, 2747 @NonNull Predicate<ShortcutInfo> filter) { 2748 2749 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 2750 2751 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2752 ps.findAll(ret, query, filter, cloneFlags); 2753 return new ParceledListSlice<>(setReturnedByServer(ret)); 2754 } 2755 2756 @Override getMaxShortcutCountPerActivity(String packageName, @UserIdInt int userId)2757 public int getMaxShortcutCountPerActivity(String packageName, @UserIdInt int userId) 2758 throws RemoteException { 2759 verifyCaller(packageName, userId); 2760 2761 return mMaxShortcuts; 2762 } 2763 2764 @Override getRemainingCallCount(String packageName, @UserIdInt int userId)2765 public int getRemainingCallCount(String packageName, @UserIdInt int userId) { 2766 verifyCaller(packageName, userId); 2767 2768 final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( 2769 injectBinderCallingPid(), injectBinderCallingUid()); 2770 2771 synchronized (mLock) { 2772 throwIfUserLockedL(userId); 2773 2774 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2775 return mMaxUpdatesPerInterval - ps.getApiCallCount(unlimited); 2776 } 2777 } 2778 2779 @Override getRateLimitResetTime(String packageName, @UserIdInt int userId)2780 public long getRateLimitResetTime(String packageName, @UserIdInt int userId) { 2781 verifyCaller(packageName, userId); 2782 2783 synchronized (mLock) { 2784 throwIfUserLockedL(userId); 2785 2786 return getNextResetTimeLocked(); 2787 } 2788 } 2789 2790 @Override getIconMaxDimensions(String packageName, int userId)2791 public int getIconMaxDimensions(String packageName, int userId) { 2792 verifyCaller(packageName, userId); 2793 2794 synchronized (mLock) { 2795 return mMaxIconDimension; 2796 } 2797 } 2798 2799 @Override reportShortcutUsed(String packageName, String shortcutId, int userId)2800 public AndroidFuture reportShortcutUsed(String packageName, String shortcutId, int userId) { 2801 final AndroidFuture<Boolean> ret = new AndroidFuture<>(); 2802 if (!verifyCaller(packageName, userId, ret)) { 2803 return ret; 2804 } 2805 injectPostToHandlerIfAppSearch(() -> { 2806 try { 2807 Objects.requireNonNull(shortcutId); 2808 2809 if (DEBUG) { 2810 Slog.d(TAG, String.format( 2811 "reportShortcutUsed: Shortcut %s package %s used on user %d", 2812 shortcutId, packageName, userId)); 2813 } 2814 2815 synchronized (mLock) { 2816 throwIfUserLockedL(userId); 2817 2818 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, 2819 userId); 2820 2821 if (ps.findShortcutById(shortcutId) == null) { 2822 Log.w(TAG, String.format( 2823 "reportShortcutUsed: package %s doesn't have shortcut %s", 2824 packageName, shortcutId)); 2825 ret.complete(false); 2826 return; 2827 } 2828 } 2829 2830 reportShortcutUsedInternal(packageName, shortcutId, userId); 2831 ret.complete(true); 2832 } catch (Exception e) { 2833 ret.completeExceptionally(e); 2834 } 2835 }); 2836 return ret; 2837 } 2838 reportShortcutUsedInternal(String packageName, String shortcutId, int userId)2839 private void reportShortcutUsedInternal(String packageName, String shortcutId, int userId) { 2840 final long token = injectClearCallingIdentity(); 2841 try { 2842 mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId); 2843 } finally { 2844 injectRestoreCallingIdentity(token); 2845 } 2846 } 2847 2848 @Override isRequestPinItemSupported(int callingUserId, int requestType)2849 public boolean isRequestPinItemSupported(int callingUserId, int requestType) { 2850 final long token = injectClearCallingIdentity(); 2851 try { 2852 return mShortcutRequestPinProcessor 2853 .isRequestPinItemSupported(callingUserId, requestType); 2854 } finally { 2855 injectRestoreCallingIdentity(token); 2856 } 2857 } 2858 2859 /** 2860 * Reset all throttling, for developer options and command line. Only system/shell can call 2861 * it. 2862 */ 2863 @Override resetThrottling()2864 public void resetThrottling() { 2865 enforceSystemOrShell(); 2866 2867 resetThrottlingInner(getCallingUserId()); 2868 } 2869 resetThrottlingInner(@serIdInt int userId)2870 void resetThrottlingInner(@UserIdInt int userId) { 2871 synchronized (mLock) { 2872 if (!isUserUnlockedL(userId)) { 2873 Log.w(TAG, "User " + userId + " is locked or not running"); 2874 return; 2875 } 2876 2877 getUserShortcutsLocked(userId).resetThrottling(); 2878 } 2879 scheduleSaveUser(userId); 2880 Slog.i(TAG, "ShortcutManager: throttling counter reset for user " + userId); 2881 } 2882 resetAllThrottlingInner()2883 void resetAllThrottlingInner() { 2884 synchronized (mLock) { 2885 mRawLastResetTime = injectCurrentTimeMillis(); 2886 } 2887 scheduleSaveBaseState(); 2888 Slog.i(TAG, "ShortcutManager: throttling counter reset for all users"); 2889 } 2890 2891 @Override onApplicationActive(String packageName, int userId)2892 public AndroidFuture onApplicationActive(String packageName, int userId) { 2893 final AndroidFuture<Void> ret = new AndroidFuture<>(); 2894 if (DEBUG) { 2895 Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId); 2896 } 2897 try { 2898 enforceResetThrottlingPermission(); 2899 } catch (Exception e) { 2900 ret.completeExceptionally(e); 2901 return ret; 2902 } 2903 injectPostToHandlerIfAppSearch(() -> { 2904 try { 2905 synchronized (mLock) { 2906 if (!isUserUnlockedL(userId)) { 2907 // This is called by system UI, so no need to throw. Just ignore. 2908 ret.complete(null); 2909 return; 2910 } 2911 2912 getPackageShortcutsLocked(packageName, userId) 2913 .resetRateLimitingForCommandLineNoSaving(); 2914 saveUserLocked(userId); 2915 } 2916 ret.complete(null); 2917 } catch (Exception e) { 2918 ret.completeExceptionally(e); 2919 } 2920 }); 2921 return ret; 2922 } 2923 2924 // We override this method in unit tests to do a simpler check. hasShortcutHostPermission(@onNull String callingPackage, int userId, int callingPid, int callingUid)2925 boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId, 2926 int callingPid, int callingUid) { 2927 if (canSeeAnyPinnedShortcut(callingPackage, userId, callingPid, callingUid)) { 2928 return true; 2929 } 2930 final long start = getStatStartTime(); 2931 try { 2932 return hasShortcutHostPermissionInner(callingPackage, userId); 2933 } finally { 2934 logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start); 2935 } 2936 } 2937 canSeeAnyPinnedShortcut(@onNull String callingPackage, int userId, int callingPid, int callingUid)2938 boolean canSeeAnyPinnedShortcut(@NonNull String callingPackage, int userId, 2939 int callingPid, int callingUid) { 2940 if (injectHasAccessShortcutsPermission(callingPid, callingUid)) { 2941 return true; 2942 } 2943 synchronized (mNonPersistentUsersLock) { 2944 return getNonPersistentUserLocked(userId).hasHostPackage(callingPackage); 2945 } 2946 } 2947 2948 /** 2949 * Returns true if the caller has the "ACCESS_SHORTCUTS" permission. 2950 */ 2951 @VisibleForTesting injectHasAccessShortcutsPermission(int callingPid, int callingUid)2952 boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) { 2953 return mContext.checkPermission(android.Manifest.permission.ACCESS_SHORTCUTS, 2954 callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; 2955 } 2956 2957 /** 2958 * Returns true if the caller has the "UNLIMITED_SHORTCUTS_API_CALLS" permission. 2959 */ 2960 @VisibleForTesting injectHasUnlimitedShortcutsApiCallsPermission(int callingPid, int callingUid)2961 boolean injectHasUnlimitedShortcutsApiCallsPermission(int callingPid, int callingUid) { 2962 return mContext.checkPermission(permission.UNLIMITED_SHORTCUTS_API_CALLS, 2963 callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; 2964 } 2965 2966 // This method is extracted so we can directly call this method from unit tests, 2967 // even when hasShortcutPermission() is overridden. 2968 @VisibleForTesting hasShortcutHostPermissionInner(@onNull String packageName, int userId)2969 boolean hasShortcutHostPermissionInner(@NonNull String packageName, int userId) { 2970 synchronized (mLock) { 2971 throwIfUserLockedL(userId); 2972 2973 final String defaultLauncher = getDefaultLauncher(userId); 2974 2975 if (defaultLauncher != null) { 2976 if (DEBUG) { 2977 Slog.v(TAG, "Detected launcher: " + defaultLauncher + " user: " + userId); 2978 } 2979 return defaultLauncher.equals(packageName); 2980 } else { 2981 return false; 2982 } 2983 } 2984 } 2985 2986 @Nullable getDefaultLauncher(@serIdInt int userId)2987 String getDefaultLauncher(@UserIdInt int userId) { 2988 final long start = getStatStartTime(); 2989 final long token = injectClearCallingIdentity(); 2990 try { 2991 synchronized (mLock) { 2992 throwIfUserLockedL(userId); 2993 2994 final ShortcutUser user = getUserShortcutsLocked(userId); 2995 String cachedLauncher = user.getCachedLauncher(); 2996 if (cachedLauncher != null) { 2997 return cachedLauncher; 2998 } 2999 3000 // Default launcher from role manager. 3001 final long startGetHomeRoleHoldersAsUser = getStatStartTime(); 3002 final String defaultLauncher = injectGetHomeRoleHolderAsUser( 3003 getParentOrSelfUserId(userId)); 3004 logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeRoleHoldersAsUser); 3005 3006 if (defaultLauncher != null) { 3007 if (DEBUG) { 3008 Slog.v(TAG, "Default launcher from RoleManager: " + defaultLauncher 3009 + " user: " + userId); 3010 } 3011 user.setCachedLauncher(defaultLauncher); 3012 } else { 3013 Slog.e(TAG, "Default launcher not found." + " user: " + userId); 3014 } 3015 3016 return defaultLauncher; 3017 } 3018 } finally { 3019 injectRestoreCallingIdentity(token); 3020 logDurationStat(Stats.GET_DEFAULT_LAUNCHER, start); 3021 } 3022 } 3023 setShortcutHostPackage(@onNull String type, @Nullable String packageName, int userId)3024 public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName, 3025 int userId) { 3026 synchronized (mNonPersistentUsersLock) { 3027 getNonPersistentUserLocked(userId).setShortcutHostPackage(type, packageName); 3028 } 3029 } 3030 3031 // === House keeping === 3032 cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId, boolean appStillExists)3033 private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId, 3034 boolean appStillExists) { 3035 synchronized (mLock) { 3036 forEachLoadedUserLocked(user -> 3037 cleanUpPackageLocked(packageName, user.getUserId(), packageUserId, 3038 appStillExists)); 3039 } 3040 } 3041 createQuery(final boolean matchDynamic, final boolean matchPinned, final boolean matchManifest, final boolean matchCached)3042 private String createQuery(final boolean matchDynamic, final boolean matchPinned, 3043 final boolean matchManifest, final boolean matchCached) { 3044 3045 final List<String> queries = new ArrayList<>(1); 3046 if (matchDynamic) { 3047 queries.add(AppSearchShortcutInfo.QUERY_IS_DYNAMIC); 3048 } 3049 if (matchPinned) { 3050 queries.add(AppSearchShortcutInfo.QUERY_IS_PINNED); 3051 } 3052 if (matchManifest) { 3053 queries.add(AppSearchShortcutInfo.QUERY_IS_MANIFEST); 3054 } 3055 if (matchCached) { 3056 queries.add(AppSearchShortcutInfo.QUERY_IS_CACHED); 3057 } 3058 if (queries.isEmpty()) { 3059 return ""; 3060 } 3061 return "(" + String.join(" OR ", queries) + ")"; 3062 } 3063 3064 /** 3065 * Remove all the information associated with a package. This will really remove all the 3066 * information, including the restore information (i.e. it'll remove packages even if they're 3067 * shadow). 3068 * 3069 * This is called when an app is uninstalled, or an app gets "clear data"ed. 3070 */ 3071 @GuardedBy("mLock") 3072 @VisibleForTesting cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId, boolean appStillExists)3073 void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId, 3074 boolean appStillExists) { 3075 final boolean wasUserLoaded = isUserLoadedLocked(owningUserId); 3076 3077 final ShortcutUser user = getUserShortcutsLocked(owningUserId); 3078 boolean doNotify = false; 3079 3080 // First, remove the package from the package list (if the package is a publisher). 3081 if (packageUserId == owningUserId) { 3082 if (user.removePackage(packageName) != null) { 3083 doNotify = true; 3084 } 3085 } 3086 3087 // Also remove from the launcher list (if the package is a launcher). 3088 user.removeLauncher(packageUserId, packageName); 3089 3090 // Then remove pinned shortcuts from all launchers. 3091 user.forAllLaunchers(l -> l.cleanUpPackage(packageName, packageUserId)); 3092 3093 // Now there may be orphan shortcuts because we removed pinned shortcuts at the previous 3094 // step. Remove them too. 3095 user.forAllPackages(p -> p.refreshPinnedFlags()); 3096 3097 scheduleSaveUser(owningUserId); 3098 3099 if (doNotify) { 3100 notifyListeners(packageName, owningUserId); 3101 } 3102 3103 // If the app still exists (i.e. data cleared), we need to re-publish manifest shortcuts. 3104 if (appStillExists && (packageUserId == owningUserId)) { 3105 // This will do the notification and save when needed, so do it after the above 3106 // notifyListeners. 3107 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 3108 } 3109 3110 if (!wasUserLoaded) { 3111 // Note this will execute the scheduled save. 3112 unloadUserLocked(owningUserId); 3113 } 3114 } 3115 3116 /** 3117 * Entry point from {@link LauncherApps}. 3118 */ 3119 private class LocalService extends ShortcutServiceInternal { 3120 3121 @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)3122 public List<ShortcutInfo> getShortcuts(int launcherUserId, 3123 @NonNull String callingPackage, long changedSince, 3124 @Nullable String packageName, @Nullable List<String> shortcutIds, 3125 @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName, 3126 int queryFlags, int userId, int callingPid, int callingUid) { 3127 if (DEBUG_REBOOT) { 3128 Slog.d(TAG, "Getting shortcuts for launcher= " + callingPackage 3129 + "user=" + userId + " pkg=" + packageName); 3130 } 3131 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 3132 3133 int flags = ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER; 3134 if ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0) { 3135 flags = ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO; 3136 } else if ((queryFlags & ShortcutQuery.FLAG_GET_PERSONS_DATA) != 0) { 3137 flags &= ~ShortcutInfo.CLONE_REMOVE_PERSON; 3138 } 3139 final int cloneFlag = flags; 3140 3141 if (packageName == null) { 3142 shortcutIds = null; // LauncherAppsService already threw for it though. 3143 } 3144 3145 synchronized (mLock) { 3146 throwIfUserLockedL(userId); 3147 throwIfUserLockedL(launcherUserId); 3148 3149 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3150 .attemptToRestoreIfNeededAndSave(); 3151 3152 if (packageName != null) { 3153 getShortcutsInnerLocked(launcherUserId, 3154 callingPackage, packageName, shortcutIds, locusIds, changedSince, 3155 componentName, queryFlags, userId, ret, cloneFlag, 3156 callingPid, callingUid); 3157 } else { 3158 final List<String> shortcutIdsF = shortcutIds; 3159 final List<LocusId> locusIdsF = locusIds; 3160 getUserShortcutsLocked(userId).forAllPackages(p -> { 3161 getShortcutsInnerLocked(launcherUserId, 3162 callingPackage, p.getPackageName(), shortcutIdsF, locusIdsF, 3163 changedSince, componentName, queryFlags, userId, ret, cloneFlag, 3164 callingPid, callingUid); 3165 }); 3166 } 3167 } 3168 return setReturnedByServer(ret); 3169 } 3170 3171 @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)3172 private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage, 3173 @Nullable String packageName, @Nullable List<String> shortcutIds, 3174 @Nullable List<LocusId> locusIds, long changedSince, 3175 @Nullable ComponentName componentName, int queryFlags, 3176 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag, 3177 int callingPid, int callingUid) { 3178 final ArraySet<String> ids = shortcutIds == null ? null 3179 : new ArraySet<>(shortcutIds); 3180 3181 final ShortcutUser user = getUserShortcutsLocked(userId); 3182 final ShortcutPackage p = user.getPackageShortcutsIfExists(packageName); 3183 if (p == null) { 3184 return; // No need to instantiate ShortcutPackage. 3185 } 3186 3187 final boolean canAccessAllShortcuts = 3188 canSeeAnyPinnedShortcut(callingPackage, launcherUserId, callingPid, callingUid); 3189 3190 final boolean getPinnedByAnyLauncher = 3191 canAccessAllShortcuts && 3192 ((queryFlags & ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER) != 0); 3193 queryFlags |= (getPinnedByAnyLauncher ? ShortcutQuery.FLAG_MATCH_PINNED : 0); 3194 3195 final boolean matchPinnedOnly = 3196 ((queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0) 3197 && ((queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) == 0) 3198 && ((queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) == 0) 3199 && ((queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) == 0); 3200 3201 final Predicate<ShortcutInfo> filter = getFilterFromQuery(ids, locusIds, changedSince, 3202 componentName, queryFlags, getPinnedByAnyLauncher); 3203 if (matchPinnedOnly) { 3204 p.findAllPinned(ret, filter, cloneFlag, callingPackage, launcherUserId, 3205 getPinnedByAnyLauncher); 3206 } else if (ids != null && !ids.isEmpty()) { 3207 p.findAllByIds(ret, ids, filter, cloneFlag, callingPackage, launcherUserId, 3208 getPinnedByAnyLauncher); 3209 } else { 3210 final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0; 3211 final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0; 3212 final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0; 3213 final boolean matchCached = (queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) != 0; 3214 p.findAll(ret, createQuery(matchDynamic, matchPinned, matchManifest, matchCached), 3215 filter, cloneFlag, callingPackage, launcherUserId, getPinnedByAnyLauncher); 3216 } 3217 } 3218 getFilterFromQuery(@ullable ArraySet<String> ids, @Nullable List<LocusId> locusIds, long changedSince, @Nullable ComponentName componentName, int queryFlags, boolean getPinnedByAnyLauncher)3219 private Predicate<ShortcutInfo> getFilterFromQuery(@Nullable ArraySet<String> ids, 3220 @Nullable List<LocusId> locusIds, long changedSince, 3221 @Nullable ComponentName componentName, int queryFlags, 3222 boolean getPinnedByAnyLauncher) { 3223 final ArraySet<LocusId> locIds = locusIds == null ? null 3224 : new ArraySet<>(locusIds); 3225 3226 final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0; 3227 final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0; 3228 final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0; 3229 final boolean matchCached = (queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) != 0; 3230 return si -> { 3231 if (si.getLastChangedTimestamp() < changedSince) { 3232 return false; 3233 } 3234 if (ids != null && !ids.contains(si.getId())) { 3235 return false; 3236 } 3237 if (locIds != null && !locIds.contains(si.getLocusId())) { 3238 return false; 3239 } 3240 if (componentName != null) { 3241 if (si.getActivity() != null 3242 && !si.getActivity().equals(componentName)) { 3243 return false; 3244 } 3245 } 3246 if (matchDynamic && si.isDynamic()) { 3247 return true; 3248 } 3249 if ((matchPinned || getPinnedByAnyLauncher) && si.isPinned()) { 3250 return true; 3251 } 3252 if (matchManifest && si.isDeclaredInManifest()) { 3253 return true; 3254 } 3255 if (matchCached && si.isCached()) { 3256 return true; 3257 } 3258 return false; 3259 }; 3260 } 3261 3262 @Override isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)3263 public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, 3264 @NonNull String packageName, @NonNull String shortcutId, int userId) { 3265 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3266 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 3267 3268 synchronized (mLock) { 3269 throwIfUserLockedL(userId); 3270 throwIfUserLockedL(launcherUserId); 3271 3272 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3273 .attemptToRestoreIfNeededAndSave(); 3274 3275 final ShortcutInfo si = getShortcutInfoLocked( 3276 launcherUserId, callingPackage, packageName, shortcutId, userId, 3277 /*getPinnedByAnyLauncher=*/ false); 3278 return si != null && si.isPinned(); 3279 } 3280 } 3281 3282 @GuardedBy("ShortcutService.this.mLock") getShortcutInfoLocked( int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId, boolean getPinnedByAnyLauncher)3283 private ShortcutInfo getShortcutInfoLocked( 3284 int launcherUserId, @NonNull String callingPackage, 3285 @NonNull String packageName, @NonNull String shortcutId, int userId, 3286 boolean getPinnedByAnyLauncher) { 3287 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3288 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 3289 3290 throwIfUserLockedL(userId); 3291 throwIfUserLockedL(launcherUserId); 3292 3293 final ShortcutPackage p = getUserShortcutsLocked(userId) 3294 .getPackageShortcutsIfExists(packageName); 3295 if (p == null) { 3296 return null; 3297 } 3298 3299 final ArrayList<ShortcutInfo> list = new ArrayList<>(1); 3300 p.findAllByIds(list, Collections.singletonList(shortcutId), 3301 (ShortcutInfo si) -> shortcutId.equals(si.getId()), 3302 /* clone flags=*/ 0, callingPackage, launcherUserId, getPinnedByAnyLauncher); 3303 return list.size() == 0 ? null : list.get(0); 3304 } 3305 3306 @Override pinShortcuts(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull List<String> shortcutIds, int userId)3307 public void pinShortcuts(int launcherUserId, 3308 @NonNull String callingPackage, @NonNull String packageName, 3309 @NonNull List<String> shortcutIds, int userId) { 3310 // Calling permission must be checked by LauncherAppsImpl. 3311 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3312 Objects.requireNonNull(shortcutIds, "shortcutIds"); 3313 3314 List<ShortcutInfo> changedShortcuts = null; 3315 List<ShortcutInfo> removedShortcuts = null; 3316 3317 synchronized (mLock) { 3318 throwIfUserLockedL(userId); 3319 throwIfUserLockedL(launcherUserId); 3320 3321 final ShortcutLauncher launcher = 3322 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId); 3323 launcher.attemptToRestoreIfNeededAndSave(); 3324 3325 final ShortcutPackage sp = getUserShortcutsLocked(userId) 3326 .getPackageShortcutsIfExists(packageName); 3327 if (sp != null) { 3328 // List the shortcuts that are pinned only, these will get removed. 3329 removedShortcuts = new ArrayList<>(); 3330 sp.findAll(removedShortcuts, AppSearchShortcutInfo.QUERY_IS_VISIBLE_PINNED_ONLY, 3331 (ShortcutInfo si) -> si.isVisibleToPublisher() 3332 && si.isPinned() && !si.isCached() && !si.isDynamic() 3333 && !si.isDeclaredInManifest(), 3334 ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO, 3335 callingPackage, launcherUserId, false); 3336 } 3337 // Get list of shortcuts that will get unpinned. 3338 ArraySet<String> oldPinnedIds = launcher.getPinnedShortcutIds(packageName, userId); 3339 3340 launcher.pinShortcuts(userId, packageName, shortcutIds, /*forPinRequest=*/ false); 3341 3342 if (oldPinnedIds != null && removedShortcuts != null) { 3343 for (int i = 0; i < removedShortcuts.size(); i++) { 3344 oldPinnedIds.remove(removedShortcuts.get(i).getId()); 3345 } 3346 } 3347 changedShortcuts = prepareChangedShortcuts( 3348 oldPinnedIds, new ArraySet<>(shortcutIds), removedShortcuts, sp); 3349 } 3350 3351 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 3352 3353 verifyStates(); 3354 } 3355 3356 @Override cacheShortcuts(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull List<String> shortcutIds, int userId, int cacheFlags)3357 public void cacheShortcuts(int launcherUserId, 3358 @NonNull String callingPackage, @NonNull String packageName, 3359 @NonNull List<String> shortcutIds, int userId, int cacheFlags) { 3360 updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds, 3361 userId, cacheFlags, /* doCache= */ true); 3362 } 3363 3364 @Override uncacheShortcuts(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull List<String> shortcutIds, int userId, int cacheFlags)3365 public void uncacheShortcuts(int launcherUserId, 3366 @NonNull String callingPackage, @NonNull String packageName, 3367 @NonNull List<String> shortcutIds, int userId, int cacheFlags) { 3368 updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds, 3369 userId, cacheFlags, /* doCache= */ false); 3370 } 3371 3372 @Override getShareTargets( @onNull String callingPackage, @NonNull IntentFilter intentFilter, int userId)3373 public List<ShortcutManager.ShareShortcutInfo> getShareTargets( 3374 @NonNull String callingPackage, @NonNull IntentFilter intentFilter, int userId) { 3375 final AndroidFuture<ParceledListSlice> future = ShortcutService.this.getShareTargets( 3376 callingPackage, intentFilter, userId); 3377 try { 3378 return future.get().getList(); 3379 } catch (InterruptedException | ExecutionException e) { 3380 throw new RuntimeException(e); 3381 } 3382 } 3383 3384 @Override isSharingShortcut(int callingUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId, @NonNull IntentFilter filter)3385 public boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage, 3386 @NonNull String packageName, @NonNull String shortcutId, int userId, 3387 @NonNull IntentFilter filter) { 3388 Preconditions.checkStringNotEmpty(callingPackage, "callingPackage"); 3389 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3390 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 3391 3392 return ShortcutService.this.isSharingShortcut(callingUserId, callingPackage, 3393 packageName, shortcutId, userId, filter); 3394 } 3395 updateCachedShortcutsInternal(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull List<String> shortcutIds, int userId, int cacheFlags, boolean doCache)3396 private void updateCachedShortcutsInternal(int launcherUserId, 3397 @NonNull String callingPackage, @NonNull String packageName, 3398 @NonNull List<String> shortcutIds, int userId, int cacheFlags, boolean doCache) { 3399 // Calling permission must be checked by LauncherAppsImpl. 3400 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3401 Objects.requireNonNull(shortcutIds, "shortcutIds"); 3402 Preconditions.checkState( 3403 (cacheFlags & ShortcutInfo.FLAG_CACHED_ALL) != 0, "invalid cacheFlags"); 3404 3405 List<ShortcutInfo> changedShortcuts = null; 3406 List<ShortcutInfo> removedShortcuts = null; 3407 3408 synchronized (mLock) { 3409 throwIfUserLockedL(userId); 3410 throwIfUserLockedL(launcherUserId); 3411 3412 final int idSize = shortcutIds.size(); 3413 final ShortcutPackage sp = getUserShortcutsLocked(userId) 3414 .getPackageShortcutsIfExists(packageName); 3415 if (idSize == 0 || sp == null) { 3416 return; 3417 } 3418 3419 for (int i = 0; i < idSize; i++) { 3420 final String id = Preconditions.checkStringNotEmpty(shortcutIds.get(i)); 3421 final ShortcutInfo si = sp.findShortcutById(id); 3422 if (si == null || doCache == si.hasFlags(cacheFlags)) { 3423 continue; 3424 } 3425 3426 if (doCache) { 3427 if (si.isLongLived()) { 3428 sp.mutateShortcut(si.getId(), si, 3429 shortcut -> shortcut.addFlags(cacheFlags)); 3430 if (changedShortcuts == null) { 3431 changedShortcuts = new ArrayList<>(1); 3432 } 3433 changedShortcuts.add(si); 3434 } else { 3435 Log.w(TAG, "Only long lived shortcuts can get cached. Ignoring id " 3436 + si.getId()); 3437 } 3438 } else { 3439 ShortcutInfo removed = null; 3440 sp.mutateShortcut(si.getId(), si, shortcut -> 3441 shortcut.clearFlags(cacheFlags)); 3442 if (!si.isDynamic() && !si.isCached()) { 3443 removed = sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true); 3444 } 3445 if (removed != null) { 3446 if (removedShortcuts == null) { 3447 removedShortcuts = new ArrayList<>(1); 3448 } 3449 removedShortcuts.add(removed); 3450 } else { 3451 if (changedShortcuts == null) { 3452 changedShortcuts = new ArrayList<>(1); 3453 } 3454 changedShortcuts.add(si); 3455 } 3456 } 3457 } 3458 } 3459 packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts); 3460 3461 verifyStates(); 3462 } 3463 3464 @Override createShortcutIntents(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId, int callingPid, int callingUid)3465 public Intent[] createShortcutIntents(int launcherUserId, 3466 @NonNull String callingPackage, 3467 @NonNull String packageName, @NonNull String shortcutId, int userId, 3468 int callingPid, int callingUid) { 3469 // Calling permission must be checked by LauncherAppsImpl. 3470 Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty"); 3471 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty"); 3472 3473 synchronized (mLock) { 3474 throwIfUserLockedL(userId); 3475 throwIfUserLockedL(launcherUserId); 3476 3477 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3478 .attemptToRestoreIfNeededAndSave(); 3479 3480 final boolean getPinnedByAnyLauncher = 3481 canSeeAnyPinnedShortcut(callingPackage, launcherUserId, 3482 callingPid, callingUid); 3483 3484 // Make sure the shortcut is actually visible to the launcher. 3485 final ShortcutInfo si = getShortcutInfoLocked( 3486 launcherUserId, callingPackage, packageName, shortcutId, userId, 3487 getPinnedByAnyLauncher); 3488 // "si == null" should suffice here, but check the flags too just to make sure. 3489 if (si == null || !si.isEnabled() || !(si.isAlive() || getPinnedByAnyLauncher)) { 3490 Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled"); 3491 return null; 3492 } 3493 return si.getIntents(); 3494 } 3495 } 3496 3497 @Override addListener(@onNull ShortcutChangeListener listener)3498 public void addListener(@NonNull ShortcutChangeListener listener) { 3499 synchronized (mLock) { 3500 mListeners.add(Objects.requireNonNull(listener)); 3501 } 3502 } 3503 3504 @Override addShortcutChangeCallback( @onNull LauncherApps.ShortcutChangeCallback callback)3505 public void addShortcutChangeCallback( 3506 @NonNull LauncherApps.ShortcutChangeCallback callback) { 3507 synchronized (mLock) { 3508 mShortcutChangeCallbacks.add(Objects.requireNonNull(callback)); 3509 } 3510 } 3511 3512 @Override getShortcutIconResId(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)3513 public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage, 3514 @NonNull String packageName, @NonNull String shortcutId, int userId) { 3515 Objects.requireNonNull(callingPackage, "callingPackage"); 3516 Objects.requireNonNull(packageName, "packageName"); 3517 Objects.requireNonNull(shortcutId, "shortcutId"); 3518 3519 synchronized (mLock) { 3520 throwIfUserLockedL(userId); 3521 throwIfUserLockedL(launcherUserId); 3522 3523 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3524 .attemptToRestoreIfNeededAndSave(); 3525 3526 final ShortcutPackage p = getUserShortcutsLocked(userId) 3527 .getPackageShortcutsIfExists(packageName); 3528 if (p == null) { 3529 return 0; 3530 } 3531 3532 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 3533 return (shortcutInfo != null && shortcutInfo.hasIconResource()) 3534 ? shortcutInfo.getIconResourceId() : 0; 3535 } 3536 } 3537 3538 @Override 3539 @Nullable getShortcutStartingThemeResName(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)3540 public String getShortcutStartingThemeResName(int launcherUserId, 3541 @NonNull String callingPackage, @NonNull String packageName, 3542 @NonNull String shortcutId, int userId) { 3543 Objects.requireNonNull(callingPackage, "callingPackage"); 3544 Objects.requireNonNull(packageName, "packageName"); 3545 Objects.requireNonNull(shortcutId, "shortcutId"); 3546 3547 synchronized (mLock) { 3548 throwIfUserLockedL(userId); 3549 throwIfUserLockedL(launcherUserId); 3550 3551 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3552 .attemptToRestoreIfNeededAndSave(); 3553 3554 final ShortcutPackage p = getUserShortcutsLocked(userId) 3555 .getPackageShortcutsIfExists(packageName); 3556 if (p == null) { 3557 return null; 3558 } 3559 3560 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 3561 return shortcutInfo != null ? shortcutInfo.getStartingThemeResName() : null; 3562 } 3563 } 3564 3565 @Override getShortcutIconFd(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)3566 public ParcelFileDescriptor getShortcutIconFd(int launcherUserId, 3567 @NonNull String callingPackage, @NonNull String packageName, 3568 @NonNull String shortcutId, int userId) { 3569 Objects.requireNonNull(callingPackage, "callingPackage"); 3570 Objects.requireNonNull(packageName, "packageName"); 3571 Objects.requireNonNull(shortcutId, "shortcutId"); 3572 3573 synchronized (mLock) { 3574 throwIfUserLockedL(userId); 3575 throwIfUserLockedL(launcherUserId); 3576 3577 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3578 .attemptToRestoreIfNeededAndSave(); 3579 3580 final ShortcutPackage p = getUserShortcutsLocked(userId) 3581 .getPackageShortcutsIfExists(packageName); 3582 if (p == null) { 3583 return null; 3584 } 3585 3586 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 3587 if (shortcutInfo == null || !shortcutInfo.hasIconFile()) { 3588 return null; 3589 } 3590 final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo); 3591 if (path == null) { 3592 Slog.w(TAG, "null bitmap detected in getShortcutIconFd()"); 3593 return null; 3594 } 3595 try { 3596 return ParcelFileDescriptor.open( 3597 new File(path), 3598 ParcelFileDescriptor.MODE_READ_ONLY); 3599 } catch (FileNotFoundException e) { 3600 Slog.e(TAG, "Icon file not found: " + path); 3601 return null; 3602 } 3603 } 3604 } 3605 3606 @Override getShortcutIconUri(int launcherUserId, @NonNull String launcherPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)3607 public String getShortcutIconUri(int launcherUserId, @NonNull String launcherPackage, 3608 @NonNull String packageName, @NonNull String shortcutId, int userId) { 3609 Objects.requireNonNull(launcherPackage, "launcherPackage"); 3610 Objects.requireNonNull(packageName, "packageName"); 3611 Objects.requireNonNull(shortcutId, "shortcutId"); 3612 3613 synchronized (mLock) { 3614 throwIfUserLockedL(userId); 3615 throwIfUserLockedL(launcherUserId); 3616 3617 getLauncherShortcutsLocked(launcherPackage, userId, launcherUserId) 3618 .attemptToRestoreIfNeededAndSave(); 3619 3620 final ShortcutPackage p = getUserShortcutsLocked(userId) 3621 .getPackageShortcutsIfExists(packageName); 3622 if (p == null) { 3623 return null; 3624 } 3625 3626 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 3627 if (shortcutInfo == null || !shortcutInfo.hasIconUri()) { 3628 return null; 3629 } 3630 String uri = shortcutInfo.getIconUri(); 3631 if (uri == null) { 3632 Slog.w(TAG, "null uri detected in getShortcutIconUri()"); 3633 return null; 3634 } 3635 3636 final long token = Binder.clearCallingIdentity(); 3637 try { 3638 int packageUid = mPackageManagerInternal.getPackageUid(packageName, 3639 PackageManager.MATCH_DIRECT_BOOT_AUTO, userId); 3640 // Grant read uri permission to the caller on behalf of the shortcut owner. All 3641 // granted permissions are revoked when the default launcher changes, or when 3642 // device is rebooted. 3643 mUriGrantsManager.grantUriPermissionFromOwner(mUriPermissionOwner, packageUid, 3644 launcherPackage, Uri.parse(uri), Intent.FLAG_GRANT_READ_URI_PERMISSION, 3645 userId, launcherUserId); 3646 } catch (Exception e) { 3647 Slog.e(TAG, "Failed to grant uri access to " + launcherPackage + " for " + uri, 3648 e); 3649 uri = null; 3650 } finally { 3651 Binder.restoreCallingIdentity(token); 3652 } 3653 return uri; 3654 } 3655 } 3656 3657 @Override hasShortcutHostPermission(int launcherUserId, @NonNull String callingPackage, int callingPid, int callingUid)3658 public boolean hasShortcutHostPermission(int launcherUserId, 3659 @NonNull String callingPackage, int callingPid, int callingUid) { 3660 return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId, 3661 callingPid, callingUid); 3662 } 3663 3664 @Override setShortcutHostPackage(@onNull String type, @Nullable String packageName, int userId)3665 public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName, 3666 int userId) { 3667 ShortcutService.this.setShortcutHostPackage(type, packageName, userId); 3668 } 3669 3670 @Override requestPinAppWidget(@onNull String callingPackage, @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras, @Nullable IntentSender resultIntent, int userId)3671 public boolean requestPinAppWidget(@NonNull String callingPackage, 3672 @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras, 3673 @Nullable IntentSender resultIntent, int userId) { 3674 Objects.requireNonNull(appWidget); 3675 return requestPinItem(callingPackage, userId, null, appWidget, extras, resultIntent); 3676 } 3677 3678 @Override isRequestPinItemSupported(int callingUserId, int requestType)3679 public boolean isRequestPinItemSupported(int callingUserId, int requestType) { 3680 return ShortcutService.this.isRequestPinItemSupported(callingUserId, requestType); 3681 } 3682 3683 @Override isForegroundDefaultLauncher(@onNull String callingPackage, int callingUid)3684 public boolean isForegroundDefaultLauncher(@NonNull String callingPackage, int callingUid) { 3685 Objects.requireNonNull(callingPackage); 3686 3687 final int userId = UserHandle.getUserId(callingUid); 3688 final String defaultLauncher = getDefaultLauncher(userId); 3689 if (defaultLauncher == null) { 3690 return false; 3691 } 3692 if (!callingPackage.equals(defaultLauncher)) { 3693 return false; 3694 } 3695 synchronized (mLock) { 3696 if (!isUidForegroundLocked(callingUid)) { 3697 return false; 3698 } 3699 } 3700 return true; 3701 } 3702 } 3703 3704 final BroadcastReceiver mReceiver = new BroadcastReceiver() { 3705 @Override 3706 public void onReceive(Context context, Intent intent) { 3707 if (!mBootCompleted.get()) { 3708 return; // Boot not completed, ignore the broadcast. 3709 } 3710 try { 3711 if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) { 3712 handleLocaleChanged(); 3713 } 3714 } catch (Exception e) { 3715 wtf("Exception in mReceiver.onReceive", e); 3716 } 3717 } 3718 }; 3719 3720 void handleLocaleChanged() { 3721 if (DEBUG) { 3722 Slog.d(TAG, "handleLocaleChanged"); 3723 } 3724 scheduleSaveBaseState(); 3725 3726 synchronized (mLock) { 3727 final long token = injectClearCallingIdentity(); 3728 try { 3729 forEachLoadedUserLocked(user -> user.detectLocaleChange()); 3730 } finally { 3731 injectRestoreCallingIdentity(token); 3732 } 3733 } 3734 } 3735 3736 /** 3737 * Package event callbacks. 3738 */ 3739 @VisibleForTesting 3740 final BroadcastReceiver mPackageMonitor = new BroadcastReceiver() { 3741 @Override 3742 public void onReceive(Context context, Intent intent) { 3743 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); 3744 if (userId == UserHandle.USER_NULL) { 3745 Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent); 3746 return; 3747 } 3748 3749 final String action = intent.getAction(); 3750 3751 // This is normally called on Handler, so clearCallingIdentity() isn't needed, 3752 // but we still check it in unit tests. 3753 final long token = injectClearCallingIdentity(); 3754 try { 3755 synchronized (mLock) { 3756 if (!isUserUnlockedL(userId)) { 3757 if (DEBUG) { 3758 Slog.d(TAG, "Ignoring package broadcast " + action 3759 + " for locked/stopped user " + userId); 3760 } 3761 return; 3762 } 3763 } 3764 3765 final Uri intentUri = intent.getData(); 3766 final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart() 3767 : null; 3768 if (packageName == null) { 3769 Slog.w(TAG, "Intent broadcast does not contain package name: " + intent); 3770 return; 3771 } 3772 3773 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 3774 3775 switch (action) { 3776 case Intent.ACTION_PACKAGE_ADDED: 3777 if (replacing) { 3778 handlePackageUpdateFinished(packageName, userId); 3779 } else { 3780 handlePackageAdded(packageName, userId); 3781 } 3782 break; 3783 case Intent.ACTION_PACKAGE_REMOVED: 3784 if (!replacing) { 3785 handlePackageRemoved(packageName, userId); 3786 } 3787 break; 3788 case Intent.ACTION_PACKAGE_CHANGED: 3789 handlePackageChanged(packageName, userId); 3790 3791 break; 3792 case Intent.ACTION_PACKAGE_DATA_CLEARED: 3793 handlePackageDataCleared(packageName, userId); 3794 break; 3795 } 3796 } catch (Exception e) { 3797 wtf("Exception in mPackageMonitor.onReceive", e); 3798 } finally { 3799 injectRestoreCallingIdentity(token); 3800 } 3801 } 3802 }; 3803 3804 private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() { 3805 @Override 3806 public void onReceive(Context context, Intent intent) { 3807 // Since it cleans up the shortcut directory and rewrite the ShortcutPackageItems 3808 // in odrder during saveToXml(), it could lead to shortcuts missing when shutdown. 3809 // We need it so that it can finish up saving before shutdown. 3810 synchronized (mLock) { 3811 if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) { 3812 mHandler.removeCallbacks(mSaveDirtyInfoRunner); 3813 saveDirtyInfo(); 3814 } 3815 mShutdown.set(true); 3816 } 3817 } 3818 }; 3819 3820 /** 3821 * Called when a user is unlocked. 3822 * - Check all known packages still exist, and otherwise perform cleanup. 3823 * - If a package still exists, check the version code. If it's been updated, may need to 3824 * update timestamps of its shortcuts. 3825 */ 3826 @VisibleForTesting 3827 void checkPackageChanges(@UserIdInt int ownerUserId) { 3828 if (DEBUG || DEBUG_REBOOT) { 3829 Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId); 3830 } 3831 if (injectIsSafeModeEnabled()) { 3832 Slog.i(TAG, "Safe mode, skipping checkPackageChanges()"); 3833 return; 3834 } 3835 3836 final long start = getStatStartTime(); 3837 try { 3838 final ArrayList<PackageWithUser> gonePackages = new ArrayList<>(); 3839 3840 synchronized (mLock) { 3841 final ShortcutUser user = getUserShortcutsLocked(ownerUserId); 3842 3843 // Find packages that have been uninstalled. 3844 user.forAllPackageItems(spi -> { 3845 if (spi.getPackageInfo().isShadow()) { 3846 return; // Don't delete shadow information. 3847 } 3848 if (!isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) { 3849 if (DEBUG) { 3850 Slog.d(TAG, "Uninstalled: " + spi.getPackageName() 3851 + " user " + spi.getPackageUserId()); 3852 } 3853 gonePackages.add(PackageWithUser.of(spi)); 3854 } 3855 }); 3856 if (gonePackages.size() > 0) { 3857 for (int i = gonePackages.size() - 1; i >= 0; i--) { 3858 final PackageWithUser pu = gonePackages.get(i); 3859 cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId, 3860 /* appStillExists = */ false); 3861 } 3862 } 3863 3864 rescanUpdatedPackagesLocked(ownerUserId, user.getLastAppScanTime()); 3865 } 3866 } finally { 3867 logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start); 3868 } 3869 verifyStates(); 3870 } 3871 3872 @GuardedBy("mLock") 3873 private void rescanUpdatedPackagesLocked(@UserIdInt int userId, long lastScanTime) { 3874 if (DEBUG_REBOOT) { 3875 Slog.d(TAG, "rescan updated package user=" + userId + " last scanned=" + lastScanTime); 3876 } 3877 final ShortcutUser user = getUserShortcutsLocked(userId); 3878 3879 // Note after each OTA, we'll need to rescan all system apps, as their lastUpdateTime 3880 // is not reliable. 3881 final long now = injectCurrentTimeMillis(); 3882 final boolean afterOta = 3883 !injectBuildFingerprint().equals(user.getLastAppScanOsFingerprint()); 3884 3885 // Then for each installed app, publish manifest shortcuts when needed. 3886 forUpdatedPackages(userId, lastScanTime, afterOta, ai -> { 3887 user.attemptToRestoreIfNeededAndSave(this, ai.packageName, userId); 3888 3889 user.rescanPackageIfNeeded(ai.packageName, /* forceRescan= */ true); 3890 }); 3891 3892 // Write the time just before the scan, because there may be apps that have just 3893 // been updated, and we want to catch them in the next time. 3894 user.setLastAppScanTime(now); 3895 user.setLastAppScanOsFingerprint(injectBuildFingerprint()); 3896 scheduleSaveUser(userId); 3897 } 3898 3899 private void handlePackageAdded(String packageName, @UserIdInt int userId) { 3900 if (DEBUG || DEBUG_REBOOT) { 3901 Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId)); 3902 } 3903 synchronized (mLock) { 3904 final ShortcutUser user = getUserShortcutsLocked(userId); 3905 user.attemptToRestoreIfNeededAndSave(this, packageName, userId); 3906 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 3907 } 3908 verifyStates(); 3909 } 3910 3911 private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) { 3912 if (DEBUG || DEBUG_REBOOT) { 3913 Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d", 3914 packageName, userId)); 3915 } 3916 synchronized (mLock) { 3917 final ShortcutUser user = getUserShortcutsLocked(userId); 3918 user.attemptToRestoreIfNeededAndSave(this, packageName, userId); 3919 3920 if (isPackageInstalled(packageName, userId)) { 3921 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 3922 } 3923 } 3924 verifyStates(); 3925 } 3926 3927 private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) { 3928 if (DEBUG || DEBUG_REBOOT) { 3929 Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, 3930 packageUserId)); 3931 } 3932 cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ false); 3933 3934 verifyStates(); 3935 } 3936 3937 private void handlePackageDataCleared(String packageName, int packageUserId) { 3938 if (DEBUG || DEBUG_REBOOT) { 3939 Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName, 3940 packageUserId)); 3941 } 3942 cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ true); 3943 3944 verifyStates(); 3945 } 3946 3947 private void handlePackageChanged(String packageName, int packageUserId) { 3948 if (!isPackageInstalled(packageName, packageUserId)) { 3949 // Probably disabled, which is the same thing as uninstalled. 3950 handlePackageRemoved(packageName, packageUserId); 3951 return; 3952 } 3953 if (DEBUG || DEBUG_REBOOT) { 3954 Slog.d(TAG, String.format("handlePackageChanged: %s user=%d", packageName, 3955 packageUserId)); 3956 } 3957 3958 // Activities may be disabled or enabled. Just rescan the package. 3959 synchronized (mLock) { 3960 final ShortcutUser user = getUserShortcutsLocked(packageUserId); 3961 3962 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 3963 } 3964 3965 verifyStates(); 3966 } 3967 3968 // === PackageManager interaction === 3969 3970 /** 3971 * Returns {@link PackageInfo} unless it's uninstalled or disabled. 3972 */ 3973 @Nullable 3974 final PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) { 3975 return getPackageInfo(packageName, userId, true); 3976 } 3977 3978 /** 3979 * Returns {@link PackageInfo} unless it's uninstalled or disabled. 3980 */ 3981 @Nullable 3982 final PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) { 3983 return getPackageInfo(packageName, userId, false); 3984 } 3985 3986 int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) { 3987 final long token = injectClearCallingIdentity(); 3988 try { 3989 return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS, userId); 3990 } catch (RemoteException e) { 3991 // Shouldn't happen. 3992 Slog.wtf(TAG, "RemoteException", e); 3993 return -1; 3994 } finally { 3995 injectRestoreCallingIdentity(token); 3996 } 3997 } 3998 3999 /** 4000 * Returns {@link PackageInfo} unless it's uninstalled or disabled. 4001 */ 4002 @Nullable 4003 @VisibleForTesting 4004 final PackageInfo getPackageInfo(String packageName, @UserIdInt int userId, 4005 boolean getSignatures) { 4006 return isInstalledOrNull(injectPackageInfoWithUninstalled( 4007 packageName, userId, getSignatures)); 4008 } 4009 4010 /** 4011 * Do not use directly; this returns uninstalled packages too. 4012 */ 4013 @Nullable 4014 @VisibleForTesting 4015 PackageInfo injectPackageInfoWithUninstalled(String packageName, @UserIdInt int userId, 4016 boolean getSignatures) { 4017 final long start = getStatStartTime(); 4018 final long token = injectClearCallingIdentity(); 4019 try { 4020 return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS 4021 | (getSignatures ? PackageManager.GET_SIGNING_CERTIFICATES : 0), userId); 4022 } catch (RemoteException e) { 4023 // Shouldn't happen. 4024 Slog.wtf(TAG, "RemoteException", e); 4025 return null; 4026 } finally { 4027 injectRestoreCallingIdentity(token); 4028 4029 logDurationStat( 4030 (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO), 4031 start); 4032 } 4033 } 4034 4035 /** 4036 * Returns {@link ApplicationInfo} unless it's uninstalled or disabled. 4037 */ 4038 @Nullable 4039 @VisibleForTesting 4040 final ApplicationInfo getApplicationInfo(String packageName, @UserIdInt int userId) { 4041 return isInstalledOrNull(injectApplicationInfoWithUninstalled(packageName, userId)); 4042 } 4043 4044 /** 4045 * Do not use directly; this returns uninstalled packages too. 4046 */ 4047 @Nullable 4048 @VisibleForTesting 4049 ApplicationInfo injectApplicationInfoWithUninstalled( 4050 String packageName, @UserIdInt int userId) { 4051 final long start = getStatStartTime(); 4052 final long token = injectClearCallingIdentity(); 4053 try { 4054 return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId); 4055 } catch (RemoteException e) { 4056 // Shouldn't happen. 4057 Slog.wtf(TAG, "RemoteException", e); 4058 return null; 4059 } finally { 4060 injectRestoreCallingIdentity(token); 4061 4062 logDurationStat(Stats.GET_APPLICATION_INFO, start); 4063 } 4064 } 4065 4066 /** 4067 * Returns {@link ActivityInfo} with its metadata unless it's uninstalled or disabled. 4068 */ 4069 @Nullable 4070 final ActivityInfo getActivityInfoWithMetadata(ComponentName activity, @UserIdInt int userId) { 4071 return isInstalledOrNull(injectGetActivityInfoWithMetadataWithUninstalled( 4072 activity, userId)); 4073 } 4074 4075 /** 4076 * Do not use directly; this returns uninstalled packages too. 4077 */ 4078 @Nullable 4079 @VisibleForTesting 4080 ActivityInfo injectGetActivityInfoWithMetadataWithUninstalled( 4081 ComponentName activity, @UserIdInt int userId) { 4082 final long start = getStatStartTime(); 4083 final long token = injectClearCallingIdentity(); 4084 try { 4085 return mIPackageManager.getActivityInfo(activity, 4086 PACKAGE_MATCH_FLAGS | PackageManager.GET_META_DATA, userId); 4087 } catch (RemoteException e) { 4088 // Shouldn't happen. 4089 Slog.wtf(TAG, "RemoteException", e); 4090 return null; 4091 } finally { 4092 injectRestoreCallingIdentity(token); 4093 4094 logDurationStat(Stats.GET_ACTIVITY_WITH_METADATA, start); 4095 } 4096 } 4097 4098 /** 4099 * Return all installed and enabled packages. 4100 */ 4101 @NonNull 4102 @VisibleForTesting 4103 final List<PackageInfo> getInstalledPackages(@UserIdInt int userId) { 4104 final long start = getStatStartTime(); 4105 final long token = injectClearCallingIdentity(); 4106 try { 4107 final List<PackageInfo> all = injectGetPackagesWithUninstalled(userId); 4108 4109 all.removeIf(PACKAGE_NOT_INSTALLED); 4110 4111 return all; 4112 } catch (RemoteException e) { 4113 // Shouldn't happen. 4114 Slog.wtf(TAG, "RemoteException", e); 4115 return null; 4116 } finally { 4117 injectRestoreCallingIdentity(token); 4118 4119 logDurationStat(Stats.GET_INSTALLED_PACKAGES, start); 4120 } 4121 } 4122 4123 /** 4124 * Do not use directly; this returns uninstalled packages too. 4125 */ 4126 @NonNull 4127 @VisibleForTesting 4128 List<PackageInfo> injectGetPackagesWithUninstalled(@UserIdInt int userId) 4129 throws RemoteException { 4130 final ParceledListSlice<PackageInfo> parceledList = 4131 mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId); 4132 if (parceledList == null) { 4133 return Collections.emptyList(); 4134 } 4135 return parceledList.getList(); 4136 } 4137 4138 private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime, boolean afterOta, 4139 Consumer<ApplicationInfo> callback) { 4140 if (DEBUG || DEBUG_REBOOT) { 4141 Slog.d(TAG, "forUpdatedPackages for user " + userId + ", lastScanTime=" + lastScanTime 4142 + " afterOta=" + afterOta); 4143 } 4144 final List<PackageInfo> list = getInstalledPackages(userId); 4145 for (int i = list.size() - 1; i >= 0; i--) { 4146 final PackageInfo pi = list.get(i); 4147 4148 // If the package has been updated since the last scan time, then scan it. 4149 // Also if it's right after an OTA, always re-scan all apps anyway, since the 4150 // shortcut parser might have changed. 4151 if (afterOta || (pi.lastUpdateTime >= lastScanTime)) { 4152 if (DEBUG || DEBUG_REBOOT) { 4153 Slog.d(TAG, "Found updated package " + pi.packageName 4154 + " updateTime=" + pi.lastUpdateTime); 4155 } 4156 callback.accept(pi.applicationInfo); 4157 } 4158 } 4159 } 4160 4161 private boolean isApplicationFlagSet(@NonNull String packageName, int userId, int flags) { 4162 final ApplicationInfo ai = injectApplicationInfoWithUninstalled(packageName, userId); 4163 return (ai != null) && ((ai.flags & flags) == flags); 4164 } 4165 4166 // Due to b/38267327, ActivityInfo.enabled may not reflect the current state of the component 4167 // and we need to check the enabled state via PackageManager.getComponentEnabledSetting. 4168 private boolean isEnabled(@Nullable ActivityInfo ai, int userId) { 4169 if (ai == null) { 4170 return false; 4171 } 4172 4173 int enabledFlag; 4174 final long token = injectClearCallingIdentity(); 4175 try { 4176 enabledFlag = mIPackageManager.getComponentEnabledSetting( 4177 ai.getComponentName(), userId); 4178 } catch (RemoteException e) { 4179 // Shouldn't happen. 4180 Slog.wtf(TAG, "RemoteException", e); 4181 return false; 4182 } finally { 4183 injectRestoreCallingIdentity(token); 4184 } 4185 4186 if ((enabledFlag == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && ai.enabled) 4187 || enabledFlag == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { 4188 return true; 4189 } 4190 return false; 4191 } 4192 4193 private static boolean isSystem(@Nullable ActivityInfo ai) { 4194 return (ai != null) && isSystem(ai.applicationInfo); 4195 } 4196 4197 private static boolean isSystem(@Nullable ApplicationInfo ai) { 4198 return (ai != null) && (ai.flags & SYSTEM_APP_MASK) != 0; 4199 } 4200 4201 private static boolean isInstalled(@Nullable ApplicationInfo ai) { 4202 return (ai != null) && ai.enabled && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0; 4203 } 4204 4205 private static boolean isEphemeralApp(@Nullable ApplicationInfo ai) { 4206 return (ai != null) && ai.isInstantApp(); 4207 } 4208 4209 private static boolean isInstalled(@Nullable PackageInfo pi) { 4210 return (pi != null) && isInstalled(pi.applicationInfo); 4211 } 4212 4213 private static boolean isInstalled(@Nullable ActivityInfo ai) { 4214 return (ai != null) && isInstalled(ai.applicationInfo); 4215 } 4216 4217 private static ApplicationInfo isInstalledOrNull(ApplicationInfo ai) { 4218 return isInstalled(ai) ? ai : null; 4219 } 4220 4221 private static PackageInfo isInstalledOrNull(PackageInfo pi) { 4222 return isInstalled(pi) ? pi : null; 4223 } 4224 4225 private static ActivityInfo isInstalledOrNull(ActivityInfo ai) { 4226 return isInstalled(ai) ? ai : null; 4227 } 4228 4229 boolean isPackageInstalled(String packageName, int userId) { 4230 return getApplicationInfo(packageName, userId) != null; 4231 } 4232 4233 boolean isEphemeralApp(String packageName, int userId) { 4234 return isEphemeralApp(getApplicationInfo(packageName, userId)); 4235 } 4236 4237 @Nullable 4238 XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) { 4239 return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key); 4240 } 4241 4242 @Nullable 4243 Resources injectGetResourcesForApplicationAsUser(String packageName, int userId) { 4244 final long start = getStatStartTime(); 4245 final long token = injectClearCallingIdentity(); 4246 try { 4247 return mContext.createContextAsUser(UserHandle.of(userId), /* flags */ 0) 4248 .getPackageManager().getResourcesForApplication(packageName); 4249 } catch (NameNotFoundException e) { 4250 Slog.e(TAG, "Resources of package " + packageName + " for user " + userId 4251 + " not found"); 4252 return null; 4253 } finally { 4254 injectRestoreCallingIdentity(token); 4255 4256 logDurationStat(Stats.GET_APPLICATION_RESOURCES, start); 4257 } 4258 } 4259 4260 private Intent getMainActivityIntent() { 4261 final Intent intent = new Intent(Intent.ACTION_MAIN); 4262 intent.addCategory(LAUNCHER_INTENT_CATEGORY); 4263 return intent; 4264 } 4265 4266 /** 4267 * Same as queryIntentActivitiesAsUser, except it makes sure the package is installed, 4268 * and only returns exported activities. 4269 */ 4270 @NonNull 4271 @VisibleForTesting 4272 List<ResolveInfo> queryActivities(@NonNull Intent baseIntent, 4273 @NonNull String packageName, @Nullable ComponentName activity, int userId) { 4274 4275 baseIntent.setPackage(Objects.requireNonNull(packageName)); 4276 if (activity != null) { 4277 baseIntent.setComponent(activity); 4278 } 4279 return queryActivities(baseIntent, userId, /* exportedOnly =*/ true); 4280 } 4281 4282 @NonNull 4283 List<ResolveInfo> queryActivities(@NonNull Intent intent, int userId, 4284 boolean exportedOnly) { 4285 final List<ResolveInfo> resolved; 4286 final long token = injectClearCallingIdentity(); 4287 try { 4288 resolved = mContext.getPackageManager().queryIntentActivitiesAsUser(intent, 4289 PACKAGE_MATCH_FLAGS | PackageManager.MATCH_DISABLED_COMPONENTS, userId); 4290 } finally { 4291 injectRestoreCallingIdentity(token); 4292 } 4293 if (resolved == null || resolved.size() == 0) { 4294 return EMPTY_RESOLVE_INFO; 4295 } 4296 // Make sure the package is installed. 4297 resolved.removeIf(ACTIVITY_NOT_INSTALLED); 4298 resolved.removeIf((ri) -> { 4299 final ActivityInfo ai = ri.activityInfo; 4300 return !isSystem(ai) && !isEnabled(ai, userId); 4301 }); 4302 if (exportedOnly) { 4303 resolved.removeIf(ACTIVITY_NOT_EXPORTED); 4304 } 4305 return resolved; 4306 } 4307 4308 /** 4309 * Return the main activity that is exported and, for non-system apps, enabled. If multiple 4310 * activities are found, return the first one. 4311 */ 4312 @Nullable 4313 ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) { 4314 final long start = getStatStartTime(); 4315 try { 4316 final List<ResolveInfo> resolved = 4317 queryActivities(getMainActivityIntent(), packageName, null, userId); 4318 return resolved.size() == 0 ? null : resolved.get(0).activityInfo.getComponentName(); 4319 } finally { 4320 logDurationStat(Stats.GET_LAUNCHER_ACTIVITY, start); 4321 } 4322 } 4323 4324 /** 4325 * Return whether an activity is main, exported and, for non-system apps, enabled. 4326 */ 4327 boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) { 4328 final long start = getStatStartTime(); 4329 try { 4330 if (activity == null) { 4331 wtf("null activity detected"); 4332 return false; 4333 } 4334 if (DUMMY_MAIN_ACTIVITY.equals(activity.getClassName())) { 4335 return true; 4336 } 4337 final List<ResolveInfo> resolved = queryActivities( 4338 getMainActivityIntent(), activity.getPackageName(), activity, userId); 4339 return resolved.size() > 0; 4340 } finally { 4341 logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start); 4342 } 4343 } 4344 4345 /** 4346 * Create a placeholder "main activity" component name which is used to create a dynamic shortcut 4347 * with no main activity temporarily. 4348 */ 4349 @NonNull 4350 ComponentName getDummyMainActivity(@NonNull String packageName) { 4351 return new ComponentName(packageName, DUMMY_MAIN_ACTIVITY); 4352 } 4353 4354 boolean isDummyMainActivity(@Nullable ComponentName name) { 4355 return name != null && DUMMY_MAIN_ACTIVITY.equals(name.getClassName()); 4356 } 4357 4358 /** 4359 * Return all the main activities that are exported and, for non-system apps, enabled, from a 4360 * package. 4361 */ 4362 @NonNull 4363 List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) { 4364 final long start = getStatStartTime(); 4365 try { 4366 return queryActivities(getMainActivityIntent(), packageName, null, userId); 4367 } finally { 4368 logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start); 4369 } 4370 } 4371 4372 /** 4373 * Return whether an activity is enabled and exported. 4374 */ 4375 @VisibleForTesting 4376 boolean injectIsActivityEnabledAndExported( 4377 @NonNull ComponentName activity, @UserIdInt int userId) { 4378 final long start = getStatStartTime(); 4379 try { 4380 return queryActivities(new Intent(), activity.getPackageName(), activity, userId) 4381 .size() > 0; 4382 } finally { 4383 logDurationStat(Stats.IS_ACTIVITY_ENABLED, start); 4384 } 4385 } 4386 4387 /** 4388 * Get the {@link LauncherApps#ACTION_CONFIRM_PIN_SHORTCUT} or 4389 * {@link LauncherApps#ACTION_CONFIRM_PIN_APPWIDGET} activity in a given package depending on 4390 * the requestType. 4391 */ 4392 @Nullable 4393 ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName, 4394 int launcherUserId, int requestType) { 4395 Objects.requireNonNull(launcherPackageName); 4396 String action = requestType == LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ? 4397 LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT : 4398 LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET; 4399 4400 final Intent confirmIntent = new Intent(action).setPackage(launcherPackageName); 4401 final List<ResolveInfo> candidates = queryActivities( 4402 confirmIntent, launcherUserId, /* exportedOnly =*/ false); 4403 for (ResolveInfo ri : candidates) { 4404 return ri.activityInfo.getComponentName(); 4405 } 4406 return null; 4407 } 4408 4409 boolean injectIsSafeModeEnabled() { 4410 final long token = injectClearCallingIdentity(); 4411 try { 4412 return IWindowManager.Stub 4413 .asInterface(ServiceManager.getService(Context.WINDOW_SERVICE)) 4414 .isSafeModeEnabled(); 4415 } catch (RemoteException e) { 4416 return false; // Shouldn't happen though. 4417 } finally { 4418 injectRestoreCallingIdentity(token); 4419 } 4420 } 4421 4422 /** 4423 * If {@code userId} is of a managed profile, return the parent user ID. Otherwise return 4424 * itself. 4425 */ 4426 int getParentOrSelfUserId(int userId) { 4427 return mUserManagerInternal.getProfileParentId(userId); 4428 } 4429 4430 void injectSendIntentSender(IntentSender intentSender, Intent extras) { 4431 if (intentSender == null) { 4432 return; 4433 } 4434 try { 4435 intentSender.sendIntent(mContext, /* code= */ 0, extras, 4436 /* onFinished=*/ null, /* handler= */ null); 4437 } catch (SendIntentException e) { 4438 Slog.w(TAG, "sendIntent failed().", e); 4439 } 4440 } 4441 4442 // === Backup & restore === 4443 4444 boolean shouldBackupApp(String packageName, int userId) { 4445 return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP); 4446 } 4447 4448 static boolean shouldBackupApp(PackageInfo pi) { 4449 return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0; 4450 } 4451 4452 @Override 4453 public byte[] getBackupPayload(@UserIdInt int userId) { 4454 enforceSystem(); 4455 if (DEBUG) { 4456 Slog.d(TAG, "Backing up user " + userId); 4457 } 4458 synchronized (mLock) { 4459 if (!isUserUnlockedL(userId)) { 4460 wtf("Can't backup: user " + userId + " is locked or not running"); 4461 return null; 4462 } 4463 4464 final ShortcutUser user = getUserShortcutsLocked(userId); 4465 if (user == null) { 4466 wtf("Can't backup: user not found: id=" + userId); 4467 return null; 4468 } 4469 4470 // Update the signatures for all packages. 4471 user.forAllPackageItems(spi -> spi.refreshPackageSignatureAndSave()); 4472 4473 // Rescan all apps; this will also update the version codes and "allow-backup". 4474 user.forAllPackages(pkg -> pkg.rescanPackageIfNeeded( 4475 /*isNewApp=*/ false, /*forceRescan=*/ true)); 4476 4477 // Set the version code for the launchers. 4478 user.forAllLaunchers(launcher -> launcher.ensurePackageInfo()); 4479 4480 // Save to the filesystem. 4481 scheduleSaveUser(userId); 4482 saveDirtyInfo(); 4483 4484 // Note, in case of backup, we don't have to wait on bitmap saving, because we don't 4485 // back up bitmaps anyway. 4486 4487 // Then create the backup payload. 4488 final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024); 4489 try { 4490 saveUserInternalLocked(userId, os, /* forBackup */ true); 4491 } catch (XmlPullParserException | IOException e) { 4492 // Shouldn't happen. 4493 Slog.w(TAG, "Backup failed.", e); 4494 return null; 4495 } 4496 byte[] payload = os.toByteArray(); 4497 mShortcutDumpFiles.save("backup-1-payload.txt", payload); 4498 return payload; 4499 } 4500 } 4501 4502 @Override 4503 public AndroidFuture applyRestore(byte[] payload, @UserIdInt int userId) { 4504 final AndroidFuture<Void> ret = new AndroidFuture<>(); 4505 try { 4506 enforceSystem(); 4507 } catch (Exception e) { 4508 ret.completeExceptionally(e); 4509 return ret; 4510 } 4511 injectPostToHandler(() -> { 4512 try { 4513 if (DEBUG || DEBUG_REBOOT) { 4514 Slog.d(TAG, "Restoring user " + userId); 4515 } 4516 synchronized (mLock) { 4517 if (!isUserUnlockedL(userId)) { 4518 wtf("Can't restore: user " + userId + " is locked or not running"); 4519 ret.complete(null); 4520 return; 4521 } 4522 4523 // Note we print the file timestamps in dumpsys too, but also printing the 4524 // timestamp in the files anyway. 4525 mShortcutDumpFiles.save("restore-0-start.txt", pw -> { 4526 pw.print("Start time: "); 4527 dumpCurrentTime(pw); 4528 pw.println(); 4529 }); 4530 mShortcutDumpFiles.save("restore-1-payload.xml", payload); 4531 4532 // Actually do restore. 4533 final ShortcutUser restored; 4534 final ByteArrayInputStream is = new ByteArrayInputStream(payload); 4535 try { 4536 restored = loadUserInternal(userId, is, /* fromBackup */ true); 4537 } catch (XmlPullParserException | IOException | InvalidFileFormatException e) { 4538 Slog.w(TAG, "Restoration failed.", e); 4539 ret.complete(null); 4540 return; 4541 } 4542 mShortcutDumpFiles.save("restore-2.txt", this::dumpInner); 4543 4544 getUserShortcutsLocked(userId).mergeRestoredFile(restored); 4545 4546 mShortcutDumpFiles.save("restore-3.txt", this::dumpInner); 4547 4548 // Rescan all packages to re-publish manifest shortcuts and do other checks. 4549 rescanUpdatedPackagesLocked(userId, 4550 0 // lastScanTime = 0; rescan all packages. 4551 ); 4552 4553 mShortcutDumpFiles.save("restore-4.txt", this::dumpInner); 4554 4555 mShortcutDumpFiles.save("restore-5-finish.txt", pw -> { 4556 pw.print("Finish time: "); 4557 dumpCurrentTime(pw); 4558 pw.println(); 4559 }); 4560 4561 saveUserLocked(userId); 4562 } 4563 ret.complete(null); 4564 } catch (Exception e) { 4565 ret.completeExceptionally(e); 4566 } 4567 }); 4568 return ret; 4569 } 4570 4571 // === Dump === 4572 4573 @Override 4574 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 4575 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; 4576 dumpNoCheck(fd, pw, args); 4577 } 4578 4579 @VisibleForTesting 4580 void dumpNoCheck(FileDescriptor fd, PrintWriter pw, String[] args) { 4581 final DumpFilter filter = parseDumpArgs(args); 4582 4583 if (filter.shouldDumpCheckIn()) { 4584 // Other flags are not supported for checkin. 4585 dumpCheckin(pw, filter.shouldCheckInClear()); 4586 } else { 4587 if (filter.shouldDumpMain()) { 4588 dumpInner(pw, filter); 4589 pw.println(); 4590 } 4591 if (filter.shouldDumpUid()) { 4592 dumpUid(pw); 4593 pw.println(); 4594 } 4595 if (filter.shouldDumpFiles()) { 4596 dumpDumpFiles(pw); 4597 pw.println(); 4598 } 4599 } 4600 } 4601 4602 private static DumpFilter parseDumpArgs(String[] args) { 4603 final DumpFilter filter = new DumpFilter(); 4604 if (args == null) { 4605 return filter; 4606 } 4607 4608 int argIndex = 0; 4609 while (argIndex < args.length) { 4610 final String arg = args[argIndex++]; 4611 4612 if ("-c".equals(arg)) { 4613 filter.setDumpCheckIn(true); 4614 continue; 4615 } 4616 if ("--checkin".equals(arg)) { 4617 filter.setDumpCheckIn(true); 4618 filter.setCheckInClear(true); 4619 continue; 4620 } 4621 if ("-a".equals(arg) || "--all".equals(arg)) { 4622 filter.setDumpUid(true); 4623 filter.setDumpFiles(true); 4624 continue; 4625 } 4626 if ("-u".equals(arg) || "--uid".equals(arg)) { 4627 filter.setDumpUid(true); 4628 continue; 4629 } 4630 if ("-f".equals(arg) || "--files".equals(arg)) { 4631 filter.setDumpFiles(true); 4632 continue; 4633 } 4634 if ("-n".equals(arg) || "--no-main".equals(arg)) { 4635 filter.setDumpMain(false); 4636 continue; 4637 } 4638 if ("--user".equals(arg)) { 4639 if (argIndex >= args.length) { 4640 throw new IllegalArgumentException("Missing user ID for --user"); 4641 } 4642 try { 4643 filter.addUser(Integer.parseInt(args[argIndex++])); 4644 } catch (NumberFormatException e) { 4645 throw new IllegalArgumentException("Invalid user ID", e); 4646 } 4647 continue; 4648 } 4649 if ("-p".equals(arg) || "--package".equals(arg)) { 4650 if (argIndex >= args.length) { 4651 throw new IllegalArgumentException("Missing package name for --package"); 4652 } 4653 filter.addPackageRegex(args[argIndex++]); 4654 filter.setDumpDetails(false); 4655 continue; 4656 } 4657 if (arg.startsWith("-")) { 4658 throw new IllegalArgumentException("Unknown option " + arg); 4659 } 4660 break; 4661 } 4662 while (argIndex < args.length) { 4663 filter.addPackage(args[argIndex++]); 4664 } 4665 return filter; 4666 } 4667 4668 static class DumpFilter { 4669 private boolean mDumpCheckIn = false; 4670 private boolean mCheckInClear = false; 4671 4672 private boolean mDumpMain = true; 4673 private boolean mDumpUid = false; 4674 private boolean mDumpFiles = false; 4675 4676 private boolean mDumpDetails = true; 4677 private List<Pattern> mPackagePatterns = new ArrayList<>(); 4678 private List<Integer> mUsers = new ArrayList<>(); 4679 4680 void addPackageRegex(String regex) { 4681 mPackagePatterns.add(Pattern.compile(regex)); 4682 } 4683 4684 public void addPackage(String packageName) { 4685 addPackageRegex(Pattern.quote(packageName)); 4686 } 4687 4688 void addUser(int userId) { 4689 mUsers.add(userId); 4690 } 4691 4692 boolean isPackageMatch(String packageName) { 4693 if (mPackagePatterns.size() == 0) { 4694 return true; 4695 } 4696 for (int i = 0; i < mPackagePatterns.size(); i++) { 4697 if (mPackagePatterns.get(i).matcher(packageName).find()) { 4698 return true; 4699 } 4700 } 4701 return false; 4702 } 4703 4704 boolean isUserMatch(int userId) { 4705 if (mUsers.size() == 0) { 4706 return true; 4707 } 4708 for (int i = 0; i < mUsers.size(); i++) { 4709 if (mUsers.get(i) == userId) { 4710 return true; 4711 } 4712 } 4713 return false; 4714 } 4715 4716 public boolean shouldDumpCheckIn() { 4717 return mDumpCheckIn; 4718 } 4719 4720 public void setDumpCheckIn(boolean dumpCheckIn) { 4721 mDumpCheckIn = dumpCheckIn; 4722 } 4723 4724 public boolean shouldCheckInClear() { 4725 return mCheckInClear; 4726 } 4727 4728 public void setCheckInClear(boolean checkInClear) { 4729 mCheckInClear = checkInClear; 4730 } 4731 4732 public boolean shouldDumpMain() { 4733 return mDumpMain; 4734 } 4735 4736 public void setDumpMain(boolean dumpMain) { 4737 mDumpMain = dumpMain; 4738 } 4739 4740 public boolean shouldDumpUid() { 4741 return mDumpUid; 4742 } 4743 4744 public void setDumpUid(boolean dumpUid) { 4745 mDumpUid = dumpUid; 4746 } 4747 4748 public boolean shouldDumpFiles() { 4749 return mDumpFiles; 4750 } 4751 4752 public void setDumpFiles(boolean dumpFiles) { 4753 mDumpFiles = dumpFiles; 4754 } 4755 4756 public boolean shouldDumpDetails() { 4757 return mDumpDetails; 4758 } 4759 4760 public void setDumpDetails(boolean dumpDetails) { 4761 mDumpDetails = dumpDetails; 4762 } 4763 } 4764 4765 private void dumpInner(PrintWriter pw) { 4766 dumpInner(pw, new DumpFilter()); 4767 } 4768 4769 private void dumpInner(PrintWriter pw, DumpFilter filter) { 4770 synchronized (mLock) { 4771 if (filter.shouldDumpDetails()) { 4772 final long now = injectCurrentTimeMillis(); 4773 pw.print("Now: ["); 4774 pw.print(now); 4775 pw.print("] "); 4776 pw.print(formatTime(now)); 4777 4778 pw.print(" Raw last reset: ["); 4779 pw.print(mRawLastResetTime); 4780 pw.print("] "); 4781 pw.print(formatTime(mRawLastResetTime)); 4782 4783 final long last = getLastResetTimeLocked(); 4784 pw.print(" Last reset: ["); 4785 pw.print(last); 4786 pw.print("] "); 4787 pw.print(formatTime(last)); 4788 4789 final long next = getNextResetTimeLocked(); 4790 pw.print(" Next reset: ["); 4791 pw.print(next); 4792 pw.print("] "); 4793 pw.print(formatTime(next)); 4794 pw.println(); 4795 pw.println(); 4796 4797 pw.print(" Config:"); 4798 pw.print(" Max icon dim: "); 4799 pw.println(mMaxIconDimension); 4800 pw.print(" Icon format: "); 4801 pw.println(mIconPersistFormat); 4802 pw.print(" Icon quality: "); 4803 pw.println(mIconPersistQuality); 4804 pw.print(" saveDelayMillis: "); 4805 pw.println(mSaveDelayMillis); 4806 pw.print(" resetInterval: "); 4807 pw.println(mResetInterval); 4808 pw.print(" maxUpdatesPerInterval: "); 4809 pw.println(mMaxUpdatesPerInterval); 4810 pw.print(" maxShortcutsPerActivity: "); 4811 pw.println(mMaxShortcuts); 4812 pw.println(); 4813 4814 mStatLogger.dump(pw, " "); 4815 4816 pw.println(); 4817 pw.print(" #Failures: "); 4818 pw.println(mWtfCount); 4819 4820 if (mLastWtfStacktrace != null) { 4821 pw.print(" Last failure stack trace: "); 4822 pw.println(Log.getStackTraceString(mLastWtfStacktrace)); 4823 } 4824 4825 pw.println(); 4826 mShortcutBitmapSaver.dumpLocked(pw, " "); 4827 4828 pw.println(); 4829 } 4830 4831 for (int i = 0; i < mUsers.size(); i++) { 4832 final ShortcutUser user = mUsers.valueAt(i); 4833 if (filter.isUserMatch(user.getUserId())) { 4834 user.dump(pw, " ", filter); 4835 pw.println(); 4836 } 4837 } 4838 4839 for (int i = 0; i < mShortcutNonPersistentUsers.size(); i++) { 4840 final ShortcutNonPersistentUser user = mShortcutNonPersistentUsers.valueAt(i); 4841 if (filter.isUserMatch(user.getUserId())) { 4842 user.dump(pw, " ", filter); 4843 pw.println(); 4844 } 4845 } 4846 } 4847 } 4848 4849 private void dumpUid(PrintWriter pw) { 4850 synchronized (mLock) { 4851 pw.println("** SHORTCUT MANAGER UID STATES (dumpsys shortcut -n -u)"); 4852 4853 for (int i = 0; i < mUidState.size(); i++) { 4854 final int uid = mUidState.keyAt(i); 4855 final int state = mUidState.valueAt(i); 4856 pw.print(" UID="); 4857 pw.print(uid); 4858 pw.print(" state="); 4859 pw.print(state); 4860 if (isProcessStateForeground(state)) { 4861 pw.print(" [FG]"); 4862 } 4863 pw.print(" last FG="); 4864 pw.print(mUidLastForegroundElapsedTime.get(uid)); 4865 pw.println(); 4866 } 4867 } 4868 } 4869 4870 static String formatTime(long time) { 4871 return TimeMigrationUtils.formatMillisWithFixedFormat(time); 4872 } 4873 4874 private void dumpCurrentTime(PrintWriter pw) { 4875 pw.print(formatTime(injectCurrentTimeMillis())); 4876 } 4877 4878 /** 4879 * Dumpsys for checkin. 4880 * 4881 * @param clear if true, clear the history information. Some other system services have this 4882 * behavior but shortcut service doesn't for now. 4883 */ 4884 private void dumpCheckin(PrintWriter pw, boolean clear) { 4885 synchronized (mLock) { 4886 try { 4887 final JSONArray users = new JSONArray(); 4888 4889 for (int i = 0; i < mUsers.size(); i++) { 4890 users.put(mUsers.valueAt(i).dumpCheckin(clear)); 4891 } 4892 4893 final JSONObject result = new JSONObject(); 4894 4895 result.put(KEY_SHORTCUT, users); 4896 result.put(KEY_LOW_RAM, injectIsLowRamDevice()); 4897 result.put(KEY_ICON_SIZE, mMaxIconDimension); 4898 4899 pw.println(result.toString(1)); 4900 } catch (JSONException e) { 4901 Slog.e(TAG, "Unable to write in json", e); 4902 } 4903 } 4904 } 4905 4906 private void dumpDumpFiles(PrintWriter pw) { 4907 synchronized (mLock) { 4908 pw.println("** SHORTCUT MANAGER FILES (dumpsys shortcut -n -f)"); 4909 mShortcutDumpFiles.dumpAll(pw); 4910 } 4911 } 4912 4913 // === Shell support === 4914 4915 @Override 4916 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 4917 String[] args, ShellCallback callback, ResultReceiver resultReceiver) { 4918 4919 enforceShell(); 4920 4921 final long token = injectClearCallingIdentity(); 4922 try { 4923 final int status = (new MyShellCommand()).exec(this, in, out, err, args, callback, 4924 resultReceiver); 4925 resultReceiver.send(status, null); 4926 } finally { 4927 injectRestoreCallingIdentity(token); 4928 } 4929 } 4930 4931 static class CommandException extends Exception { 4932 public CommandException(String message) { 4933 super(message); 4934 } 4935 } 4936 4937 /** 4938 * Handle "adb shell cmd". 4939 */ 4940 private class MyShellCommand extends ShellCommand { 4941 4942 private int mUserId = UserHandle.USER_SYSTEM; 4943 4944 private int mShortcutMatchFlags = ShortcutManager.FLAG_MATCH_CACHED 4945 | ShortcutManager.FLAG_MATCH_DYNAMIC | ShortcutManager.FLAG_MATCH_MANIFEST 4946 | ShortcutManager.FLAG_MATCH_PINNED; 4947 4948 private void parseOptionsLocked(boolean takeUser) 4949 throws CommandException { 4950 String opt; 4951 while ((opt = getNextOption()) != null) { 4952 switch (opt) { 4953 case "--user": 4954 if (takeUser) { 4955 mUserId = UserHandle.parseUserArg(getNextArgRequired()); 4956 if (!isUserUnlockedL(mUserId)) { 4957 throw new CommandException( 4958 "User " + mUserId + " is not running or locked"); 4959 } 4960 break; 4961 } 4962 // fallthrough 4963 case "--flags": 4964 mShortcutMatchFlags = Integer.parseInt(getNextArgRequired()); 4965 break; 4966 default: 4967 throw new CommandException("Unknown option: " + opt); 4968 } 4969 } 4970 } 4971 4972 @Override 4973 public int onCommand(String cmd) { 4974 if (cmd == null) { 4975 return handleDefaultCommands(cmd); 4976 } 4977 final PrintWriter pw = getOutPrintWriter(); 4978 try { 4979 switch (cmd) { 4980 case "reset-throttling": 4981 handleResetThrottling(); 4982 break; 4983 case "reset-all-throttling": 4984 handleResetAllThrottling(); 4985 break; 4986 case "override-config": 4987 handleOverrideConfig(); 4988 break; 4989 case "reset-config": 4990 handleResetConfig(); 4991 break; 4992 case "get-default-launcher": 4993 handleGetDefaultLauncher(); 4994 break; 4995 case "unload-user": 4996 handleUnloadUser(); 4997 break; 4998 case "clear-shortcuts": 4999 handleClearShortcuts(); 5000 break; 5001 case "get-shortcuts": 5002 handleGetShortcuts(); 5003 break; 5004 case "verify-states": // hidden command to verify various internal states. 5005 handleVerifyStates(); 5006 break; 5007 case "has-shortcut-access": 5008 handleHasShortcutAccess(); 5009 break; 5010 default: 5011 return handleDefaultCommands(cmd); 5012 } 5013 } catch (CommandException e) { 5014 pw.println("Error: " + e.getMessage()); 5015 return 1; 5016 } 5017 pw.println("Success"); 5018 return 0; 5019 } 5020 5021 @Override 5022 public void onHelp() { 5023 final PrintWriter pw = getOutPrintWriter(); 5024 pw.println("Usage: cmd shortcut COMMAND [options ...]"); 5025 pw.println(); 5026 pw.println("cmd shortcut reset-throttling [--user USER_ID]"); 5027 pw.println(" Reset throttling for all packages and users"); 5028 pw.println(); 5029 pw.println("cmd shortcut reset-all-throttling"); 5030 pw.println(" Reset the throttling state for all users"); 5031 pw.println(); 5032 pw.println("cmd shortcut override-config CONFIG"); 5033 pw.println(" Override the configuration for testing (will last until reboot)"); 5034 pw.println(); 5035 pw.println("cmd shortcut reset-config"); 5036 pw.println(" Reset the configuration set with \"update-config\""); 5037 pw.println(); 5038 pw.println("[Deprecated] cmd shortcut get-default-launcher [--user USER_ID]"); 5039 pw.println(" Show the default launcher"); 5040 pw.println(" Note: This command is deprecated. Callers should query the default" 5041 + " launcher from RoleManager instead."); 5042 pw.println(); 5043 pw.println("cmd shortcut unload-user [--user USER_ID]"); 5044 pw.println(" Unload a user from the memory"); 5045 pw.println(" (This should not affect any observable behavior)"); 5046 pw.println(); 5047 pw.println("cmd shortcut clear-shortcuts [--user USER_ID] PACKAGE"); 5048 pw.println(" Remove all shortcuts from a package, including pinned shortcuts"); 5049 pw.println(); 5050 pw.println("cmd shortcut get-shortcuts [--user USER_ID] [--flags FLAGS] PACKAGE"); 5051 pw.println(" Show the shortcuts for a package that match the given flags"); 5052 pw.println(); 5053 pw.println("cmd shortcut has-shortcut-access [--user USER_ID] PACKAGE"); 5054 pw.println(" Prints \"true\" if the package can access shortcuts," 5055 + " \"false\" otherwise"); 5056 pw.println(); 5057 } 5058 5059 private void handleResetThrottling() throws CommandException { 5060 synchronized (mLock) { 5061 parseOptionsLocked(/* takeUser =*/ true); 5062 5063 Slog.i(TAG, "cmd: handleResetThrottling: user=" + mUserId); 5064 5065 resetThrottlingInner(mUserId); 5066 } 5067 } 5068 5069 private void handleResetAllThrottling() { 5070 Slog.i(TAG, "cmd: handleResetAllThrottling"); 5071 5072 resetAllThrottlingInner(); 5073 } 5074 5075 private void handleOverrideConfig() throws CommandException { 5076 final String config = getNextArgRequired(); 5077 5078 Slog.i(TAG, "cmd: handleOverrideConfig: " + config); 5079 5080 synchronized (mLock) { 5081 if (!updateConfigurationLocked(config)) { 5082 throw new CommandException("override-config failed. See logcat for details."); 5083 } 5084 } 5085 } 5086 5087 private void handleResetConfig() { 5088 Slog.i(TAG, "cmd: handleResetConfig"); 5089 5090 synchronized (mLock) { 5091 loadConfigurationLocked(); 5092 } 5093 } 5094 5095 // This method is used by various cts modules to get the current default launcher. Tests 5096 // should query this information directly from RoleManager instead. Keeping the old behavior 5097 // by returning the result from package manager. 5098 private void handleGetDefaultLauncher() throws CommandException { 5099 synchronized (mLock) { 5100 parseOptionsLocked(/* takeUser =*/ true); 5101 5102 final String defaultLauncher = getDefaultLauncher(mUserId); 5103 if (defaultLauncher == null) { 5104 throw new CommandException( 5105 "Failed to get the default launcher for user " + mUserId); 5106 } 5107 5108 // Get the class name of the component from PM to keep the old behaviour. 5109 final List<ResolveInfo> allHomeCandidates = new ArrayList<>(); 5110 mPackageManagerInternal.getHomeActivitiesAsUser(allHomeCandidates, 5111 getParentOrSelfUserId(mUserId)); 5112 for (ResolveInfo ri : allHomeCandidates) { 5113 final ComponentInfo ci = ri.getComponentInfo(); 5114 if (ci.packageName.equals(defaultLauncher)) { 5115 getOutPrintWriter().println("Launcher: " + ci.getComponentName()); 5116 break; 5117 } 5118 } 5119 } 5120 } 5121 5122 private void handleUnloadUser() throws CommandException { 5123 synchronized (mLock) { 5124 parseOptionsLocked(/* takeUser =*/ true); 5125 5126 Slog.i(TAG, "cmd: handleUnloadUser: user=" + mUserId); 5127 5128 ShortcutService.this.handleStopUser(mUserId); 5129 } 5130 } 5131 5132 private void handleClearShortcuts() throws CommandException { 5133 synchronized (mLock) { 5134 parseOptionsLocked(/* takeUser =*/ true); 5135 final String packageName = getNextArgRequired(); 5136 5137 Slog.i(TAG, "cmd: handleClearShortcuts: user" + mUserId + ", " + packageName); 5138 5139 ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId, 5140 /* appStillExists = */ true); 5141 } 5142 } 5143 5144 private void handleGetShortcuts() throws CommandException { 5145 synchronized (mLock) { 5146 parseOptionsLocked(/* takeUser =*/ true); 5147 final String packageName = getNextArgRequired(); 5148 5149 Slog.i(TAG, "cmd: handleGetShortcuts: user=" + mUserId + ", flags=" 5150 + mShortcutMatchFlags + ", package=" + packageName); 5151 5152 final ShortcutUser user = ShortcutService.this.getUserShortcutsLocked(mUserId); 5153 final ShortcutPackage p = user.getPackageShortcutsIfExists(packageName); 5154 if (p == null) { 5155 return; 5156 } 5157 5158 p.dumpShortcuts(getOutPrintWriter(), mShortcutMatchFlags); 5159 } 5160 } 5161 5162 private void handleVerifyStates() throws CommandException { 5163 try { 5164 verifyStatesForce(); // This will throw when there's an issue. 5165 } catch (Throwable th) { 5166 throw new CommandException(th.getMessage() + "\n" + Log.getStackTraceString(th)); 5167 } 5168 } 5169 5170 private void handleHasShortcutAccess() throws CommandException { 5171 synchronized (mLock) { 5172 parseOptionsLocked(/* takeUser =*/ true); 5173 final String packageName = getNextArgRequired(); 5174 5175 boolean shortcutAccess = hasShortcutHostPermissionInner(packageName, mUserId); 5176 getOutPrintWriter().println(Boolean.toString(shortcutAccess)); 5177 } 5178 } 5179 } 5180 5181 // === Unit test support === 5182 5183 // Injection point. 5184 @VisibleForTesting 5185 long injectCurrentTimeMillis() { 5186 return System.currentTimeMillis(); 5187 } 5188 5189 @VisibleForTesting 5190 long injectElapsedRealtime() { 5191 return SystemClock.elapsedRealtime(); 5192 } 5193 5194 @VisibleForTesting 5195 long injectUptimeMillis() { 5196 return SystemClock.uptimeMillis(); 5197 } 5198 5199 // Injection point. 5200 @VisibleForTesting 5201 int injectBinderCallingUid() { 5202 return getCallingUid(); 5203 } 5204 5205 @VisibleForTesting 5206 int injectBinderCallingPid() { 5207 return getCallingPid(); 5208 } 5209 5210 private int getCallingUserId() { 5211 return UserHandle.getUserId(injectBinderCallingUid()); 5212 } 5213 5214 // Injection point. 5215 @VisibleForTesting 5216 long injectClearCallingIdentity() { 5217 return Binder.clearCallingIdentity(); 5218 } 5219 5220 // Injection point. 5221 @VisibleForTesting 5222 void injectRestoreCallingIdentity(long token) { 5223 Binder.restoreCallingIdentity(token); 5224 } 5225 5226 // Injection point. 5227 String injectBuildFingerprint() { 5228 return Build.FINGERPRINT; 5229 } 5230 5231 final void wtf(String message) { 5232 wtf(message, /* exception= */ null); 5233 } 5234 5235 // Injection point. 5236 void wtf(String message, Throwable e) { 5237 if (e == null) { 5238 e = new RuntimeException("Stacktrace"); 5239 } 5240 synchronized (mLock) { 5241 mWtfCount++; 5242 mLastWtfStacktrace = new Exception("Last failure was logged here:"); 5243 } 5244 Slog.wtf(TAG, message, e); 5245 } 5246 5247 @VisibleForTesting 5248 File injectSystemDataPath() { 5249 return Environment.getDataSystemDirectory(); 5250 } 5251 5252 @VisibleForTesting 5253 File injectUserDataPath(@UserIdInt int userId) { 5254 return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER); 5255 } 5256 5257 public File getDumpPath() { 5258 return new File(injectUserDataPath(UserHandle.USER_SYSTEM), DIRECTORY_DUMP); 5259 } 5260 5261 @VisibleForTesting 5262 boolean injectIsLowRamDevice() { 5263 return ActivityManager.isLowRamDeviceStatic(); 5264 } 5265 5266 @VisibleForTesting 5267 void injectRegisterUidObserver(IUidObserver observer, int which) { 5268 try { 5269 ActivityManager.getService().registerUidObserver(observer, which, 5270 ActivityManager.PROCESS_STATE_UNKNOWN, null); 5271 } catch (RemoteException shouldntHappen) { 5272 } 5273 } 5274 5275 @VisibleForTesting 5276 void injectRegisterRoleHoldersListener(OnRoleHoldersChangedListener listener) { 5277 mRoleManager.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(), listener, 5278 UserHandle.ALL); 5279 } 5280 5281 @VisibleForTesting 5282 String injectGetHomeRoleHolderAsUser(int userId) { 5283 List<String> roleHolders = mRoleManager.getRoleHoldersAsUser( 5284 RoleManager.ROLE_HOME, UserHandle.of(userId)); 5285 return roleHolders.isEmpty() ? null : roleHolders.get(0); 5286 } 5287 5288 File getUserBitmapFilePath(@UserIdInt int userId) { 5289 return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS); 5290 } 5291 5292 @VisibleForTesting 5293 SparseArray<ShortcutUser> getShortcutsForTest() { 5294 return mUsers; 5295 } 5296 5297 @VisibleForTesting 5298 int getMaxShortcutsForTest() { 5299 return mMaxShortcuts; 5300 } 5301 5302 @VisibleForTesting 5303 int getMaxUpdatesPerIntervalForTest() { 5304 return mMaxUpdatesPerInterval; 5305 } 5306 5307 @VisibleForTesting 5308 long getResetIntervalForTest() { 5309 return mResetInterval; 5310 } 5311 5312 @VisibleForTesting 5313 int getMaxIconDimensionForTest() { 5314 return mMaxIconDimension; 5315 } 5316 5317 @VisibleForTesting 5318 CompressFormat getIconPersistFormatForTest() { 5319 return mIconPersistFormat; 5320 } 5321 5322 @VisibleForTesting 5323 int getIconPersistQualityForTest() { 5324 return mIconPersistQuality; 5325 } 5326 5327 @VisibleForTesting 5328 ShortcutPackage getPackageShortcutForTest(String packageName, int userId) { 5329 synchronized (mLock) { 5330 final ShortcutUser user = mUsers.get(userId); 5331 if (user == null) return null; 5332 5333 return user.getAllPackagesForTest().get(packageName); 5334 } 5335 } 5336 5337 @VisibleForTesting 5338 ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) { 5339 synchronized (mLock) { 5340 final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId); 5341 if (pkg == null) return null; 5342 5343 return pkg.findShortcutById(shortcutId); 5344 } 5345 } 5346 5347 @VisibleForTesting 5348 void updatePackageShortcutForTest(String packageName, String shortcutId, int userId, 5349 Consumer<ShortcutInfo> cb) { 5350 synchronized (mLock) { 5351 final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId); 5352 if (pkg == null) return; 5353 5354 pkg.mutateShortcut(shortcutId, null, cb); 5355 } 5356 } 5357 5358 @VisibleForTesting 5359 ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) { 5360 synchronized (mLock) { 5361 final ShortcutUser user = mUsers.get(userId); 5362 if (user == null) return null; 5363 5364 return user.getAllLaunchersForTest().get(PackageWithUser.of(userId, packageName)); 5365 } 5366 } 5367 5368 @VisibleForTesting 5369 ShortcutRequestPinProcessor getShortcutRequestPinProcessorForTest() { 5370 return mShortcutRequestPinProcessor; 5371 } 5372 5373 /** 5374 * Control whether {@link #verifyStates} should be performed. We always perform it during unit 5375 * tests. 5376 */ 5377 @VisibleForTesting 5378 boolean injectShouldPerformVerification() { 5379 return DEBUG; 5380 } 5381 5382 /** 5383 * Check various internal states and throws if there's any inconsistency. 5384 * This is normally only enabled during unit tests. 5385 */ 5386 final void verifyStates() { 5387 if (injectShouldPerformVerification()) { 5388 verifyStatesInner(); 5389 } 5390 } 5391 5392 private final void verifyStatesForce() { 5393 verifyStatesInner(); 5394 } 5395 5396 private void verifyStatesInner() { 5397 synchronized (mLock) { 5398 forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates)); 5399 } 5400 } 5401 5402 @VisibleForTesting 5403 void waitForBitmapSavesForTest() { 5404 synchronized (mLock) { 5405 mShortcutBitmapSaver.waitForAllSavesLocked(); 5406 } 5407 } 5408 5409 /** 5410 * This helper method does the following 3 tasks: 5411 * 5412 * 1- Combines the |changed| and |updated| shortcut lists, while removing duplicates. 5413 * 2- If a shortcut is deleted and added at once in the same operation, removes it from the 5414 * |removed| list. 5415 * 3- Reloads the final list to get the latest flags. 5416 */ 5417 private List<ShortcutInfo> prepareChangedShortcuts(ArraySet<String> changedIds, 5418 ArraySet<String> newIds, List<ShortcutInfo> deletedList, final ShortcutPackage ps) { 5419 if (ps == null) { 5420 // This can happen when package restore is not finished yet. 5421 return null; 5422 } 5423 if (CollectionUtils.isEmpty(changedIds) && CollectionUtils.isEmpty(newIds)) { 5424 return null; 5425 } 5426 5427 ArraySet<String> resultIds = new ArraySet<>(); 5428 if (!CollectionUtils.isEmpty(changedIds)) { 5429 resultIds.addAll(changedIds); 5430 } 5431 if (!CollectionUtils.isEmpty(newIds)) { 5432 resultIds.addAll(newIds); 5433 } 5434 5435 if (!CollectionUtils.isEmpty(deletedList)) { 5436 deletedList.removeIf((ShortcutInfo si) -> resultIds.contains(si.getId())); 5437 } 5438 5439 List<ShortcutInfo> result = new ArrayList<>(); 5440 ps.findAllByIds(result, resultIds, (ShortcutInfo si) -> resultIds.contains(si.getId()), 5441 ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); 5442 return result; 5443 } 5444 5445 private List<ShortcutInfo> prepareChangedShortcuts(List<ShortcutInfo> changedList, 5446 List<ShortcutInfo> newList, List<ShortcutInfo> deletedList, final ShortcutPackage ps) { 5447 ArraySet<String> changedIds = new ArraySet<>(); 5448 addShortcutIdsToSet(changedIds, changedList); 5449 5450 ArraySet<String> newIds = new ArraySet<>(); 5451 addShortcutIdsToSet(newIds, newList); 5452 5453 return prepareChangedShortcuts(changedIds, newIds, deletedList, ps); 5454 } 5455 5456 private void addShortcutIdsToSet(ArraySet<String> ids, List<ShortcutInfo> shortcuts) { 5457 if (CollectionUtils.isEmpty(shortcuts)) { 5458 return; 5459 } 5460 final int size = shortcuts.size(); 5461 for (int i = 0; i < size; i++) { 5462 ids.add(shortcuts.get(i).getId()); 5463 } 5464 } 5465 } 5466