ShortcutService.java revision 475c36539da9687d75447adb9054c98ce03c754a
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 */ 16package com.android.server.pm; 17 18import android.annotation.IntDef; 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.annotation.UserIdInt; 22import android.app.ActivityManager; 23import android.app.ActivityManagerInternal; 24import android.app.AppGlobals; 25import android.app.IUidObserver; 26import android.app.usage.UsageStatsManagerInternal; 27import android.appwidget.AppWidgetProviderInfo; 28import android.content.BroadcastReceiver; 29import android.content.ComponentName; 30import android.content.Context; 31import android.content.Intent; 32import android.content.IntentFilter; 33import android.content.IntentSender; 34import android.content.IntentSender.SendIntentException; 35import android.content.pm.ActivityInfo; 36import android.content.pm.ApplicationInfo; 37import android.content.pm.IPackageManager; 38import android.content.pm.IShortcutService; 39import android.content.pm.LauncherApps; 40import android.content.pm.LauncherApps.ShortcutQuery; 41import android.content.pm.PackageInfo; 42import android.content.pm.PackageManager; 43import android.content.pm.PackageManager.NameNotFoundException; 44import android.content.pm.PackageManagerInternal; 45import android.content.pm.ParceledListSlice; 46import android.content.pm.ResolveInfo; 47import android.content.pm.ShortcutInfo; 48import android.content.pm.ShortcutServiceInternal; 49import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; 50import android.content.pm.UserInfo; 51import android.content.res.Resources; 52import android.content.res.XmlResourceParser; 53import android.graphics.Bitmap; 54import android.graphics.Bitmap.CompressFormat; 55import android.graphics.Canvas; 56import android.graphics.RectF; 57import android.graphics.drawable.Icon; 58import android.net.Uri; 59import android.os.Binder; 60import android.os.Build; 61import android.os.Bundle; 62import android.os.Environment; 63import android.os.FileUtils; 64import android.os.Handler; 65import android.os.LocaleList; 66import android.os.Looper; 67import android.os.ParcelFileDescriptor; 68import android.os.PersistableBundle; 69import android.os.Process; 70import android.os.RemoteException; 71import android.os.ResultReceiver; 72import android.os.SELinux; 73import android.os.ServiceManager; 74import android.os.ShellCallback; 75import android.os.ShellCommand; 76import android.os.SystemClock; 77import android.os.UserHandle; 78import android.os.UserManager; 79import android.text.TextUtils; 80import android.text.format.Time; 81import android.util.ArraySet; 82import android.util.AtomicFile; 83import android.util.KeyValueListParser; 84import android.util.Log; 85import android.util.Slog; 86import android.util.SparseArray; 87import android.util.SparseBooleanArray; 88import android.util.SparseIntArray; 89import android.util.SparseLongArray; 90import android.util.TypedValue; 91import android.util.Xml; 92import android.view.IWindowManager; 93 94import com.android.internal.annotations.GuardedBy; 95import com.android.internal.annotations.VisibleForTesting; 96import com.android.internal.os.BackgroundThread; 97import com.android.internal.util.DumpUtils; 98import com.android.internal.util.FastXmlSerializer; 99import com.android.internal.util.Preconditions; 100import com.android.server.LocalServices; 101import com.android.server.SystemService; 102import com.android.server.pm.ShortcutUser.PackageWithUser; 103 104import libcore.io.IoUtils; 105 106import org.json.JSONArray; 107import org.json.JSONException; 108import org.json.JSONObject; 109import org.xmlpull.v1.XmlPullParser; 110import org.xmlpull.v1.XmlPullParserException; 111import org.xmlpull.v1.XmlSerializer; 112 113import java.io.BufferedInputStream; 114import java.io.BufferedOutputStream; 115import java.io.ByteArrayInputStream; 116import java.io.ByteArrayOutputStream; 117import java.io.File; 118import java.io.FileDescriptor; 119import java.io.FileInputStream; 120import java.io.FileNotFoundException; 121import java.io.FileOutputStream; 122import java.io.IOException; 123import java.io.InputStream; 124import java.io.OutputStream; 125import java.io.PrintWriter; 126import java.lang.annotation.Retention; 127import java.lang.annotation.RetentionPolicy; 128import java.net.URISyntaxException; 129import java.nio.charset.StandardCharsets; 130import java.util.ArrayList; 131import java.util.Collections; 132import java.util.List; 133import java.util.concurrent.atomic.AtomicBoolean; 134import java.util.function.Consumer; 135import java.util.function.Predicate; 136 137/** 138 * TODO: 139 * - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi. 140 * -> But TypedValue.applyDimension() doesn't differentiate x and y..? 141 * 142 * - Detect when already registered instances are passed to APIs again, which might break 143 * internal bitmap handling. 144 */ 145public class ShortcutService extends IShortcutService.Stub { 146 static final String TAG = "ShortcutService"; 147 148 static final boolean DEBUG = false; // STOPSHIP if true 149 static final boolean DEBUG_LOAD = false; // STOPSHIP if true 150 static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true 151 152 @VisibleForTesting 153 static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day 154 155 @VisibleForTesting 156 static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10; 157 158 @VisibleForTesting 159 static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5; 160 161 @VisibleForTesting 162 static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96; 163 164 @VisibleForTesting 165 static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48; 166 167 @VisibleForTesting 168 static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name(); 169 170 @VisibleForTesting 171 static final int DEFAULT_ICON_PERSIST_QUALITY = 100; 172 173 @VisibleForTesting 174 static final int DEFAULT_SAVE_DELAY_MS = 3000; 175 176 @VisibleForTesting 177 static final String FILENAME_BASE_STATE = "shortcut_service.xml"; 178 179 @VisibleForTesting 180 static final String DIRECTORY_PER_USER = "shortcut_service"; 181 182 @VisibleForTesting 183 static final String FILENAME_USER_PACKAGES = "shortcuts.xml"; 184 185 static final String DIRECTORY_BITMAPS = "bitmaps"; 186 187 private static final String TAG_ROOT = "root"; 188 private static final String TAG_LAST_RESET_TIME = "last_reset_time"; 189 190 private static final String ATTR_VALUE = "value"; 191 192 private static final String LAUNCHER_INTENT_CATEGORY = Intent.CATEGORY_LAUNCHER; 193 194 private static final String KEY_SHORTCUT = "shortcut"; 195 private static final String KEY_LOW_RAM = "lowRam"; 196 private static final String KEY_ICON_SIZE = "iconSize"; 197 198 private static final String DUMMY_MAIN_ACTIVITY = "android.__dummy__"; 199 200 @VisibleForTesting 201 interface ConfigConstants { 202 /** 203 * Key name for the save delay, in milliseconds. (int) 204 */ 205 String KEY_SAVE_DELAY_MILLIS = "save_delay_ms"; 206 207 /** 208 * Key name for the throttling reset interval, in seconds. (long) 209 */ 210 String KEY_RESET_INTERVAL_SEC = "reset_interval_sec"; 211 212 /** 213 * Key name for the max number of modifying API calls per app for every interval. (int) 214 */ 215 String KEY_MAX_UPDATES_PER_INTERVAL = "max_updates_per_interval"; 216 217 /** 218 * Key name for the max icon dimensions in DP, for non-low-memory devices. 219 */ 220 String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp"; 221 222 /** 223 * Key name for the max icon dimensions in DP, for low-memory devices. 224 */ 225 String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram"; 226 227 /** 228 * Key name for the max dynamic shortcuts per activity. (int) 229 */ 230 String KEY_MAX_SHORTCUTS = "max_shortcuts"; 231 232 /** 233 * Key name for icon compression quality, 0-100. 234 */ 235 String KEY_ICON_QUALITY = "icon_quality"; 236 237 /** 238 * Key name for icon compression format: "PNG", "JPEG" or "WEBP" 239 */ 240 String KEY_ICON_FORMAT = "icon_format"; 241 } 242 243 final Context mContext; 244 245 private final Object mLock = new Object(); 246 247 private static List<ResolveInfo> EMPTY_RESOLVE_INFO = new ArrayList<>(0); 248 249 // Temporarily reverted to anonymous inner class form due to: b/32554459 250 private static Predicate<ResolveInfo> ACTIVITY_NOT_EXPORTED = new Predicate<ResolveInfo>() { 251 public boolean test(ResolveInfo ri) { 252 return !ri.activityInfo.exported; 253 } 254 }; 255 256 // Temporarily reverted to anonymous inner class form due to: b/32554459 257 private static Predicate<PackageInfo> PACKAGE_NOT_INSTALLED = new Predicate<PackageInfo>() { 258 public boolean test(PackageInfo pi) { 259 return !isInstalled(pi); 260 } 261 }; 262 263 private final Handler mHandler; 264 265 @GuardedBy("mLock") 266 private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1); 267 268 @GuardedBy("mLock") 269 private long mRawLastResetTime; 270 271 /** 272 * User ID -> UserShortcuts 273 */ 274 @GuardedBy("mLock") 275 private final SparseArray<ShortcutUser> mUsers = new SparseArray<>(); 276 277 /** 278 * Max number of dynamic + manifest shortcuts that each application can have at a time. 279 */ 280 private int mMaxShortcuts; 281 282 /** 283 * Max number of updating API calls that each application can make during the interval. 284 */ 285 int mMaxUpdatesPerInterval; 286 287 /** 288 * Actual throttling-reset interval. By default it's a day. 289 */ 290 private long mResetInterval; 291 292 /** 293 * Icon max width/height in pixels. 294 */ 295 private int mMaxIconDimension; 296 297 private CompressFormat mIconPersistFormat; 298 private int mIconPersistQuality; 299 300 private int mSaveDelayMillis; 301 302 private final IPackageManager mIPackageManager; 303 private final PackageManagerInternal mPackageManagerInternal; 304 private final UserManager mUserManager; 305 private final UsageStatsManagerInternal mUsageStatsManagerInternal; 306 private final ActivityManagerInternal mActivityManagerInternal; 307 308 private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor; 309 private final ShortcutBitmapSaver mShortcutBitmapSaver; 310 311 @GuardedBy("mLock") 312 final SparseIntArray mUidState = new SparseIntArray(); 313 314 @GuardedBy("mLock") 315 final SparseLongArray mUidLastForegroundElapsedTime = new SparseLongArray(); 316 317 @GuardedBy("mLock") 318 private List<Integer> mDirtyUserIds = new ArrayList<>(); 319 320 private final AtomicBoolean mBootCompleted = new AtomicBoolean(); 321 322 private static final int PACKAGE_MATCH_FLAGS = 323 PackageManager.MATCH_DIRECT_BOOT_AWARE 324 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 325 | PackageManager.MATCH_UNINSTALLED_PACKAGES; 326 327 @GuardedBy("mLock") 328 final SparseBooleanArray mUnlockedUsers = new SparseBooleanArray(); 329 330 // Stats 331 @VisibleForTesting 332 interface Stats { 333 int GET_DEFAULT_HOME = 0; 334 int GET_PACKAGE_INFO = 1; 335 int GET_PACKAGE_INFO_WITH_SIG = 2; 336 int GET_APPLICATION_INFO = 3; 337 int LAUNCHER_PERMISSION_CHECK = 4; 338 int CLEANUP_DANGLING_BITMAPS = 5; 339 int GET_ACTIVITY_WITH_METADATA = 6; 340 int GET_INSTALLED_PACKAGES = 7; 341 int CHECK_PACKAGE_CHANGES = 8; 342 int GET_APPLICATION_RESOURCES = 9; 343 int RESOURCE_NAME_LOOKUP = 10; 344 int GET_LAUNCHER_ACTIVITY = 11; 345 int CHECK_LAUNCHER_ACTIVITY = 12; 346 int IS_ACTIVITY_ENABLED = 13; 347 int PACKAGE_UPDATE_CHECK = 14; 348 int ASYNC_PRELOAD_USER_DELAY = 15; 349 int GET_DEFAULT_LAUNCHER = 16; 350 351 int COUNT = GET_DEFAULT_LAUNCHER + 1; 352 } 353 354 private static final String[] STAT_LABELS = { 355 "getHomeActivities()", 356 "Launcher permission check", 357 "getPackageInfo()", 358 "getPackageInfo(SIG)", 359 "getApplicationInfo", 360 "cleanupDanglingBitmaps", 361 "getActivity+metadata", 362 "getInstalledPackages", 363 "checkPackageChanges", 364 "getApplicationResources", 365 "resourceNameLookup", 366 "getLauncherActivity", 367 "checkLauncherActivity", 368 "isActivityEnabled", 369 "packageUpdateCheck", 370 "asyncPreloadUserDelay", 371 "getDefaultLauncher()" 372 }; 373 374 final Object mStatLock = new Object(); 375 376 @GuardedBy("mStatLock") 377 private final int[] mCountStats = new int[Stats.COUNT]; 378 379 @GuardedBy("mStatLock") 380 private final long[] mDurationStats = new long[Stats.COUNT]; 381 382 private static final int PROCESS_STATE_FOREGROUND_THRESHOLD = 383 ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; 384 385 static final int OPERATION_SET = 0; 386 static final int OPERATION_ADD = 1; 387 static final int OPERATION_UPDATE = 2; 388 389 /** @hide */ 390 @IntDef(value = { 391 OPERATION_SET, 392 OPERATION_ADD, 393 OPERATION_UPDATE 394 }) 395 @Retention(RetentionPolicy.SOURCE) 396 @interface ShortcutOperation { 397 } 398 399 @GuardedBy("mLock") 400 private int mWtfCount = 0; 401 402 @GuardedBy("mLock") 403 private Exception mLastWtfStacktrace; 404 405 static class InvalidFileFormatException extends Exception { 406 public InvalidFileFormatException(String message, Throwable cause) { 407 super(message, cause); 408 } 409 } 410 411 public ShortcutService(Context context) { 412 this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false); 413 } 414 415 @VisibleForTesting 416 ShortcutService(Context context, Looper looper, boolean onlyForPackageManagerApis) { 417 mContext = Preconditions.checkNotNull(context); 418 LocalServices.addService(ShortcutServiceInternal.class, new LocalService()); 419 mHandler = new Handler(looper); 420 mIPackageManager = AppGlobals.getPackageManager(); 421 mPackageManagerInternal = Preconditions.checkNotNull( 422 LocalServices.getService(PackageManagerInternal.class)); 423 mUserManager = Preconditions.checkNotNull(context.getSystemService(UserManager.class)); 424 mUsageStatsManagerInternal = Preconditions.checkNotNull( 425 LocalServices.getService(UsageStatsManagerInternal.class)); 426 mActivityManagerInternal = Preconditions.checkNotNull( 427 LocalServices.getService(ActivityManagerInternal.class)); 428 429 mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock); 430 mShortcutBitmapSaver = new ShortcutBitmapSaver(this); 431 432 if (onlyForPackageManagerApis) { 433 return; // Don't do anything further. For unit tests only. 434 } 435 436 // Register receivers. 437 438 // We need to set a priority, so let's just not use PackageMonitor for now. 439 // TODO Refactor PackageMonitor to support priorities. 440 final IntentFilter packageFilter = new IntentFilter(); 441 packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 442 packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 443 packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); 444 packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); 445 packageFilter.addDataScheme("package"); 446 packageFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 447 mContext.registerReceiverAsUser(mPackageMonitor, UserHandle.ALL, 448 packageFilter, null, mHandler); 449 450 final IntentFilter preferedActivityFilter = new IntentFilter(); 451 preferedActivityFilter.addAction(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED); 452 preferedActivityFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 453 mContext.registerReceiverAsUser(mPackageMonitor, UserHandle.ALL, 454 preferedActivityFilter, null, mHandler); 455 456 final IntentFilter localeFilter = new IntentFilter(); 457 localeFilter.addAction(Intent.ACTION_LOCALE_CHANGED); 458 localeFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 459 mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, 460 localeFilter, null, mHandler); 461 462 injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE 463 | ActivityManager.UID_OBSERVER_GONE); 464 } 465 466 void logDurationStat(int statId, long start) { 467 synchronized (mStatLock) { 468 mCountStats[statId]++; 469 mDurationStats[statId] += (injectElapsedRealtime() - start); 470 } 471 } 472 473 public String injectGetLocaleTagsForUser(@UserIdInt int userId) { 474 // TODO This should get the per-user locale. b/30123329 b/30119489 475 return LocaleList.getDefault().toLanguageTags(); 476 } 477 478 final private IUidObserver mUidObserver = new IUidObserver.Stub() { 479 @Override 480 public void onUidStateChanged(int uid, int procState, long procStateSeq) 481 throws RemoteException { 482 handleOnUidStateChanged(uid, procState); 483 } 484 485 @Override 486 public void onUidGone(int uid, boolean disabled) throws RemoteException { 487 handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT); 488 } 489 490 @Override 491 public void onUidActive(int uid) throws RemoteException { 492 } 493 494 @Override 495 public void onUidIdle(int uid, boolean disabled) throws RemoteException { 496 } 497 }; 498 499 void handleOnUidStateChanged(int uid, int procState) { 500 if (DEBUG_PROCSTATE) { 501 Slog.d(TAG, "onUidStateChanged: uid=" + uid + " state=" + procState); 502 } 503 synchronized (mLock) { 504 mUidState.put(uid, procState); 505 506 // We need to keep track of last time an app comes to foreground. 507 // See ShortcutPackage.getApiCallCount() for how it's used. 508 // It doesn't have to be persisted, but it needs to be the elapsed time. 509 if (isProcessStateForeground(procState)) { 510 mUidLastForegroundElapsedTime.put(uid, injectElapsedRealtime()); 511 } 512 } 513 } 514 515 private boolean isProcessStateForeground(int processState) { 516 return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD; 517 } 518 519 boolean isUidForegroundLocked(int uid) { 520 if (uid == Process.SYSTEM_UID) { 521 // IUidObserver doesn't report the state of SYSTEM, but it always has bound services, 522 // so it's foreground anyway. 523 return true; 524 } 525 // First, check with the local cache. 526 if (isProcessStateForeground(mUidState.get(uid, ActivityManager.MAX_PROCESS_STATE))) { 527 return true; 528 } 529 // If the cache says background, reach out to AM. Since it'll internally need to hold 530 // the AM lock, we use it as a last resort. 531 return isProcessStateForeground(mActivityManagerInternal.getUidProcessState(uid)); 532 } 533 534 long getUidLastForegroundElapsedTimeLocked(int uid) { 535 return mUidLastForegroundElapsedTime.get(uid); 536 } 537 538 /** 539 * System service lifecycle. 540 */ 541 public static final class Lifecycle extends SystemService { 542 final ShortcutService mService; 543 544 public Lifecycle(Context context) { 545 super(context); 546 mService = new ShortcutService(context); 547 } 548 549 @Override 550 public void onStart() { 551 publishBinderService(Context.SHORTCUT_SERVICE, mService); 552 } 553 554 @Override 555 public void onBootPhase(int phase) { 556 mService.onBootPhase(phase); 557 } 558 559 @Override 560 public void onStopUser(int userHandle) { 561 mService.handleStopUser(userHandle); 562 } 563 564 @Override 565 public void onUnlockUser(int userId) { 566 mService.handleUnlockUser(userId); 567 } 568 } 569 570 /** lifecycle event */ 571 void onBootPhase(int phase) { 572 if (DEBUG) { 573 Slog.d(TAG, "onBootPhase: " + phase); 574 } 575 switch (phase) { 576 case SystemService.PHASE_LOCK_SETTINGS_READY: 577 initialize(); 578 break; 579 case SystemService.PHASE_BOOT_COMPLETED: 580 mBootCompleted.set(true); 581 break; 582 } 583 } 584 585 /** lifecycle event */ 586 void handleUnlockUser(int userId) { 587 if (DEBUG) { 588 Slog.d(TAG, "handleUnlockUser: user=" + userId); 589 } 590 synchronized (mLock) { 591 mUnlockedUsers.put(userId, true); 592 } 593 594 // Preload the user data. 595 // Note, we don't use mHandler here but instead just start a new thread. 596 // This is because mHandler (which uses com.android.internal.os.BackgroundThread) is very 597 // busy at this point and this could take hundreds of milliseconds, which would be too 598 // late since the launcher would already have started. 599 // So we just create a new thread. This code runs rarely, so we don't use a thread pool 600 // or anything. 601 final long start = injectElapsedRealtime(); 602 injectRunOnNewThread(() -> { 603 synchronized (mLock) { 604 logDurationStat(Stats.ASYNC_PRELOAD_USER_DELAY, start); 605 getUserShortcutsLocked(userId); 606 } 607 }); 608 } 609 610 /** lifecycle event */ 611 void handleStopUser(int userId) { 612 if (DEBUG) { 613 Slog.d(TAG, "handleStopUser: user=" + userId); 614 } 615 synchronized (mLock) { 616 unloadUserLocked(userId); 617 618 mUnlockedUsers.put(userId, false); 619 } 620 } 621 622 private void unloadUserLocked(int userId) { 623 if (DEBUG) { 624 Slog.d(TAG, "unloadUserLocked: user=" + userId); 625 } 626 // Save all dirty information. 627 saveDirtyInfo(); 628 629 // Unload 630 mUsers.delete(userId); 631 } 632 633 /** Return the base state file name */ 634 private AtomicFile getBaseStateFile() { 635 final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE); 636 path.mkdirs(); 637 return new AtomicFile(path); 638 } 639 640 /** 641 * Init the instance. (load the state file, etc) 642 */ 643 private void initialize() { 644 synchronized (mLock) { 645 loadConfigurationLocked(); 646 loadBaseStateLocked(); 647 } 648 } 649 650 /** 651 * Load the configuration from Settings. 652 */ 653 private void loadConfigurationLocked() { 654 updateConfigurationLocked(injectShortcutManagerConstants()); 655 } 656 657 /** 658 * Load the configuration from Settings. 659 */ 660 @VisibleForTesting 661 boolean updateConfigurationLocked(String config) { 662 boolean result = true; 663 664 final KeyValueListParser parser = new KeyValueListParser(','); 665 try { 666 parser.setString(config); 667 } catch (IllegalArgumentException e) { 668 // Failed to parse the settings string, log this and move on 669 // with defaults. 670 Slog.e(TAG, "Bad shortcut manager settings", e); 671 result = false; 672 } 673 674 mSaveDelayMillis = Math.max(0, (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS, 675 DEFAULT_SAVE_DELAY_MS)); 676 677 mResetInterval = Math.max(1, parser.getLong( 678 ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC) 679 * 1000L); 680 681 mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong( 682 ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL)); 683 684 mMaxShortcuts = Math.max(0, (int) parser.getLong( 685 ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP)); 686 687 final int iconDimensionDp = Math.max(1, injectIsLowRamDevice() 688 ? (int) parser.getLong( 689 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM, 690 DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP) 691 : (int) parser.getLong( 692 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP, 693 DEFAULT_MAX_ICON_DIMENSION_DP)); 694 695 mMaxIconDimension = injectDipToPixel(iconDimensionDp); 696 697 mIconPersistFormat = CompressFormat.valueOf( 698 parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT)); 699 700 mIconPersistQuality = (int) parser.getLong( 701 ConfigConstants.KEY_ICON_QUALITY, 702 DEFAULT_ICON_PERSIST_QUALITY); 703 704 return result; 705 } 706 707 @VisibleForTesting 708 String injectShortcutManagerConstants() { 709 return android.provider.Settings.Global.getString( 710 mContext.getContentResolver(), 711 android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS); 712 } 713 714 @VisibleForTesting 715 int injectDipToPixel(int dip) { 716 return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, 717 mContext.getResources().getDisplayMetrics()); 718 } 719 720 // === Persisting === 721 722 @Nullable 723 static String parseStringAttribute(XmlPullParser parser, String attribute) { 724 return parser.getAttributeValue(null, attribute); 725 } 726 727 static boolean parseBooleanAttribute(XmlPullParser parser, String attribute) { 728 return parseLongAttribute(parser, attribute) == 1; 729 } 730 731 static int parseIntAttribute(XmlPullParser parser, String attribute) { 732 return (int) parseLongAttribute(parser, attribute); 733 } 734 735 static int parseIntAttribute(XmlPullParser parser, String attribute, int def) { 736 return (int) parseLongAttribute(parser, attribute, def); 737 } 738 739 static long parseLongAttribute(XmlPullParser parser, String attribute) { 740 return parseLongAttribute(parser, attribute, 0); 741 } 742 743 static long parseLongAttribute(XmlPullParser parser, String attribute, long def) { 744 final String value = parseStringAttribute(parser, attribute); 745 if (TextUtils.isEmpty(value)) { 746 return def; 747 } 748 try { 749 return Long.parseLong(value); 750 } catch (NumberFormatException e) { 751 Slog.e(TAG, "Error parsing long " + value); 752 return def; 753 } 754 } 755 756 @Nullable 757 static ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) { 758 final String value = parseStringAttribute(parser, attribute); 759 if (TextUtils.isEmpty(value)) { 760 return null; 761 } 762 return ComponentName.unflattenFromString(value); 763 } 764 765 @Nullable 766 static Intent parseIntentAttributeNoDefault(XmlPullParser parser, String attribute) { 767 final String value = parseStringAttribute(parser, attribute); 768 Intent parsed = null; 769 if (!TextUtils.isEmpty(value)) { 770 try { 771 parsed = Intent.parseUri(value, /* flags =*/ 0); 772 } catch (URISyntaxException e) { 773 Slog.e(TAG, "Error parsing intent", e); 774 } 775 } 776 return parsed; 777 } 778 779 @Nullable 780 static Intent parseIntentAttribute(XmlPullParser parser, String attribute) { 781 Intent parsed = parseIntentAttributeNoDefault(parser, attribute); 782 if (parsed == null) { 783 // Default intent. 784 parsed = new Intent(Intent.ACTION_VIEW); 785 } 786 return parsed; 787 } 788 789 static void writeTagValue(XmlSerializer out, String tag, String value) throws IOException { 790 if (TextUtils.isEmpty(value)) return; 791 792 out.startTag(null, tag); 793 out.attribute(null, ATTR_VALUE, value); 794 out.endTag(null, tag); 795 } 796 797 static void writeTagValue(XmlSerializer out, String tag, long value) throws IOException { 798 writeTagValue(out, tag, Long.toString(value)); 799 } 800 801 static void writeTagValue(XmlSerializer out, String tag, ComponentName name) throws IOException { 802 if (name == null) return; 803 writeTagValue(out, tag, name.flattenToString()); 804 } 805 806 static void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle) 807 throws IOException, XmlPullParserException { 808 if (bundle == null) return; 809 810 out.startTag(null, tag); 811 bundle.saveToXml(out); 812 out.endTag(null, tag); 813 } 814 815 static void writeAttr(XmlSerializer out, String name, CharSequence value) throws IOException { 816 if (TextUtils.isEmpty(value)) return; 817 818 out.attribute(null, name, value.toString()); 819 } 820 821 static void writeAttr(XmlSerializer out, String name, long value) throws IOException { 822 writeAttr(out, name, String.valueOf(value)); 823 } 824 825 static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException { 826 if (value) { 827 writeAttr(out, name, "1"); 828 } 829 } 830 831 static void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException { 832 if (comp == null) return; 833 writeAttr(out, name, comp.flattenToString()); 834 } 835 836 static void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException { 837 if (intent == null) return; 838 839 writeAttr(out, name, intent.toUri(/* flags =*/ 0)); 840 } 841 842 @VisibleForTesting 843 void saveBaseStateLocked() { 844 final AtomicFile file = getBaseStateFile(); 845 if (DEBUG) { 846 Slog.d(TAG, "Saving to " + file.getBaseFile()); 847 } 848 849 FileOutputStream outs = null; 850 try { 851 outs = file.startWrite(); 852 853 // Write to XML 854 XmlSerializer out = new FastXmlSerializer(); 855 out.setOutput(outs, StandardCharsets.UTF_8.name()); 856 out.startDocument(null, true); 857 out.startTag(null, TAG_ROOT); 858 859 // Body. 860 writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime); 861 862 // Epilogue. 863 out.endTag(null, TAG_ROOT); 864 out.endDocument(); 865 866 // Close. 867 file.finishWrite(outs); 868 } catch (IOException e) { 869 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); 870 file.failWrite(outs); 871 } 872 } 873 874 private void loadBaseStateLocked() { 875 mRawLastResetTime = 0; 876 877 final AtomicFile file = getBaseStateFile(); 878 if (DEBUG) { 879 Slog.d(TAG, "Loading from " + file.getBaseFile()); 880 } 881 try (FileInputStream in = file.openRead()) { 882 XmlPullParser parser = Xml.newPullParser(); 883 parser.setInput(in, StandardCharsets.UTF_8.name()); 884 885 int type; 886 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 887 if (type != XmlPullParser.START_TAG) { 888 continue; 889 } 890 final int depth = parser.getDepth(); 891 // Check the root tag 892 final String tag = parser.getName(); 893 if (depth == 1) { 894 if (!TAG_ROOT.equals(tag)) { 895 Slog.e(TAG, "Invalid root tag: " + tag); 896 return; 897 } 898 continue; 899 } 900 // Assume depth == 2 901 switch (tag) { 902 case TAG_LAST_RESET_TIME: 903 mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE); 904 break; 905 default: 906 Slog.e(TAG, "Invalid tag: " + tag); 907 break; 908 } 909 } 910 } catch (FileNotFoundException e) { 911 // Use the default 912 } catch (IOException | XmlPullParserException e) { 913 Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); 914 915 mRawLastResetTime = 0; 916 } 917 // Adjust the last reset time. 918 getLastResetTimeLocked(); 919 } 920 921 @VisibleForTesting 922 final File getUserFile(@UserIdInt int userId) { 923 return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); 924 } 925 926 private void saveUserLocked(@UserIdInt int userId) { 927 final File path = getUserFile(userId); 928 if (DEBUG) { 929 Slog.d(TAG, "Saving to " + path); 930 } 931 932 mShortcutBitmapSaver.waitForAllSavesLocked(); 933 934 path.getParentFile().mkdirs(); 935 final AtomicFile file = new AtomicFile(path); 936 FileOutputStream os = null; 937 try { 938 os = file.startWrite(); 939 940 saveUserInternalLocked(userId, os, /* forBackup= */ false); 941 942 file.finishWrite(os); 943 944 // Remove all dangling bitmap files. 945 cleanupDanglingBitmapDirectoriesLocked(userId); 946 } catch (XmlPullParserException | IOException e) { 947 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); 948 file.failWrite(os); 949 } 950 } 951 952 private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os, 953 boolean forBackup) throws IOException, XmlPullParserException { 954 955 final BufferedOutputStream bos = new BufferedOutputStream(os); 956 957 // Write to XML 958 XmlSerializer out = new FastXmlSerializer(); 959 out.setOutput(bos, StandardCharsets.UTF_8.name()); 960 out.startDocument(null, true); 961 962 getUserShortcutsLocked(userId).saveToXml(out, forBackup); 963 964 out.endDocument(); 965 966 bos.flush(); 967 os.flush(); 968 } 969 970 static IOException throwForInvalidTag(int depth, String tag) throws IOException { 971 throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth)); 972 } 973 974 static void warnForInvalidTag(int depth, String tag) throws IOException { 975 Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth)); 976 } 977 978 @Nullable 979 private ShortcutUser loadUserLocked(@UserIdInt int userId) { 980 final File path = getUserFile(userId); 981 if (DEBUG) { 982 Slog.d(TAG, "Loading from " + path); 983 } 984 final AtomicFile file = new AtomicFile(path); 985 986 final FileInputStream in; 987 try { 988 in = file.openRead(); 989 } catch (FileNotFoundException e) { 990 if (DEBUG) { 991 Slog.d(TAG, "Not found " + path); 992 } 993 return null; 994 } 995 try { 996 final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false); 997 return ret; 998 } catch (IOException | XmlPullParserException | InvalidFileFormatException e) { 999 Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); 1000 return null; 1001 } finally { 1002 IoUtils.closeQuietly(in); 1003 } 1004 } 1005 1006 private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, 1007 boolean fromBackup) throws XmlPullParserException, IOException, 1008 InvalidFileFormatException { 1009 1010 final BufferedInputStream bis = new BufferedInputStream(is); 1011 1012 ShortcutUser ret = null; 1013 XmlPullParser parser = Xml.newPullParser(); 1014 parser.setInput(bis, StandardCharsets.UTF_8.name()); 1015 1016 int type; 1017 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 1018 if (type != XmlPullParser.START_TAG) { 1019 continue; 1020 } 1021 final int depth = parser.getDepth(); 1022 1023 final String tag = parser.getName(); 1024 if (DEBUG_LOAD) { 1025 Slog.d(TAG, String.format("depth=%d type=%d name=%s", 1026 depth, type, tag)); 1027 } 1028 if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) { 1029 ret = ShortcutUser.loadFromXml(this, parser, userId, fromBackup); 1030 continue; 1031 } 1032 throwForInvalidTag(depth, tag); 1033 } 1034 return ret; 1035 } 1036 1037 private void scheduleSaveBaseState() { 1038 scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state. 1039 } 1040 1041 void scheduleSaveUser(@UserIdInt int userId) { 1042 scheduleSaveInner(userId); 1043 } 1044 1045 // In order to re-schedule, we need to reuse the same instance, so keep it in final. 1046 private final Runnable mSaveDirtyInfoRunner = this::saveDirtyInfo; 1047 1048 private void scheduleSaveInner(@UserIdInt int userId) { 1049 if (DEBUG) { 1050 Slog.d(TAG, "Scheduling to save for " + userId); 1051 } 1052 synchronized (mLock) { 1053 if (!mDirtyUserIds.contains(userId)) { 1054 mDirtyUserIds.add(userId); 1055 } 1056 } 1057 // If already scheduled, remove that and re-schedule in N seconds. 1058 mHandler.removeCallbacks(mSaveDirtyInfoRunner); 1059 mHandler.postDelayed(mSaveDirtyInfoRunner, mSaveDelayMillis); 1060 } 1061 1062 @VisibleForTesting 1063 void saveDirtyInfo() { 1064 if (DEBUG) { 1065 Slog.d(TAG, "saveDirtyInfo"); 1066 } 1067 try { 1068 synchronized (mLock) { 1069 for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) { 1070 final int userId = mDirtyUserIds.get(i); 1071 if (userId == UserHandle.USER_NULL) { // USER_NULL for base state. 1072 saveBaseStateLocked(); 1073 } else { 1074 saveUserLocked(userId); 1075 } 1076 } 1077 mDirtyUserIds.clear(); 1078 } 1079 } catch (Exception e) { 1080 wtf("Exception in saveDirtyInfo", e); 1081 } 1082 } 1083 1084 /** Return the last reset time. */ 1085 long getLastResetTimeLocked() { 1086 updateTimesLocked(); 1087 return mRawLastResetTime; 1088 } 1089 1090 /** Return the next reset time. */ 1091 long getNextResetTimeLocked() { 1092 updateTimesLocked(); 1093 return mRawLastResetTime + mResetInterval; 1094 } 1095 1096 static boolean isClockValid(long time) { 1097 return time >= 1420070400; // Thu, 01 Jan 2015 00:00:00 GMT 1098 } 1099 1100 /** 1101 * Update the last reset time. 1102 */ 1103 private void updateTimesLocked() { 1104 1105 final long now = injectCurrentTimeMillis(); 1106 1107 final long prevLastResetTime = mRawLastResetTime; 1108 1109 if (mRawLastResetTime == 0) { // first launch. 1110 // TODO Randomize?? 1111 mRawLastResetTime = now; 1112 } else if (now < mRawLastResetTime) { 1113 // Clock rewound. 1114 if (isClockValid(now)) { 1115 Slog.w(TAG, "Clock rewound"); 1116 // TODO Randomize?? 1117 mRawLastResetTime = now; 1118 } 1119 } else { 1120 if ((mRawLastResetTime + mResetInterval) <= now) { 1121 final long offset = mRawLastResetTime % mResetInterval; 1122 mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset; 1123 } 1124 } 1125 if (prevLastResetTime != mRawLastResetTime) { 1126 scheduleSaveBaseState(); 1127 } 1128 } 1129 1130 // Requires mLock held, but "Locked" prefix would look weired so we just say "L". 1131 protected boolean isUserUnlockedL(@UserIdInt int userId) { 1132 // First, check the local copy. 1133 if (mUnlockedUsers.get(userId)) { 1134 return true; 1135 } 1136 // If the local copy says the user is locked, check with AM for the actual state, since 1137 // the user might just have been unlocked. 1138 // Note we just don't use isUserUnlockingOrUnlocked() here, because it'll return false 1139 // when the user is STOPPING, which we still want to consider as "unlocked". 1140 final long token = injectClearCallingIdentity(); 1141 try { 1142 return mUserManager.isUserUnlockingOrUnlocked(userId); 1143 } finally { 1144 injectRestoreCallingIdentity(token); 1145 } 1146 } 1147 1148 // Requires mLock held, but "Locked" prefix would look weired so we jsut say "L". 1149 void throwIfUserLockedL(@UserIdInt int userId) { 1150 if (!isUserUnlockedL(userId)) { 1151 throw new IllegalStateException("User " + userId + " is locked or not running"); 1152 } 1153 } 1154 1155 @GuardedBy("mLock") 1156 @NonNull 1157 private boolean isUserLoadedLocked(@UserIdInt int userId) { 1158 return mUsers.get(userId) != null; 1159 } 1160 1161 /** Return the per-user state. */ 1162 @GuardedBy("mLock") 1163 @NonNull 1164 ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) { 1165 if (!isUserUnlockedL(userId)) { 1166 wtf("User still locked"); 1167 } 1168 1169 ShortcutUser userPackages = mUsers.get(userId); 1170 if (userPackages == null) { 1171 userPackages = loadUserLocked(userId); 1172 if (userPackages == null) { 1173 userPackages = new ShortcutUser(this, userId); 1174 } 1175 mUsers.put(userId, userPackages); 1176 1177 // Also when a user's data is first accessed, scan all packages. 1178 checkPackageChanges(userId); 1179 } 1180 return userPackages; 1181 } 1182 1183 void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) { 1184 for (int i = mUsers.size() - 1; i >= 0; i--) { 1185 c.accept(mUsers.valueAt(i)); 1186 } 1187 } 1188 1189 /** 1190 * Return the per-user per-package state. If the caller is a publisher, use 1191 * {@link #getPackageShortcutsForPublisherLocked} instead. 1192 */ 1193 @GuardedBy("mLock") 1194 @NonNull 1195 ShortcutPackage getPackageShortcutsLocked( 1196 @NonNull String packageName, @UserIdInt int userId) { 1197 return getUserShortcutsLocked(userId).getPackageShortcuts(packageName); 1198 } 1199 1200 /** Return the per-user per-package state. Use this when the caller is a publisher. */ 1201 @GuardedBy("mLock") 1202 @NonNull 1203 ShortcutPackage getPackageShortcutsForPublisherLocked( 1204 @NonNull String packageName, @UserIdInt int userId) { 1205 final ShortcutPackage ret = getUserShortcutsLocked(userId).getPackageShortcuts(packageName); 1206 ret.getUser().onCalledByPublisher(packageName); 1207 return ret; 1208 } 1209 1210 @GuardedBy("mLock") 1211 @NonNull 1212 ShortcutLauncher getLauncherShortcutsLocked( 1213 @NonNull String packageName, @UserIdInt int ownerUserId, 1214 @UserIdInt int launcherUserId) { 1215 return getUserShortcutsLocked(ownerUserId) 1216 .getLauncherShortcuts(packageName, launcherUserId); 1217 } 1218 1219 // === Caller validation === 1220 1221 void removeIconLocked(ShortcutInfo shortcut) { 1222 mShortcutBitmapSaver.removeIcon(shortcut); 1223 } 1224 1225 public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) { 1226 final File packagePath = new File(getUserBitmapFilePath(userId), packageName); 1227 if (!packagePath.isDirectory()) { 1228 return; 1229 } 1230 if (!(FileUtils.deleteContents(packagePath) && packagePath.delete())) { 1231 Slog.w(TAG, "Unable to remove directory " + packagePath); 1232 } 1233 } 1234 1235 /** 1236 * Remove dangling bitmap files for a user. 1237 * 1238 * Note this method must be called with the lock held after calling 1239 * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap 1240 * saves are going on. 1241 */ 1242 private void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) { 1243 if (DEBUG) { 1244 Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId); 1245 } 1246 final long start = injectElapsedRealtime(); 1247 1248 final ShortcutUser user = getUserShortcutsLocked(userId); 1249 1250 final File bitmapDir = getUserBitmapFilePath(userId); 1251 final File[] children = bitmapDir.listFiles(); 1252 if (children == null) { 1253 return; 1254 } 1255 for (File child : children) { 1256 if (!child.isDirectory()) { 1257 continue; 1258 } 1259 final String packageName = child.getName(); 1260 if (DEBUG) { 1261 Slog.d(TAG, "cleanupDanglingBitmaps: Found directory=" + packageName); 1262 } 1263 if (!user.hasPackage(packageName)) { 1264 if (DEBUG) { 1265 Slog.d(TAG, "Removing dangling bitmap directory: " + packageName); 1266 } 1267 cleanupBitmapsForPackage(userId, packageName); 1268 } else { 1269 cleanupDanglingBitmapFilesLocked(userId, user, packageName, child); 1270 } 1271 } 1272 logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start); 1273 } 1274 1275 /** 1276 * Remove dangling bitmap files for a package. 1277 * 1278 * Note this method must be called with the lock held after calling 1279 * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap 1280 * saves are going on. 1281 */ 1282 private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user, 1283 @NonNull String packageName, @NonNull File path) { 1284 final ArraySet<String> usedFiles = 1285 user.getPackageShortcuts(packageName).getUsedBitmapFiles(); 1286 1287 for (File child : path.listFiles()) { 1288 if (!child.isFile()) { 1289 continue; 1290 } 1291 final String name = child.getName(); 1292 if (!usedFiles.contains(name)) { 1293 if (DEBUG) { 1294 Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath()); 1295 } 1296 child.delete(); 1297 } 1298 } 1299 } 1300 1301 @VisibleForTesting 1302 static class FileOutputStreamWithPath extends FileOutputStream { 1303 private final File mFile; 1304 1305 public FileOutputStreamWithPath(File file) throws FileNotFoundException { 1306 super(file); 1307 mFile = file; 1308 } 1309 1310 public File getFile() { 1311 return mFile; 1312 } 1313 } 1314 1315 /** 1316 * Build the cached bitmap filename for a shortcut icon. 1317 * 1318 * The filename will be based on the ID, except certain characters will be escaped. 1319 */ 1320 FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut) 1321 throws IOException { 1322 final File packagePath = new File(getUserBitmapFilePath(userId), 1323 shortcut.getPackage()); 1324 if (!packagePath.isDirectory()) { 1325 packagePath.mkdirs(); 1326 if (!packagePath.isDirectory()) { 1327 throw new IOException("Unable to create directory " + packagePath); 1328 } 1329 SELinux.restorecon(packagePath); 1330 } 1331 1332 final String baseName = String.valueOf(injectCurrentTimeMillis()); 1333 for (int suffix = 0; ; suffix++) { 1334 final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png"; 1335 final File file = new File(packagePath, filename); 1336 if (!file.exists()) { 1337 if (DEBUG) { 1338 Slog.d(TAG, "Saving icon to " + file.getAbsolutePath()); 1339 } 1340 return new FileOutputStreamWithPath(file); 1341 } 1342 } 1343 } 1344 1345 void saveIconAndFixUpShortcutLocked(ShortcutInfo shortcut) { 1346 if (shortcut.hasIconFile() || shortcut.hasIconResource()) { 1347 return; 1348 } 1349 1350 final long token = injectClearCallingIdentity(); 1351 try { 1352 // Clear icon info on the shortcut. 1353 removeIconLocked(shortcut); 1354 1355 final Icon icon = shortcut.getIcon(); 1356 if (icon == null) { 1357 return; // has no icon 1358 } 1359 1360 Bitmap bitmap; 1361 try { 1362 switch (icon.getType()) { 1363 case Icon.TYPE_RESOURCE: { 1364 injectValidateIconResPackage(shortcut, icon); 1365 1366 shortcut.setIconResourceId(icon.getResId()); 1367 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES); 1368 return; 1369 } 1370 case Icon.TYPE_BITMAP: 1371 case Icon.TYPE_ADAPTIVE_BITMAP: { 1372 bitmap = icon.getBitmap(); // Don't recycle in this case. 1373 break; 1374 } 1375 default: 1376 // This shouldn't happen because we've already validated the icon, but 1377 // just in case. 1378 throw ShortcutInfo.getInvalidIconException(); 1379 } 1380 mShortcutBitmapSaver.saveBitmapLocked(shortcut, 1381 mMaxIconDimension, mIconPersistFormat, mIconPersistQuality); 1382 } finally { 1383 // Once saved, we won't use the original icon information, so null it out. 1384 shortcut.clearIcon(); 1385 } 1386 } finally { 1387 injectRestoreCallingIdentity(token); 1388 } 1389 } 1390 1391 // Unfortunately we can't do this check in unit tests because we fake creator package names, 1392 // so override in unit tests. 1393 // TODO CTS this case. 1394 void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) { 1395 if (!shortcut.getPackage().equals(icon.getResPackage())) { 1396 throw new IllegalArgumentException( 1397 "Icon resource must reside in shortcut owner package"); 1398 } 1399 } 1400 1401 static Bitmap shrinkBitmap(Bitmap in, int maxSize) { 1402 // Original width/height. 1403 final int ow = in.getWidth(); 1404 final int oh = in.getHeight(); 1405 if ((ow <= maxSize) && (oh <= maxSize)) { 1406 if (DEBUG) { 1407 Slog.d(TAG, String.format("Icon size %dx%d, no need to shrink", ow, oh)); 1408 } 1409 return in; 1410 } 1411 final int longerDimension = Math.max(ow, oh); 1412 1413 // New width and height. 1414 final int nw = ow * maxSize / longerDimension; 1415 final int nh = oh * maxSize / longerDimension; 1416 if (DEBUG) { 1417 Slog.d(TAG, String.format("Icon size %dx%d, shrinking to %dx%d", 1418 ow, oh, nw, nh)); 1419 } 1420 1421 final Bitmap scaledBitmap = Bitmap.createBitmap(nw, nh, Bitmap.Config.ARGB_8888); 1422 final Canvas c = new Canvas(scaledBitmap); 1423 1424 final RectF dst = new RectF(0, 0, nw, nh); 1425 1426 c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null); 1427 1428 return scaledBitmap; 1429 } 1430 1431 /** 1432 * For a shortcut, update all resource names from resource IDs, and also update all 1433 * resource-based strings. 1434 */ 1435 void fixUpShortcutResourceNamesAndValues(ShortcutInfo si) { 1436 final Resources publisherRes = injectGetResourcesForApplicationAsUser( 1437 si.getPackage(), si.getUserId()); 1438 if (publisherRes != null) { 1439 final long start = injectElapsedRealtime(); 1440 try { 1441 si.lookupAndFillInResourceNames(publisherRes); 1442 } finally { 1443 logDurationStat(Stats.RESOURCE_NAME_LOOKUP, start); 1444 } 1445 si.resolveResourceStrings(publisherRes); 1446 } 1447 } 1448 1449 // === Caller validation === 1450 1451 private boolean isCallerSystem() { 1452 final int callingUid = injectBinderCallingUid(); 1453 return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID); 1454 } 1455 1456 private boolean isCallerShell() { 1457 final int callingUid = injectBinderCallingUid(); 1458 return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID; 1459 } 1460 1461 private void enforceSystemOrShell() { 1462 if (!(isCallerSystem() || isCallerShell())) { 1463 throw new SecurityException("Caller must be system or shell"); 1464 } 1465 } 1466 1467 private void enforceShell() { 1468 if (!isCallerShell()) { 1469 throw new SecurityException("Caller must be shell"); 1470 } 1471 } 1472 1473 private void enforceSystem() { 1474 if (!isCallerSystem()) { 1475 throw new SecurityException("Caller must be system"); 1476 } 1477 } 1478 1479 private void enforceResetThrottlingPermission() { 1480 if (isCallerSystem()) { 1481 return; 1482 } 1483 enforceCallingOrSelfPermission( 1484 android.Manifest.permission.RESET_SHORTCUT_MANAGER_THROTTLING, null); 1485 } 1486 1487 private void enforceCallingOrSelfPermission( 1488 @NonNull String permission, @Nullable String message) { 1489 if (isCallerSystem()) { 1490 return; 1491 } 1492 injectEnforceCallingPermission(permission, message); 1493 } 1494 1495 /** 1496 * Somehow overriding ServiceContext.enforceCallingPermission() in the unit tests would confuse 1497 * mockito. So instead we extracted it here and override it in the tests. 1498 */ 1499 @VisibleForTesting 1500 void injectEnforceCallingPermission( 1501 @NonNull String permission, @Nullable String message) { 1502 mContext.enforceCallingPermission(permission, message); 1503 } 1504 1505 private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) { 1506 Preconditions.checkStringNotEmpty(packageName, "packageName"); 1507 1508 if (isCallerSystem()) { 1509 return; // no check 1510 } 1511 1512 final int callingUid = injectBinderCallingUid(); 1513 1514 // Otherwise, make sure the arguments are valid. 1515 if (UserHandle.getUserId(callingUid) != userId) { 1516 throw new SecurityException("Invalid user-ID"); 1517 } 1518 if (injectGetPackageUid(packageName, userId) != callingUid) { 1519 throw new SecurityException("Calling package name mismatch"); 1520 } 1521 Preconditions.checkState(!isEphemeralApp(packageName, userId), 1522 "Ephemeral apps can't use ShortcutManager"); 1523 } 1524 1525 // Overridden in unit tests to execute r synchronously. 1526 void injectPostToHandler(Runnable r) { 1527 mHandler.post(r); 1528 } 1529 1530 void injectRunOnNewThread(Runnable r) { 1531 new Thread(r).start(); 1532 } 1533 1534 /** 1535 * @throws IllegalArgumentException if {@code numShortcuts} is bigger than 1536 * {@link #getMaxActivityShortcuts()}. 1537 */ 1538 void enforceMaxActivityShortcuts(int numShortcuts) { 1539 if (numShortcuts > mMaxShortcuts) { 1540 throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded"); 1541 } 1542 } 1543 1544 /** 1545 * Return the max number of dynamic + manifest shortcuts for each launcher icon. 1546 */ 1547 int getMaxActivityShortcuts() { 1548 return mMaxShortcuts; 1549 } 1550 1551 /** 1552 * - Sends a notification to LauncherApps 1553 * - Write to file 1554 */ 1555 void packageShortcutsChanged(@NonNull String packageName, @UserIdInt int userId) { 1556 notifyListeners(packageName, userId); 1557 scheduleSaveUser(userId); 1558 } 1559 1560 private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) { 1561 if (DEBUG) { 1562 Slog.d(TAG, String.format( 1563 "Shortcut changes: package=%s, user=%d", packageName, userId)); 1564 } 1565 injectPostToHandler(() -> { 1566 try { 1567 final ArrayList<ShortcutChangeListener> copy; 1568 synchronized (mLock) { 1569 if (!isUserUnlockedL(userId)) { 1570 return; 1571 } 1572 1573 copy = new ArrayList<>(mListeners); 1574 } 1575 // Note onShortcutChanged() needs to be called with the system service permissions. 1576 for (int i = copy.size() - 1; i >= 0; i--) { 1577 copy.get(i).onShortcutChanged(packageName, userId); 1578 } 1579 } catch (Exception ignore) { 1580 } 1581 }); 1582 } 1583 1584 /** 1585 * Clean up / validate an incoming shortcut. 1586 * - Make sure all mandatory fields are set. 1587 * - Make sure the intent's extras are persistable, and them to set 1588 * {@link ShortcutInfo#mIntentPersistableExtrases}. Also clear its extras. 1589 * - Clear flags. 1590 */ 1591 private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate, 1592 boolean forPinRequest) { 1593 if (shortcut.isReturnedByServer()) { 1594 Log.w(TAG, 1595 "Re-publishing ShortcutInfo returned by server is not supported." 1596 + " Some information such as icon may lost from shortcut."); 1597 } 1598 Preconditions.checkNotNull(shortcut, "Null shortcut detected"); 1599 if (shortcut.getActivity() != null) { 1600 Preconditions.checkState( 1601 shortcut.getPackage().equals(shortcut.getActivity().getPackageName()), 1602 "Cannot publish shortcut: activity " + shortcut.getActivity() + " does not" 1603 + " belong to package " + shortcut.getPackage()); 1604 Preconditions.checkState( 1605 injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()), 1606 "Cannot publish shortcut: activity " + shortcut.getActivity() + " is not" 1607 + " main activity"); 1608 } 1609 1610 if (!forUpdate) { 1611 shortcut.enforceMandatoryFields(/* forPinned= */ forPinRequest); 1612 if (!forPinRequest) { 1613 Preconditions.checkState(shortcut.getActivity() != null, 1614 "Cannot publish shortcut: target activity is not set"); 1615 } 1616 } 1617 if (shortcut.getIcon() != null) { 1618 ShortcutInfo.validateIcon(shortcut.getIcon()); 1619 } 1620 1621 shortcut.replaceFlags(0); 1622 } 1623 1624 private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) { 1625 fixUpIncomingShortcutInfo(shortcut, forUpdate, /*forPinRequest=*/ false); 1626 } 1627 1628 public void validateShortcutForPinRequest(@NonNull ShortcutInfo shortcut) { 1629 fixUpIncomingShortcutInfo(shortcut, /* forUpdate= */ false, /*forPinRequest=*/ true); 1630 } 1631 1632 /** 1633 * When a shortcut has no target activity, set the default one from the package. 1634 */ 1635 private void fillInDefaultActivity(List<ShortcutInfo> shortcuts) { 1636 ComponentName defaultActivity = null; 1637 for (int i = shortcuts.size() - 1; i >= 0; i--) { 1638 final ShortcutInfo si = shortcuts.get(i); 1639 if (si.getActivity() == null) { 1640 if (defaultActivity == null) { 1641 defaultActivity = injectGetDefaultMainActivity( 1642 si.getPackage(), si.getUserId()); 1643 Preconditions.checkState(defaultActivity != null, 1644 "Launcher activity not found for package " + si.getPackage()); 1645 } 1646 si.setActivity(defaultActivity); 1647 } 1648 } 1649 } 1650 1651 private void assignImplicitRanks(List<ShortcutInfo> shortcuts) { 1652 for (int i = shortcuts.size() - 1; i >= 0; i--) { 1653 shortcuts.get(i).setImplicitRank(i); 1654 } 1655 } 1656 1657 private List<ShortcutInfo> setReturnedByServer(List<ShortcutInfo> shortcuts) { 1658 for (int i = shortcuts.size() - 1; i >= 0; i--) { 1659 shortcuts.get(i).setReturnedByServer(); 1660 } 1661 return shortcuts; 1662 } 1663 1664 // === APIs === 1665 1666 @Override 1667 public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, 1668 @UserIdInt int userId) { 1669 verifyCaller(packageName, userId); 1670 1671 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); 1672 final int size = newShortcuts.size(); 1673 1674 synchronized (mLock) { 1675 throwIfUserLockedL(userId); 1676 1677 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 1678 1679 ps.ensureImmutableShortcutsNotIncluded(newShortcuts); 1680 1681 fillInDefaultActivity(newShortcuts); 1682 1683 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET); 1684 1685 // Throttling. 1686 if (!ps.tryApiCall()) { 1687 return false; 1688 } 1689 1690 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 1691 ps.clearAllImplicitRanks(); 1692 assignImplicitRanks(newShortcuts); 1693 1694 for (int i = 0; i < size; i++) { 1695 fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false); 1696 } 1697 1698 // First, remove all un-pinned; dynamic shortcuts 1699 ps.deleteAllDynamicShortcuts(); 1700 1701 // Then, add/update all. We need to make sure to take over "pinned" flag. 1702 for (int i = 0; i < size; i++) { 1703 final ShortcutInfo newShortcut = newShortcuts.get(i); 1704 ps.addOrUpdateDynamicShortcut(newShortcut); 1705 } 1706 1707 // Lastly, adjust the ranks. 1708 ps.adjustRanks(); 1709 } 1710 packageShortcutsChanged(packageName, userId); 1711 1712 verifyStates(); 1713 1714 return true; 1715 } 1716 1717 @Override 1718 public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, 1719 @UserIdInt int userId) { 1720 verifyCaller(packageName, userId); 1721 1722 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); 1723 final int size = newShortcuts.size(); 1724 1725 synchronized (mLock) { 1726 throwIfUserLockedL(userId); 1727 1728 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 1729 1730 ps.ensureImmutableShortcutsNotIncluded(newShortcuts); 1731 1732 // For update, don't fill in the default activity. Having null activity means 1733 // "don't update the activity" here. 1734 1735 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE); 1736 1737 // Throttling. 1738 if (!ps.tryApiCall()) { 1739 return false; 1740 } 1741 1742 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 1743 ps.clearAllImplicitRanks(); 1744 assignImplicitRanks(newShortcuts); 1745 1746 for (int i = 0; i < size; i++) { 1747 final ShortcutInfo source = newShortcuts.get(i); 1748 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true); 1749 1750 final ShortcutInfo target = ps.findShortcutById(source.getId()); 1751 if (target == null) { 1752 continue; 1753 } 1754 1755 if (target.isEnabled() != source.isEnabled()) { 1756 Slog.w(TAG, 1757 "ShortcutInfo.enabled cannot be changed with updateShortcuts()"); 1758 } 1759 1760 // When updating the rank, we need to insert between existing ranks, so set 1761 // this setRankChanged, and also copy the implicit rank fo adjustRanks(). 1762 if (source.hasRank()) { 1763 target.setRankChanged(); 1764 target.setImplicitRank(source.getImplicitRank()); 1765 } 1766 1767 final boolean replacingIcon = (source.getIcon() != null); 1768 if (replacingIcon) { 1769 removeIconLocked(target); 1770 } 1771 1772 // Note copyNonNullFieldsFrom() does the "updatable with?" check too. 1773 target.copyNonNullFieldsFrom(source); 1774 target.setTimestamp(injectCurrentTimeMillis()); 1775 1776 if (replacingIcon) { 1777 saveIconAndFixUpShortcutLocked(target); 1778 } 1779 1780 // When we're updating any resource related fields, re-extract the res names and 1781 // the values. 1782 if (replacingIcon || source.hasStringResources()) { 1783 fixUpShortcutResourceNamesAndValues(target); 1784 } 1785 } 1786 1787 // Lastly, adjust the ranks. 1788 ps.adjustRanks(); 1789 } 1790 packageShortcutsChanged(packageName, userId); 1791 1792 verifyStates(); 1793 1794 return true; 1795 } 1796 1797 @Override 1798 public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, 1799 @UserIdInt int userId) { 1800 verifyCaller(packageName, userId); 1801 1802 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); 1803 final int size = newShortcuts.size(); 1804 1805 synchronized (mLock) { 1806 throwIfUserLockedL(userId); 1807 1808 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 1809 1810 ps.ensureImmutableShortcutsNotIncluded(newShortcuts); 1811 1812 fillInDefaultActivity(newShortcuts); 1813 1814 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD); 1815 1816 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 1817 ps.clearAllImplicitRanks(); 1818 assignImplicitRanks(newShortcuts); 1819 1820 // Throttling. 1821 if (!ps.tryApiCall()) { 1822 return false; 1823 } 1824 for (int i = 0; i < size; i++) { 1825 final ShortcutInfo newShortcut = newShortcuts.get(i); 1826 1827 // Validate the shortcut. 1828 fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false); 1829 1830 // When ranks are changing, we need to insert between ranks, so set the 1831 // "rank changed" flag. 1832 newShortcut.setRankChanged(); 1833 1834 // Add it. 1835 ps.addOrUpdateDynamicShortcut(newShortcut); 1836 } 1837 1838 // Lastly, adjust the ranks. 1839 ps.adjustRanks(); 1840 } 1841 packageShortcutsChanged(packageName, userId); 1842 1843 verifyStates(); 1844 1845 return true; 1846 } 1847 1848 @Override 1849 public boolean requestPinShortcut(String packageName, ShortcutInfo shortcut, 1850 IntentSender resultIntent, int userId) { 1851 Preconditions.checkNotNull(shortcut); 1852 Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); 1853 return requestPinItem(packageName, userId, shortcut, null, null, resultIntent); 1854 } 1855 1856 @Override 1857 public Intent createShortcutResultIntent(String packageName, ShortcutInfo shortcut, int userId) 1858 throws RemoteException { 1859 Preconditions.checkNotNull(shortcut); 1860 Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); 1861 verifyCaller(packageName, userId); 1862 1863 final Intent ret; 1864 synchronized (mLock) { 1865 throwIfUserLockedL(userId); 1866 1867 // Send request to the launcher, if supported. 1868 ret = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, userId); 1869 } 1870 1871 verifyStates(); 1872 return ret; 1873 } 1874 1875 /** 1876 * Handles {@link #requestPinShortcut} and {@link ShortcutServiceInternal#requestPinAppWidget}. 1877 * After validating the caller, it passes the request to {@link #mShortcutRequestPinProcessor}. 1878 * Either {@param shortcut} or {@param appWidget} should be non-null. 1879 */ 1880 private boolean requestPinItem(String packageName, int userId, ShortcutInfo shortcut, 1881 AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent) { 1882 verifyCaller(packageName, userId); 1883 1884 final boolean ret; 1885 synchronized (mLock) { 1886 throwIfUserLockedL(userId); 1887 1888 Preconditions.checkState(isUidForegroundLocked(injectBinderCallingUid()), 1889 "Calling application must have a foreground activity or a foreground service"); 1890 1891 // Send request to the launcher, if supported. 1892 ret = mShortcutRequestPinProcessor.requestPinItemLocked(shortcut, appWidget, extras, 1893 userId, resultIntent); 1894 } 1895 1896 verifyStates(); 1897 1898 return ret; 1899 } 1900 1901 @Override 1902 public void disableShortcuts(String packageName, List shortcutIds, 1903 CharSequence disabledMessage, int disabledMessageResId, @UserIdInt int userId) { 1904 verifyCaller(packageName, userId); 1905 Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); 1906 1907 synchronized (mLock) { 1908 throwIfUserLockedL(userId); 1909 1910 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 1911 1912 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds); 1913 1914 final String disabledMessageString = 1915 (disabledMessage == null) ? null : disabledMessage.toString(); 1916 1917 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 1918 ps.disableWithId(Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)), 1919 disabledMessageString, disabledMessageResId, 1920 /* overrideImmutable=*/ false); 1921 } 1922 1923 // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks. 1924 ps.adjustRanks(); 1925 } 1926 packageShortcutsChanged(packageName, userId); 1927 1928 verifyStates(); 1929 } 1930 1931 @Override 1932 public void enableShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) { 1933 verifyCaller(packageName, userId); 1934 Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); 1935 1936 synchronized (mLock) { 1937 throwIfUserLockedL(userId); 1938 1939 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 1940 1941 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds); 1942 1943 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 1944 ps.enableWithId((String) shortcutIds.get(i)); 1945 } 1946 } 1947 packageShortcutsChanged(packageName, userId); 1948 1949 verifyStates(); 1950 } 1951 1952 @Override 1953 public void removeDynamicShortcuts(String packageName, List shortcutIds, 1954 @UserIdInt int userId) { 1955 verifyCaller(packageName, userId); 1956 Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); 1957 1958 synchronized (mLock) { 1959 throwIfUserLockedL(userId); 1960 1961 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 1962 1963 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds); 1964 1965 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 1966 ps.deleteDynamicWithId( 1967 Preconditions.checkStringNotEmpty((String) shortcutIds.get(i))); 1968 } 1969 1970 // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks. 1971 ps.adjustRanks(); 1972 } 1973 packageShortcutsChanged(packageName, userId); 1974 1975 verifyStates(); 1976 } 1977 1978 @Override 1979 public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) { 1980 verifyCaller(packageName, userId); 1981 1982 synchronized (mLock) { 1983 throwIfUserLockedL(userId); 1984 1985 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 1986 ps.deleteAllDynamicShortcuts(); 1987 } 1988 packageShortcutsChanged(packageName, userId); 1989 1990 verifyStates(); 1991 } 1992 1993 @Override 1994 public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName, 1995 @UserIdInt int userId) { 1996 verifyCaller(packageName, userId); 1997 1998 synchronized (mLock) { 1999 throwIfUserLockedL(userId); 2000 2001 return getShortcutsWithQueryLocked( 2002 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, 2003 ShortcutInfo::isDynamic); 2004 } 2005 } 2006 2007 @Override 2008 public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName, 2009 @UserIdInt int userId) { 2010 verifyCaller(packageName, userId); 2011 2012 synchronized (mLock) { 2013 throwIfUserLockedL(userId); 2014 2015 return getShortcutsWithQueryLocked( 2016 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, 2017 ShortcutInfo::isManifestShortcut); 2018 } 2019 } 2020 2021 @Override 2022 public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName, 2023 @UserIdInt int userId) { 2024 verifyCaller(packageName, userId); 2025 2026 synchronized (mLock) { 2027 throwIfUserLockedL(userId); 2028 2029 return getShortcutsWithQueryLocked( 2030 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, 2031 ShortcutInfo::isPinned); 2032 } 2033 } 2034 2035 private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName, 2036 @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) { 2037 2038 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 2039 2040 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2041 ps.findAll(ret, query, cloneFlags); 2042 2043 return new ParceledListSlice<>(setReturnedByServer(ret)); 2044 } 2045 2046 @Override 2047 public int getMaxShortcutCountPerActivity(String packageName, @UserIdInt int userId) 2048 throws RemoteException { 2049 verifyCaller(packageName, userId); 2050 2051 return mMaxShortcuts; 2052 } 2053 2054 @Override 2055 public int getRemainingCallCount(String packageName, @UserIdInt int userId) { 2056 verifyCaller(packageName, userId); 2057 2058 synchronized (mLock) { 2059 throwIfUserLockedL(userId); 2060 2061 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2062 return mMaxUpdatesPerInterval - ps.getApiCallCount(); 2063 } 2064 } 2065 2066 @Override 2067 public long getRateLimitResetTime(String packageName, @UserIdInt int userId) { 2068 verifyCaller(packageName, userId); 2069 2070 synchronized (mLock) { 2071 throwIfUserLockedL(userId); 2072 2073 return getNextResetTimeLocked(); 2074 } 2075 } 2076 2077 @Override 2078 public int getIconMaxDimensions(String packageName, int userId) { 2079 verifyCaller(packageName, userId); 2080 2081 synchronized (mLock) { 2082 return mMaxIconDimension; 2083 } 2084 } 2085 2086 @Override 2087 public void reportShortcutUsed(String packageName, String shortcutId, int userId) { 2088 verifyCaller(packageName, userId); 2089 2090 Preconditions.checkNotNull(shortcutId); 2091 2092 if (DEBUG) { 2093 Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d", 2094 shortcutId, packageName, userId)); 2095 } 2096 2097 synchronized (mLock) { 2098 throwIfUserLockedL(userId); 2099 2100 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2101 2102 if (ps.findShortcutById(shortcutId) == null) { 2103 Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s", 2104 packageName, shortcutId)); 2105 return; 2106 } 2107 } 2108 2109 final long token = injectClearCallingIdentity(); 2110 try { 2111 mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId); 2112 } finally { 2113 injectRestoreCallingIdentity(token); 2114 } 2115 } 2116 2117 @Override 2118 public boolean isRequestPinItemSupported(int callingUserId, int requestType) { 2119 final long token = injectClearCallingIdentity(); 2120 try { 2121 return mShortcutRequestPinProcessor 2122 .isRequestPinItemSupported(callingUserId, requestType); 2123 } finally { 2124 injectRestoreCallingIdentity(token); 2125 } 2126 } 2127 2128 /** 2129 * Reset all throttling, for developer options and command line. Only system/shell can call 2130 * it. 2131 */ 2132 @Override 2133 public void resetThrottling() { 2134 enforceSystemOrShell(); 2135 2136 resetThrottlingInner(getCallingUserId()); 2137 } 2138 2139 void resetThrottlingInner(@UserIdInt int userId) { 2140 synchronized (mLock) { 2141 if (!isUserUnlockedL(userId)) { 2142 Log.w(TAG, "User " + userId + " is locked or not running"); 2143 return; 2144 } 2145 2146 getUserShortcutsLocked(userId).resetThrottling(); 2147 } 2148 scheduleSaveUser(userId); 2149 Slog.i(TAG, "ShortcutManager: throttling counter reset for user " + userId); 2150 } 2151 2152 void resetAllThrottlingInner() { 2153 synchronized (mLock) { 2154 mRawLastResetTime = injectCurrentTimeMillis(); 2155 } 2156 scheduleSaveBaseState(); 2157 Slog.i(TAG, "ShortcutManager: throttling counter reset for all users"); 2158 } 2159 2160 @Override 2161 public void onApplicationActive(String packageName, int userId) { 2162 if (DEBUG) { 2163 Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId); 2164 } 2165 enforceResetThrottlingPermission(); 2166 2167 synchronized (mLock) { 2168 if (!isUserUnlockedL(userId)) { 2169 // This is called by system UI, so no need to throw. Just ignore. 2170 return; 2171 } 2172 2173 getPackageShortcutsLocked(packageName, userId) 2174 .resetRateLimitingForCommandLineNoSaving(); 2175 saveUserLocked(userId); 2176 } 2177 } 2178 2179 // We override this method in unit tests to do a simpler check. 2180 boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) { 2181 final long start = injectElapsedRealtime(); 2182 try { 2183 return hasShortcutHostPermissionInner(callingPackage, userId); 2184 } finally { 2185 logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start); 2186 } 2187 } 2188 2189 // This method is extracted so we can directly call this method from unit tests, 2190 // even when hasShortcutPermission() is overridden. 2191 @VisibleForTesting 2192 boolean hasShortcutHostPermissionInner(@NonNull String packageName, int userId) { 2193 synchronized (mLock) { 2194 throwIfUserLockedL(userId); 2195 2196 final ShortcutUser user = getUserShortcutsLocked(userId); 2197 2198 // Always trust the cached component. 2199 final ComponentName cached = user.getCachedLauncher(); 2200 if (cached != null) { 2201 if (cached.getPackageName().equals(packageName)) { 2202 return true; 2203 } 2204 } 2205 // If the cached one doesn't match, then go ahead 2206 2207 final ComponentName detected = getDefaultLauncher(userId); 2208 2209 // Update the cache. 2210 user.setLauncher(detected); 2211 if (detected != null) { 2212 if (DEBUG) { 2213 Slog.v(TAG, "Detected launcher: " + detected); 2214 } 2215 return detected.getPackageName().equals(packageName); 2216 } else { 2217 // Default launcher not found. 2218 return false; 2219 } 2220 } 2221 } 2222 2223 @Nullable 2224 ComponentName getDefaultLauncher(@UserIdInt int userId) { 2225 final long start = injectElapsedRealtime(); 2226 final long token = injectClearCallingIdentity(); 2227 try { 2228 synchronized (mLock) { 2229 throwIfUserLockedL(userId); 2230 2231 final ShortcutUser user = getUserShortcutsLocked(userId); 2232 2233 final List<ResolveInfo> allHomeCandidates = new ArrayList<>(); 2234 2235 // Default launcher from package manager. 2236 final long startGetHomeActivitiesAsUser = injectElapsedRealtime(); 2237 final ComponentName defaultLauncher = mPackageManagerInternal 2238 .getHomeActivitiesAsUser(allHomeCandidates, userId); 2239 logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser); 2240 2241 ComponentName detected = null; 2242 if (defaultLauncher != null) { 2243 detected = defaultLauncher; 2244 if (DEBUG) { 2245 Slog.v(TAG, "Default launcher from PM: " + detected); 2246 } 2247 } else { 2248 detected = user.getLastKnownLauncher(); 2249 2250 if (detected != null) { 2251 if (injectIsActivityEnabledAndExported(detected, userId)) { 2252 if (DEBUG) { 2253 Slog.v(TAG, "Cached launcher: " + detected); 2254 } 2255 } else { 2256 Slog.w(TAG, "Cached launcher " + detected + " no longer exists"); 2257 detected = null; 2258 user.clearLauncher(); 2259 } 2260 } 2261 } 2262 2263 if (detected == null) { 2264 // If we reach here, that means it's the first check since the user was created, 2265 // and there's already multiple launchers and there's no default set. 2266 // Find the system one with the highest priority. 2267 // (We need to check the priority too because of FallbackHome in Settings.) 2268 // If there's no system launcher yet, then no one can access shortcuts, until 2269 // the user explicitly 2270 final int size = allHomeCandidates.size(); 2271 2272 int lastPriority = Integer.MIN_VALUE; 2273 for (int i = 0; i < size; i++) { 2274 final ResolveInfo ri = allHomeCandidates.get(i); 2275 if (!ri.activityInfo.applicationInfo.isSystemApp()) { 2276 continue; 2277 } 2278 if (DEBUG) { 2279 Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d", 2280 ri.activityInfo.getComponentName(), ri.priority)); 2281 } 2282 if (ri.priority < lastPriority) { 2283 continue; 2284 } 2285 detected = ri.activityInfo.getComponentName(); 2286 lastPriority = ri.priority; 2287 } 2288 } 2289 return detected; 2290 } 2291 } finally { 2292 injectRestoreCallingIdentity(token); 2293 logDurationStat(Stats.GET_DEFAULT_LAUNCHER, start); 2294 } 2295 } 2296 2297 // === House keeping === 2298 2299 private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId, 2300 boolean appStillExists) { 2301 synchronized (mLock) { 2302 forEachLoadedUserLocked(user -> 2303 cleanUpPackageLocked(packageName, user.getUserId(), packageUserId, 2304 appStillExists)); 2305 } 2306 } 2307 2308 /** 2309 * Remove all the information associated with a package. This will really remove all the 2310 * information, including the restore information (i.e. it'll remove packages even if they're 2311 * shadow). 2312 * 2313 * This is called when an app is uninstalled, or an app gets "clear data"ed. 2314 */ 2315 @VisibleForTesting 2316 void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId, 2317 boolean appStillExists) { 2318 final boolean wasUserLoaded = isUserLoadedLocked(owningUserId); 2319 2320 final ShortcutUser user = getUserShortcutsLocked(owningUserId); 2321 boolean doNotify = false; 2322 2323 // First, remove the package from the package list (if the package is a publisher). 2324 if (packageUserId == owningUserId) { 2325 if (user.removePackage(packageName) != null) { 2326 doNotify = true; 2327 } 2328 } 2329 2330 // Also remove from the launcher list (if the package is a launcher). 2331 user.removeLauncher(packageUserId, packageName); 2332 2333 // Then remove pinned shortcuts from all launchers. 2334 user.forAllLaunchers(l -> l.cleanUpPackage(packageName, packageUserId)); 2335 2336 // Now there may be orphan shortcuts because we removed pinned shortcuts at the previous 2337 // step. Remove them too. 2338 user.forAllPackages(p -> p.refreshPinnedFlags()); 2339 2340 scheduleSaveUser(owningUserId); 2341 2342 if (doNotify) { 2343 notifyListeners(packageName, owningUserId); 2344 } 2345 2346 // If the app still exists (i.e. data cleared), we need to re-publish manifest shortcuts. 2347 if (appStillExists && (packageUserId == owningUserId)) { 2348 // This will do the notification and save when needed, so do it after the above 2349 // notifyListeners. 2350 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 2351 } 2352 2353 if (!wasUserLoaded) { 2354 // Note this will execute the scheduled save. 2355 unloadUserLocked(owningUserId); 2356 } 2357 } 2358 2359 /** 2360 * Entry point from {@link LauncherApps}. 2361 */ 2362 private class LocalService extends ShortcutServiceInternal { 2363 2364 @Override 2365 public List<ShortcutInfo> getShortcuts(int launcherUserId, 2366 @NonNull String callingPackage, long changedSince, 2367 @Nullable String packageName, @Nullable List<String> shortcutIds, 2368 @Nullable ComponentName componentName, 2369 int queryFlags, int userId) { 2370 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 2371 2372 final boolean cloneKeyFieldOnly = 2373 ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0); 2374 final int cloneFlag = cloneKeyFieldOnly ? ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO 2375 : ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER; 2376 if (packageName == null) { 2377 shortcutIds = null; // LauncherAppsService already threw for it though. 2378 } 2379 2380 synchronized (mLock) { 2381 throwIfUserLockedL(userId); 2382 throwIfUserLockedL(launcherUserId); 2383 2384 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 2385 .attemptToRestoreIfNeededAndSave(); 2386 2387 if (packageName != null) { 2388 getShortcutsInnerLocked(launcherUserId, 2389 callingPackage, packageName, shortcutIds, changedSince, 2390 componentName, queryFlags, userId, ret, cloneFlag); 2391 } else { 2392 final List<String> shortcutIdsF = shortcutIds; 2393 getUserShortcutsLocked(userId).forAllPackages(p -> { 2394 getShortcutsInnerLocked(launcherUserId, 2395 callingPackage, p.getPackageName(), shortcutIdsF, changedSince, 2396 componentName, queryFlags, userId, ret, cloneFlag); 2397 }); 2398 } 2399 } 2400 return setReturnedByServer(ret); 2401 } 2402 2403 private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage, 2404 @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince, 2405 @Nullable ComponentName componentName, int queryFlags, 2406 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) { 2407 final ArraySet<String> ids = shortcutIds == null ? null 2408 : new ArraySet<>(shortcutIds); 2409 2410 final ShortcutPackage p = getUserShortcutsLocked(userId) 2411 .getPackageShortcutsIfExists(packageName); 2412 if (p == null) { 2413 return; // No need to instantiate ShortcutPackage. 2414 } 2415 2416 p.findAll(ret, 2417 (ShortcutInfo si) -> { 2418 if (si.getLastChangedTimestamp() < changedSince) { 2419 return false; 2420 } 2421 if (ids != null && !ids.contains(si.getId())) { 2422 return false; 2423 } 2424 if (componentName != null) { 2425 if (si.getActivity() != null 2426 && !si.getActivity().equals(componentName)) { 2427 return false; 2428 } 2429 } 2430 if (((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0) 2431 && si.isDynamic()) { 2432 return true; 2433 } 2434 if (((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0) 2435 && si.isPinned()) { 2436 return true; 2437 } 2438 if (((queryFlags & ShortcutQuery.FLAG_GET_MANIFEST) != 0) 2439 && si.isManifestShortcut()) { 2440 return true; 2441 } 2442 return false; 2443 }, cloneFlag, callingPackage, launcherUserId); 2444 } 2445 2446 @Override 2447 public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, 2448 @NonNull String packageName, @NonNull String shortcutId, int userId) { 2449 Preconditions.checkStringNotEmpty(packageName, "packageName"); 2450 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 2451 2452 synchronized (mLock) { 2453 throwIfUserLockedL(userId); 2454 throwIfUserLockedL(launcherUserId); 2455 2456 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 2457 .attemptToRestoreIfNeededAndSave(); 2458 2459 final ShortcutInfo si = getShortcutInfoLocked( 2460 launcherUserId, callingPackage, packageName, shortcutId, userId); 2461 return si != null && si.isPinned(); 2462 } 2463 } 2464 2465 private ShortcutInfo getShortcutInfoLocked( 2466 int launcherUserId, @NonNull String callingPackage, 2467 @NonNull String packageName, @NonNull String shortcutId, int userId) { 2468 Preconditions.checkStringNotEmpty(packageName, "packageName"); 2469 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 2470 2471 throwIfUserLockedL(userId); 2472 throwIfUserLockedL(launcherUserId); 2473 2474 final ShortcutPackage p = getUserShortcutsLocked(userId) 2475 .getPackageShortcutsIfExists(packageName); 2476 if (p == null) { 2477 return null; 2478 } 2479 2480 final ArrayList<ShortcutInfo> list = new ArrayList<>(1); 2481 p.findAll(list, 2482 (ShortcutInfo si) -> shortcutId.equals(si.getId()), 2483 /* clone flags=*/ 0, callingPackage, launcherUserId); 2484 return list.size() == 0 ? null : list.get(0); 2485 } 2486 2487 @Override 2488 public void pinShortcuts(int launcherUserId, 2489 @NonNull String callingPackage, @NonNull String packageName, 2490 @NonNull List<String> shortcutIds, int userId) { 2491 // Calling permission must be checked by LauncherAppsImpl. 2492 Preconditions.checkStringNotEmpty(packageName, "packageName"); 2493 Preconditions.checkNotNull(shortcutIds, "shortcutIds"); 2494 2495 synchronized (mLock) { 2496 throwIfUserLockedL(userId); 2497 throwIfUserLockedL(launcherUserId); 2498 2499 final ShortcutLauncher launcher = 2500 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId); 2501 launcher.attemptToRestoreIfNeededAndSave(); 2502 2503 launcher.pinShortcuts(userId, packageName, shortcutIds); 2504 } 2505 packageShortcutsChanged(packageName, userId); 2506 2507 verifyStates(); 2508 } 2509 2510 @Override 2511 public Intent[] createShortcutIntents(int launcherUserId, 2512 @NonNull String callingPackage, 2513 @NonNull String packageName, @NonNull String shortcutId, int userId) { 2514 // Calling permission must be checked by LauncherAppsImpl. 2515 Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty"); 2516 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty"); 2517 2518 synchronized (mLock) { 2519 throwIfUserLockedL(userId); 2520 throwIfUserLockedL(launcherUserId); 2521 2522 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 2523 .attemptToRestoreIfNeededAndSave(); 2524 2525 // Make sure the shortcut is actually visible to the launcher. 2526 final ShortcutInfo si = getShortcutInfoLocked( 2527 launcherUserId, callingPackage, packageName, shortcutId, userId); 2528 // "si == null" should suffice here, but check the flags too just to make sure. 2529 if (si == null || !si.isEnabled() || !si.isAlive()) { 2530 Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled"); 2531 return null; 2532 } 2533 return si.getIntents(); 2534 } 2535 } 2536 2537 @Override 2538 public void addListener(@NonNull ShortcutChangeListener listener) { 2539 synchronized (mLock) { 2540 mListeners.add(Preconditions.checkNotNull(listener)); 2541 } 2542 } 2543 2544 @Override 2545 public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage, 2546 @NonNull String packageName, @NonNull String shortcutId, int userId) { 2547 Preconditions.checkNotNull(callingPackage, "callingPackage"); 2548 Preconditions.checkNotNull(packageName, "packageName"); 2549 Preconditions.checkNotNull(shortcutId, "shortcutId"); 2550 2551 synchronized (mLock) { 2552 throwIfUserLockedL(userId); 2553 throwIfUserLockedL(launcherUserId); 2554 2555 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 2556 .attemptToRestoreIfNeededAndSave(); 2557 2558 final ShortcutPackage p = getUserShortcutsLocked(userId) 2559 .getPackageShortcutsIfExists(packageName); 2560 if (p == null) { 2561 return 0; 2562 } 2563 2564 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 2565 return (shortcutInfo != null && shortcutInfo.hasIconResource()) 2566 ? shortcutInfo.getIconResourceId() : 0; 2567 } 2568 } 2569 2570 @Override 2571 public ParcelFileDescriptor getShortcutIconFd(int launcherUserId, 2572 @NonNull String callingPackage, @NonNull String packageName, 2573 @NonNull String shortcutId, int userId) { 2574 Preconditions.checkNotNull(callingPackage, "callingPackage"); 2575 Preconditions.checkNotNull(packageName, "packageName"); 2576 Preconditions.checkNotNull(shortcutId, "shortcutId"); 2577 2578 synchronized (mLock) { 2579 throwIfUserLockedL(userId); 2580 throwIfUserLockedL(launcherUserId); 2581 2582 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 2583 .attemptToRestoreIfNeededAndSave(); 2584 2585 final ShortcutPackage p = getUserShortcutsLocked(userId) 2586 .getPackageShortcutsIfExists(packageName); 2587 if (p == null) { 2588 return null; 2589 } 2590 2591 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 2592 if (shortcutInfo == null || !shortcutInfo.hasIconFile()) { 2593 return null; 2594 } 2595 final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo); 2596 if (path == null) { 2597 Slog.w(TAG, "null bitmap detected in getShortcutIconFd()"); 2598 return null; 2599 } 2600 try { 2601 return ParcelFileDescriptor.open( 2602 new File(path), 2603 ParcelFileDescriptor.MODE_READ_ONLY); 2604 } catch (FileNotFoundException e) { 2605 Slog.e(TAG, "Icon file not found: " + path); 2606 return null; 2607 } 2608 } 2609 } 2610 2611 @Override 2612 public boolean hasShortcutHostPermission(int launcherUserId, 2613 @NonNull String callingPackage) { 2614 return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId); 2615 } 2616 2617 @Override 2618 public boolean requestPinAppWidget(@NonNull String callingPackage, 2619 @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras, 2620 @Nullable IntentSender resultIntent, int userId) { 2621 Preconditions.checkNotNull(appWidget); 2622 return requestPinItem(callingPackage, userId, null, appWidget, extras, resultIntent); 2623 } 2624 2625 @Override 2626 public boolean isRequestPinItemSupported(int callingUserId, int requestType) { 2627 return ShortcutService.this.isRequestPinItemSupported(callingUserId, requestType); 2628 } 2629 } 2630 2631 final BroadcastReceiver mReceiver = new BroadcastReceiver() { 2632 @Override 2633 public void onReceive(Context context, Intent intent) { 2634 if (!mBootCompleted.get()) { 2635 return; // Boot not completed, ignore the broadcast. 2636 } 2637 try { 2638 if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) { 2639 handleLocaleChanged(); 2640 } 2641 } catch (Exception e) { 2642 wtf("Exception in mReceiver.onReceive", e); 2643 } 2644 } 2645 }; 2646 2647 void handleLocaleChanged() { 2648 if (DEBUG) { 2649 Slog.d(TAG, "handleLocaleChanged"); 2650 } 2651 scheduleSaveBaseState(); 2652 2653 synchronized (mLock) { 2654 final long token = injectClearCallingIdentity(); 2655 try { 2656 forEachLoadedUserLocked(user -> user.detectLocaleChange()); 2657 } finally { 2658 injectRestoreCallingIdentity(token); 2659 } 2660 } 2661 } 2662 2663 /** 2664 * Package event callbacks. 2665 */ 2666 @VisibleForTesting 2667 final BroadcastReceiver mPackageMonitor = new BroadcastReceiver() { 2668 @Override 2669 public void onReceive(Context context, Intent intent) { 2670 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); 2671 if (userId == UserHandle.USER_NULL) { 2672 Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent); 2673 return; 2674 } 2675 2676 final String action = intent.getAction(); 2677 2678 // This is normally called on Handler, so clearCallingIdentity() isn't needed, 2679 // but we still check it in unit tests. 2680 final long token = injectClearCallingIdentity(); 2681 try { 2682 synchronized (mLock) { 2683 if (!isUserUnlockedL(userId)) { 2684 if (DEBUG) { 2685 Slog.d(TAG, "Ignoring package broadcast " + action 2686 + " for locked/stopped user " + userId); 2687 } 2688 return; 2689 } 2690 2691 // Whenever we get one of those package broadcasts, or get 2692 // ACTION_PREFERRED_ACTIVITY_CHANGED, we purge the default launcher cache. 2693 final ShortcutUser user = getUserShortcutsLocked(userId); 2694 user.clearLauncher(); 2695 } 2696 if (Intent.ACTION_PREFERRED_ACTIVITY_CHANGED.equals(action)) { 2697 // Nothing farther to do. 2698 return; 2699 } 2700 2701 final Uri intentUri = intent.getData(); 2702 final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart() 2703 : null; 2704 if (packageName == null) { 2705 Slog.w(TAG, "Intent broadcast does not contain package name: " + intent); 2706 return; 2707 } 2708 2709 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 2710 2711 switch (action) { 2712 case Intent.ACTION_PACKAGE_ADDED: 2713 if (replacing) { 2714 handlePackageUpdateFinished(packageName, userId); 2715 } else { 2716 handlePackageAdded(packageName, userId); 2717 } 2718 break; 2719 case Intent.ACTION_PACKAGE_REMOVED: 2720 if (!replacing) { 2721 handlePackageRemoved(packageName, userId); 2722 } 2723 break; 2724 case Intent.ACTION_PACKAGE_CHANGED: 2725 handlePackageChanged(packageName, userId); 2726 2727 break; 2728 case Intent.ACTION_PACKAGE_DATA_CLEARED: 2729 handlePackageDataCleared(packageName, userId); 2730 break; 2731 } 2732 } catch (Exception e) { 2733 wtf("Exception in mPackageMonitor.onReceive", e); 2734 } finally { 2735 injectRestoreCallingIdentity(token); 2736 } 2737 } 2738 }; 2739 2740 /** 2741 * Called when a user is unlocked. 2742 * - Check all known packages still exist, and otherwise perform cleanup. 2743 * - If a package still exists, check the version code. If it's been updated, may need to 2744 * update timestamps of its shortcuts. 2745 */ 2746 @VisibleForTesting 2747 void checkPackageChanges(@UserIdInt int ownerUserId) { 2748 if (DEBUG) { 2749 Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId); 2750 } 2751 if (injectIsSafeModeEnabled()) { 2752 Slog.i(TAG, "Safe mode, skipping checkPackageChanges()"); 2753 return; 2754 } 2755 2756 final long start = injectElapsedRealtime(); 2757 try { 2758 final ArrayList<PackageWithUser> gonePackages = new ArrayList<>(); 2759 2760 synchronized (mLock) { 2761 final ShortcutUser user = getUserShortcutsLocked(ownerUserId); 2762 2763 // Find packages that have been uninstalled. 2764 user.forAllPackageItems(spi -> { 2765 if (spi.getPackageInfo().isShadow()) { 2766 return; // Don't delete shadow information. 2767 } 2768 if (!isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) { 2769 if (DEBUG) { 2770 Slog.d(TAG, "Uninstalled: " + spi.getPackageName() 2771 + " user " + spi.getPackageUserId()); 2772 } 2773 gonePackages.add(PackageWithUser.of(spi)); 2774 } 2775 }); 2776 if (gonePackages.size() > 0) { 2777 for (int i = gonePackages.size() - 1; i >= 0; i--) { 2778 final PackageWithUser pu = gonePackages.get(i); 2779 cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId, 2780 /* appStillExists = */ false); 2781 } 2782 } 2783 2784 rescanUpdatedPackagesLocked(ownerUserId, user.getLastAppScanTime()); 2785 } 2786 } finally { 2787 logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start); 2788 } 2789 verifyStates(); 2790 } 2791 2792 private void rescanUpdatedPackagesLocked(@UserIdInt int userId, long lastScanTime) { 2793 final ShortcutUser user = getUserShortcutsLocked(userId); 2794 2795 // Note after each OTA, we'll need to rescan all system apps, as their lastUpdateTime 2796 // is not reliable. 2797 final long now = injectCurrentTimeMillis(); 2798 final boolean afterOta = 2799 !injectBuildFingerprint().equals(user.getLastAppScanOsFingerprint()); 2800 2801 // Then for each installed app, publish manifest shortcuts when needed. 2802 forUpdatedPackages(userId, lastScanTime, afterOta, ai -> { 2803 user.attemptToRestoreIfNeededAndSave(this, ai.packageName, userId); 2804 2805 user.rescanPackageIfNeeded(ai.packageName, /* forceRescan= */ true); 2806 }); 2807 2808 // Write the time just before the scan, because there may be apps that have just 2809 // been updated, and we want to catch them in the next time. 2810 user.setLastAppScanTime(now); 2811 user.setLastAppScanOsFingerprint(injectBuildFingerprint()); 2812 scheduleSaveUser(userId); 2813 } 2814 2815 private void handlePackageAdded(String packageName, @UserIdInt int userId) { 2816 if (DEBUG) { 2817 Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId)); 2818 } 2819 synchronized (mLock) { 2820 final ShortcutUser user = getUserShortcutsLocked(userId); 2821 user.attemptToRestoreIfNeededAndSave(this, packageName, userId); 2822 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 2823 } 2824 verifyStates(); 2825 } 2826 2827 private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) { 2828 if (DEBUG) { 2829 Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d", 2830 packageName, userId)); 2831 } 2832 synchronized (mLock) { 2833 final ShortcutUser user = getUserShortcutsLocked(userId); 2834 user.attemptToRestoreIfNeededAndSave(this, packageName, userId); 2835 2836 if (isPackageInstalled(packageName, userId)) { 2837 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 2838 } 2839 } 2840 verifyStates(); 2841 } 2842 2843 private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) { 2844 if (DEBUG) { 2845 Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, 2846 packageUserId)); 2847 } 2848 cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ false); 2849 2850 verifyStates(); 2851 } 2852 2853 private void handlePackageDataCleared(String packageName, int packageUserId) { 2854 if (DEBUG) { 2855 Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName, 2856 packageUserId)); 2857 } 2858 cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ true); 2859 2860 verifyStates(); 2861 } 2862 2863 private void handlePackageChanged(String packageName, int packageUserId) { 2864 if (!isPackageInstalled(packageName, packageUserId)) { 2865 // Probably disabled, which is the same thing as uninstalled. 2866 handlePackageRemoved(packageName, packageUserId); 2867 return; 2868 } 2869 if (DEBUG) { 2870 Slog.d(TAG, String.format("handlePackageChanged: %s user=%d", packageName, 2871 packageUserId)); 2872 } 2873 2874 // Activities may be disabled or enabled. Just rescan the package. 2875 synchronized (mLock) { 2876 final ShortcutUser user = getUserShortcutsLocked(packageUserId); 2877 2878 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 2879 } 2880 2881 verifyStates(); 2882 } 2883 2884 // === PackageManager interaction === 2885 2886 /** 2887 * Returns {@link PackageInfo} unless it's uninstalled or disabled. 2888 */ 2889 @Nullable 2890 final PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) { 2891 return getPackageInfo(packageName, userId, true); 2892 } 2893 2894 /** 2895 * Returns {@link PackageInfo} unless it's uninstalled or disabled. 2896 */ 2897 @Nullable 2898 final PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) { 2899 return getPackageInfo(packageName, userId, false); 2900 } 2901 2902 int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) { 2903 final long token = injectClearCallingIdentity(); 2904 try { 2905 return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS, userId); 2906 } catch (RemoteException e) { 2907 // Shouldn't happen. 2908 Slog.wtf(TAG, "RemoteException", e); 2909 return -1; 2910 } finally { 2911 injectRestoreCallingIdentity(token); 2912 } 2913 } 2914 2915 /** 2916 * Returns {@link PackageInfo} unless it's uninstalled or disabled. 2917 */ 2918 @Nullable 2919 @VisibleForTesting 2920 final PackageInfo getPackageInfo(String packageName, @UserIdInt int userId, 2921 boolean getSignatures) { 2922 return isInstalledOrNull(injectPackageInfoWithUninstalled( 2923 packageName, userId, getSignatures)); 2924 } 2925 2926 /** 2927 * Do not use directly; this returns uninstalled packages too. 2928 */ 2929 @Nullable 2930 @VisibleForTesting 2931 PackageInfo injectPackageInfoWithUninstalled(String packageName, @UserIdInt int userId, 2932 boolean getSignatures) { 2933 final long start = injectElapsedRealtime(); 2934 final long token = injectClearCallingIdentity(); 2935 try { 2936 return mIPackageManager.getPackageInfo( 2937 packageName, PACKAGE_MATCH_FLAGS 2938 | (getSignatures ? PackageManager.GET_SIGNATURES : 0), userId); 2939 } catch (RemoteException e) { 2940 // Shouldn't happen. 2941 Slog.wtf(TAG, "RemoteException", e); 2942 return null; 2943 } finally { 2944 injectRestoreCallingIdentity(token); 2945 2946 logDurationStat( 2947 (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO), 2948 start); 2949 } 2950 } 2951 2952 /** 2953 * Returns {@link ApplicationInfo} unless it's uninstalled or disabled. 2954 */ 2955 @Nullable 2956 @VisibleForTesting 2957 final ApplicationInfo getApplicationInfo(String packageName, @UserIdInt int userId) { 2958 return isInstalledOrNull(injectApplicationInfoWithUninstalled(packageName, userId)); 2959 } 2960 2961 /** 2962 * Do not use directly; this returns uninstalled packages too. 2963 */ 2964 @Nullable 2965 @VisibleForTesting 2966 ApplicationInfo injectApplicationInfoWithUninstalled( 2967 String packageName, @UserIdInt int userId) { 2968 final long start = injectElapsedRealtime(); 2969 final long token = injectClearCallingIdentity(); 2970 try { 2971 return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId); 2972 } catch (RemoteException e) { 2973 // Shouldn't happen. 2974 Slog.wtf(TAG, "RemoteException", e); 2975 return null; 2976 } finally { 2977 injectRestoreCallingIdentity(token); 2978 2979 logDurationStat(Stats.GET_APPLICATION_INFO, start); 2980 } 2981 } 2982 2983 /** 2984 * Returns {@link ActivityInfo} with its metadata unless it's uninstalled or disabled. 2985 */ 2986 @Nullable 2987 final ActivityInfo getActivityInfoWithMetadata(ComponentName activity, @UserIdInt int userId) { 2988 return isInstalledOrNull(injectGetActivityInfoWithMetadataWithUninstalled( 2989 activity, userId)); 2990 } 2991 2992 /** 2993 * Do not use directly; this returns uninstalled packages too. 2994 */ 2995 @Nullable 2996 @VisibleForTesting 2997 ActivityInfo injectGetActivityInfoWithMetadataWithUninstalled( 2998 ComponentName activity, @UserIdInt int userId) { 2999 final long start = injectElapsedRealtime(); 3000 final long token = injectClearCallingIdentity(); 3001 try { 3002 return mIPackageManager.getActivityInfo(activity, 3003 (PACKAGE_MATCH_FLAGS | PackageManager.GET_META_DATA), userId); 3004 } catch (RemoteException e) { 3005 // Shouldn't happen. 3006 Slog.wtf(TAG, "RemoteException", e); 3007 return null; 3008 } finally { 3009 injectRestoreCallingIdentity(token); 3010 3011 logDurationStat(Stats.GET_ACTIVITY_WITH_METADATA, start); 3012 } 3013 } 3014 3015 /** 3016 * Return all installed and enabled packages. 3017 */ 3018 @NonNull 3019 @VisibleForTesting 3020 final List<PackageInfo> getInstalledPackages(@UserIdInt int userId) { 3021 final long start = injectElapsedRealtime(); 3022 final long token = injectClearCallingIdentity(); 3023 try { 3024 final List<PackageInfo> all = injectGetPackagesWithUninstalled(userId); 3025 3026 all.removeIf(PACKAGE_NOT_INSTALLED); 3027 3028 return all; 3029 } catch (RemoteException e) { 3030 // Shouldn't happen. 3031 Slog.wtf(TAG, "RemoteException", e); 3032 return null; 3033 } finally { 3034 injectRestoreCallingIdentity(token); 3035 3036 logDurationStat(Stats.GET_INSTALLED_PACKAGES, start); 3037 } 3038 } 3039 3040 /** 3041 * Do not use directly; this returns uninstalled packages too. 3042 */ 3043 @NonNull 3044 @VisibleForTesting 3045 List<PackageInfo> injectGetPackagesWithUninstalled(@UserIdInt int userId) 3046 throws RemoteException { 3047 final ParceledListSlice<PackageInfo> parceledList = 3048 mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId); 3049 if (parceledList == null) { 3050 return Collections.emptyList(); 3051 } 3052 return parceledList.getList(); 3053 } 3054 3055 private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime, boolean afterOta, 3056 Consumer<ApplicationInfo> callback) { 3057 if (DEBUG) { 3058 Slog.d(TAG, "forUpdatedPackages for user " + userId + ", lastScanTime=" + lastScanTime 3059 + " afterOta=" + afterOta); 3060 } 3061 final List<PackageInfo> list = getInstalledPackages(userId); 3062 for (int i = list.size() - 1; i >= 0; i--) { 3063 final PackageInfo pi = list.get(i); 3064 3065 // If the package has been updated since the last scan time, then scan it. 3066 // Also if it's right after an OTA, always re-scan all apps anyway, since the 3067 // shortcut parser might have changed. 3068 if (afterOta || (pi.lastUpdateTime >= lastScanTime)) { 3069 if (DEBUG) { 3070 Slog.d(TAG, "Found updated package " + pi.packageName 3071 + " updateTime=" + pi.lastUpdateTime); 3072 } 3073 callback.accept(pi.applicationInfo); 3074 } 3075 } 3076 } 3077 3078 private boolean isApplicationFlagSet(@NonNull String packageName, int userId, int flags) { 3079 final ApplicationInfo ai = injectApplicationInfoWithUninstalled(packageName, userId); 3080 return (ai != null) && ((ai.flags & flags) == flags); 3081 } 3082 3083 private static boolean isInstalled(@Nullable ApplicationInfo ai) { 3084 return (ai != null) && ai.enabled && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0; 3085 } 3086 3087 private static boolean isEphemeralApp(@Nullable ApplicationInfo ai) { 3088 return (ai != null) && ai.isInstantApp(); 3089 } 3090 3091 private static boolean isInstalled(@Nullable PackageInfo pi) { 3092 return (pi != null) && isInstalled(pi.applicationInfo); 3093 } 3094 3095 private static boolean isInstalled(@Nullable ActivityInfo ai) { 3096 return (ai != null) && isInstalled(ai.applicationInfo); 3097 } 3098 3099 private static ApplicationInfo isInstalledOrNull(ApplicationInfo ai) { 3100 return isInstalled(ai) ? ai : null; 3101 } 3102 3103 private static PackageInfo isInstalledOrNull(PackageInfo pi) { 3104 return isInstalled(pi) ? pi : null; 3105 } 3106 3107 private static ActivityInfo isInstalledOrNull(ActivityInfo ai) { 3108 return isInstalled(ai) ? ai : null; 3109 } 3110 3111 boolean isPackageInstalled(String packageName, int userId) { 3112 return getApplicationInfo(packageName, userId) != null; 3113 } 3114 3115 boolean isEphemeralApp(String packageName, int userId) { 3116 return isEphemeralApp(getApplicationInfo(packageName, userId)); 3117 } 3118 3119 @Nullable 3120 XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) { 3121 return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key); 3122 } 3123 3124 @Nullable 3125 Resources injectGetResourcesForApplicationAsUser(String packageName, int userId) { 3126 final long start = injectElapsedRealtime(); 3127 final long token = injectClearCallingIdentity(); 3128 try { 3129 return mContext.getPackageManager().getResourcesForApplicationAsUser( 3130 packageName, userId); 3131 } catch (NameNotFoundException e) { 3132 Slog.e(TAG, "Resources for package " + packageName + " not found"); 3133 return null; 3134 } finally { 3135 injectRestoreCallingIdentity(token); 3136 3137 logDurationStat(Stats.GET_APPLICATION_RESOURCES, start); 3138 } 3139 } 3140 3141 private Intent getMainActivityIntent() { 3142 final Intent intent = new Intent(Intent.ACTION_MAIN); 3143 intent.addCategory(LAUNCHER_INTENT_CATEGORY); 3144 return intent; 3145 } 3146 3147 /** 3148 * Same as queryIntentActivitiesAsUser, except it makes sure the package is installed, 3149 * and only returns exported activities. 3150 */ 3151 @NonNull 3152 @VisibleForTesting 3153 List<ResolveInfo> queryActivities(@NonNull Intent baseIntent, 3154 @NonNull String packageName, @Nullable ComponentName activity, int userId) { 3155 3156 baseIntent.setPackage(Preconditions.checkNotNull(packageName)); 3157 if (activity != null) { 3158 baseIntent.setComponent(activity); 3159 } 3160 return queryActivities(baseIntent, userId, /* exportedOnly =*/ true); 3161 } 3162 3163 @NonNull 3164 List<ResolveInfo> queryActivities(@NonNull Intent intent, int userId, 3165 boolean exportedOnly) { 3166 final List<ResolveInfo> resolved; 3167 final long token = injectClearCallingIdentity(); 3168 try { 3169 resolved = 3170 mContext.getPackageManager().queryIntentActivitiesAsUser( 3171 intent, PACKAGE_MATCH_FLAGS, userId); 3172 } finally { 3173 injectRestoreCallingIdentity(token); 3174 } 3175 if (resolved == null || resolved.size() == 0) { 3176 return EMPTY_RESOLVE_INFO; 3177 } 3178 // Make sure the package is installed. 3179 if (!isInstalled(resolved.get(0).activityInfo)) { 3180 return EMPTY_RESOLVE_INFO; 3181 } 3182 if (exportedOnly) { 3183 resolved.removeIf(ACTIVITY_NOT_EXPORTED); 3184 } 3185 return resolved; 3186 } 3187 3188 /** 3189 * Return the main activity that is enabled and exported. If multiple activities are found, 3190 * return the first one. 3191 */ 3192 @Nullable 3193 ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) { 3194 final long start = injectElapsedRealtime(); 3195 try { 3196 final List<ResolveInfo> resolved = 3197 queryActivities(getMainActivityIntent(), packageName, null, userId); 3198 return resolved.size() == 0 ? null : resolved.get(0).activityInfo.getComponentName(); 3199 } finally { 3200 logDurationStat(Stats.GET_LAUNCHER_ACTIVITY, start); 3201 } 3202 } 3203 3204 /** 3205 * Return whether an activity is enabled, exported and main. 3206 */ 3207 boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) { 3208 final long start = injectElapsedRealtime(); 3209 try { 3210 if (activity == null) { 3211 wtf("null activity detected"); 3212 return false; 3213 } 3214 if (DUMMY_MAIN_ACTIVITY.equals(activity.getClassName())) { 3215 return true; 3216 } 3217 final List<ResolveInfo> resolved = queryActivities( 3218 getMainActivityIntent(), activity.getPackageName(), activity, userId); 3219 return resolved.size() > 0; 3220 } finally { 3221 logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start); 3222 } 3223 } 3224 3225 /** 3226 * Create a dummy "main activity" component name which is used to create a dynamic shortcut 3227 * with no main activity temporarily. 3228 */ 3229 @NonNull 3230 ComponentName getDummyMainActivity(@NonNull String packageName) { 3231 return new ComponentName(packageName, DUMMY_MAIN_ACTIVITY); 3232 } 3233 3234 boolean isDummyMainActivity(@Nullable ComponentName name) { 3235 return name != null && DUMMY_MAIN_ACTIVITY.equals(name.getClassName()); 3236 } 3237 3238 /** 3239 * Return all the enabled, exported and main activities from a package. 3240 */ 3241 @NonNull 3242 List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) { 3243 final long start = injectElapsedRealtime(); 3244 try { 3245 return queryActivities(getMainActivityIntent(), packageName, null, userId); 3246 } finally { 3247 logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start); 3248 } 3249 } 3250 3251 /** 3252 * Return whether an activity is enabled and exported. 3253 */ 3254 @VisibleForTesting 3255 boolean injectIsActivityEnabledAndExported( 3256 @NonNull ComponentName activity, @UserIdInt int userId) { 3257 final long start = injectElapsedRealtime(); 3258 try { 3259 return queryActivities(new Intent(), activity.getPackageName(), activity, userId) 3260 .size() > 0; 3261 } finally { 3262 logDurationStat(Stats.IS_ACTIVITY_ENABLED, start); 3263 } 3264 } 3265 3266 /** 3267 * Get the {@link LauncherApps#ACTION_CONFIRM_PIN_SHORTCUT} or 3268 * {@link LauncherApps#ACTION_CONFIRM_PIN_APPWIDGET} activity in a given package depending on 3269 * the requestType. 3270 */ 3271 @Nullable 3272 ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName, 3273 int launcherUserId, int requestType) { 3274 Preconditions.checkNotNull(launcherPackageName); 3275 String action = requestType == LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ? 3276 LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT : 3277 LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET; 3278 3279 final Intent confirmIntent = new Intent(action).setPackage(launcherPackageName); 3280 final List<ResolveInfo> candidates = queryActivities( 3281 confirmIntent, launcherUserId, /* exportedOnly =*/ false); 3282 for (ResolveInfo ri : candidates) { 3283 return ri.activityInfo.getComponentName(); 3284 } 3285 return null; 3286 } 3287 3288 boolean injectIsSafeModeEnabled() { 3289 final long token = injectClearCallingIdentity(); 3290 try { 3291 return IWindowManager.Stub 3292 .asInterface(ServiceManager.getService(Context.WINDOW_SERVICE)) 3293 .isSafeModeEnabled(); 3294 } catch (RemoteException e) { 3295 return false; // Shouldn't happen though. 3296 } finally { 3297 injectRestoreCallingIdentity(token); 3298 } 3299 } 3300 3301 /** 3302 * If {@code userId} is of a managed profile, return the parent user ID. Otherwise return 3303 * itself. 3304 */ 3305 int getParentOrSelfUserId(int userId) { 3306 final long token = injectClearCallingIdentity(); 3307 try { 3308 final UserInfo parent = mUserManager.getProfileParent(userId); 3309 return (parent != null) ? parent.id : userId; 3310 } finally { 3311 injectRestoreCallingIdentity(token); 3312 } 3313 } 3314 3315 void injectSendIntentSender(IntentSender intentSender, Intent extras) { 3316 if (intentSender == null) { 3317 return; 3318 } 3319 try { 3320 intentSender.sendIntent(mContext, /* code= */ 0, extras, 3321 /* onFinished=*/ null, /* handler= */ null); 3322 } catch (SendIntentException e) { 3323 Slog.w(TAG, "sendIntent failed().", e); 3324 } 3325 } 3326 3327 // === Backup & restore === 3328 3329 boolean shouldBackupApp(String packageName, int userId) { 3330 return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP); 3331 } 3332 3333 boolean shouldBackupApp(PackageInfo pi) { 3334 return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0; 3335 } 3336 3337 @Override 3338 public byte[] getBackupPayload(@UserIdInt int userId) { 3339 enforceSystem(); 3340 if (DEBUG) { 3341 Slog.d(TAG, "Backing up user " + userId); 3342 } 3343 synchronized (mLock) { 3344 if (!isUserUnlockedL(userId)) { 3345 wtf("Can't backup: user " + userId + " is locked or not running"); 3346 return null; 3347 } 3348 3349 final ShortcutUser user = getUserShortcutsLocked(userId); 3350 if (user == null) { 3351 wtf("Can't backup: user not found: id=" + userId); 3352 return null; 3353 } 3354 3355 // Update the signatures for all packages. 3356 user.forAllPackageItems(spi -> spi.refreshPackageSignatureAndSave()); 3357 3358 // Set the version code for the launchers. 3359 // We shouldn't do this for publisher packages, because we don't want to update the 3360 // version code without rescanning the manifest. 3361 user.forAllLaunchers(launcher -> launcher.ensureVersionInfo()); 3362 3363 // Save to the filesystem. 3364 scheduleSaveUser(userId); 3365 saveDirtyInfo(); 3366 3367 // Note, in case of backup, we don't have to wait on bitmap saving, because we don't 3368 // back up bitmaps anyway. 3369 3370 // Then create the backup payload. 3371 final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024); 3372 try { 3373 saveUserInternalLocked(userId, os, /* forBackup */ true); 3374 } catch (XmlPullParserException | IOException e) { 3375 // Shouldn't happen. 3376 Slog.w(TAG, "Backup failed.", e); 3377 return null; 3378 } 3379 return os.toByteArray(); 3380 } 3381 } 3382 3383 @Override 3384 public void applyRestore(byte[] payload, @UserIdInt int userId) { 3385 enforceSystem(); 3386 if (DEBUG) { 3387 Slog.d(TAG, "Restoring user " + userId); 3388 } 3389 synchronized (mLock) { 3390 if (!isUserUnlockedL(userId)) { 3391 wtf("Can't restore: user " + userId + " is locked or not running"); 3392 return; 3393 } 3394 // Actually do restore. 3395 final ShortcutUser restored; 3396 final ByteArrayInputStream is = new ByteArrayInputStream(payload); 3397 try { 3398 restored = loadUserInternal(userId, is, /* fromBackup */ true); 3399 } catch (XmlPullParserException | IOException | InvalidFileFormatException e) { 3400 Slog.w(TAG, "Restoration failed.", e); 3401 return; 3402 } 3403 getUserShortcutsLocked(userId).mergeRestoredFile(restored); 3404 3405 // Rescan all packages to re-publish manifest shortcuts and do other checks. 3406 rescanUpdatedPackagesLocked(userId, 3407 0 // lastScanTime = 0; rescan all packages. 3408 ); 3409 3410 saveUserLocked(userId); 3411 } 3412 } 3413 3414 // === Dump === 3415 3416 @Override 3417 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 3418 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; 3419 dumpNoCheck(fd, pw, args); 3420 } 3421 3422 @VisibleForTesting 3423 void dumpNoCheck(FileDescriptor fd, PrintWriter pw, String[] args) { 3424 boolean checkin = false; 3425 boolean clear = false; 3426 if (args != null) { 3427 for (String arg : args) { 3428 if ("-c".equals(arg)) { 3429 checkin = true; 3430 } else if ("--checkin".equals(arg)) { 3431 checkin = true; 3432 clear = true; 3433 } 3434 } 3435 } 3436 3437 if (checkin) { 3438 dumpCheckin(pw, clear); 3439 } else { 3440 dumpInner(pw); 3441 } 3442 } 3443 3444 private void dumpInner(PrintWriter pw) { 3445 synchronized (mLock) { 3446 final long now = injectCurrentTimeMillis(); 3447 pw.print("Now: ["); 3448 pw.print(now); 3449 pw.print("] "); 3450 pw.print(formatTime(now)); 3451 3452 pw.print(" Raw last reset: ["); 3453 pw.print(mRawLastResetTime); 3454 pw.print("] "); 3455 pw.print(formatTime(mRawLastResetTime)); 3456 3457 final long last = getLastResetTimeLocked(); 3458 pw.print(" Last reset: ["); 3459 pw.print(last); 3460 pw.print("] "); 3461 pw.print(formatTime(last)); 3462 3463 final long next = getNextResetTimeLocked(); 3464 pw.print(" Next reset: ["); 3465 pw.print(next); 3466 pw.print("] "); 3467 pw.print(formatTime(next)); 3468 3469 pw.print(" Config:"); 3470 pw.print(" Max icon dim: "); 3471 pw.println(mMaxIconDimension); 3472 pw.print(" Icon format: "); 3473 pw.println(mIconPersistFormat); 3474 pw.print(" Icon quality: "); 3475 pw.println(mIconPersistQuality); 3476 pw.print(" saveDelayMillis: "); 3477 pw.println(mSaveDelayMillis); 3478 pw.print(" resetInterval: "); 3479 pw.println(mResetInterval); 3480 pw.print(" maxUpdatesPerInterval: "); 3481 pw.println(mMaxUpdatesPerInterval); 3482 pw.print(" maxShortcutsPerActivity: "); 3483 pw.println(mMaxShortcuts); 3484 pw.println(); 3485 3486 pw.println(" Stats:"); 3487 synchronized (mStatLock) { 3488 for (int i = 0; i < Stats.COUNT; i++) { 3489 dumpStatLS(pw, " ", i); 3490 } 3491 } 3492 3493 pw.println(); 3494 pw.print(" #Failures: "); 3495 pw.println(mWtfCount); 3496 3497 if (mLastWtfStacktrace != null) { 3498 pw.print(" Last failure stack trace: "); 3499 pw.println(Log.getStackTraceString(mLastWtfStacktrace)); 3500 } 3501 3502 pw.println(); 3503 mShortcutBitmapSaver.dumpLocked(pw, " "); 3504 3505 for (int i = 0; i < mUsers.size(); i++) { 3506 pw.println(); 3507 mUsers.valueAt(i).dump(pw, " "); 3508 } 3509 3510 pw.println(); 3511 pw.println(" UID state:"); 3512 3513 for (int i = 0; i < mUidState.size(); i++) { 3514 final int uid = mUidState.keyAt(i); 3515 final int state = mUidState.valueAt(i); 3516 pw.print(" UID="); 3517 pw.print(uid); 3518 pw.print(" state="); 3519 pw.print(state); 3520 if (isProcessStateForeground(state)) { 3521 pw.print(" [FG]"); 3522 } 3523 pw.print(" last FG="); 3524 pw.print(mUidLastForegroundElapsedTime.get(uid)); 3525 pw.println(); 3526 } 3527 } 3528 } 3529 3530 static String formatTime(long time) { 3531 Time tobj = new Time(); 3532 tobj.set(time); 3533 return tobj.format("%Y-%m-%d %H:%M:%S"); 3534 } 3535 3536 private void dumpStatLS(PrintWriter pw, String prefix, int statId) { 3537 pw.print(prefix); 3538 final int count = mCountStats[statId]; 3539 final long dur = mDurationStats[statId]; 3540 pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms", 3541 STAT_LABELS[statId], count, dur, 3542 (count == 0 ? 0 : ((double) dur) / count))); 3543 } 3544 3545 /** 3546 * Dumpsys for checkin. 3547 * 3548 * @param clear if true, clear the history information. Some other system services have this 3549 * behavior but shortcut service doesn't for now. 3550 */ 3551 private void dumpCheckin(PrintWriter pw, boolean clear) { 3552 synchronized (mLock) { 3553 try { 3554 final JSONArray users = new JSONArray(); 3555 3556 for (int i = 0; i < mUsers.size(); i++) { 3557 users.put(mUsers.valueAt(i).dumpCheckin(clear)); 3558 } 3559 3560 final JSONObject result = new JSONObject(); 3561 3562 result.put(KEY_SHORTCUT, users); 3563 result.put(KEY_LOW_RAM, injectIsLowRamDevice()); 3564 result.put(KEY_ICON_SIZE, mMaxIconDimension); 3565 3566 pw.println(result.toString(1)); 3567 } catch (JSONException e) { 3568 Slog.e(TAG, "Unable to write in json", e); 3569 } 3570 } 3571 } 3572 3573 // === Shell support === 3574 3575 @Override 3576 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 3577 String[] args, ShellCallback callback, ResultReceiver resultReceiver) { 3578 3579 enforceShell(); 3580 3581 final long token = injectClearCallingIdentity(); 3582 try { 3583 final int status = (new MyShellCommand()).exec(this, in, out, err, args, callback, 3584 resultReceiver); 3585 resultReceiver.send(status, null); 3586 } finally { 3587 injectRestoreCallingIdentity(token); 3588 } 3589 } 3590 3591 static class CommandException extends Exception { 3592 public CommandException(String message) { 3593 super(message); 3594 } 3595 } 3596 3597 /** 3598 * Handle "adb shell cmd". 3599 */ 3600 private class MyShellCommand extends ShellCommand { 3601 3602 private int mUserId = UserHandle.USER_SYSTEM; 3603 3604 private void parseOptionsLocked(boolean takeUser) 3605 throws CommandException { 3606 String opt; 3607 while ((opt = getNextOption()) != null) { 3608 switch (opt) { 3609 case "--user": 3610 if (takeUser) { 3611 mUserId = UserHandle.parseUserArg(getNextArgRequired()); 3612 if (!isUserUnlockedL(mUserId)) { 3613 throw new CommandException( 3614 "User " + mUserId + " is not running or locked"); 3615 } 3616 break; 3617 } 3618 // fallthrough 3619 default: 3620 throw new CommandException("Unknown option: " + opt); 3621 } 3622 } 3623 } 3624 3625 @Override 3626 public int onCommand(String cmd) { 3627 if (cmd == null) { 3628 return handleDefaultCommands(cmd); 3629 } 3630 final PrintWriter pw = getOutPrintWriter(); 3631 try { 3632 switch (cmd) { 3633 case "reset-throttling": 3634 handleResetThrottling(); 3635 break; 3636 case "reset-all-throttling": 3637 handleResetAllThrottling(); 3638 break; 3639 case "override-config": 3640 handleOverrideConfig(); 3641 break; 3642 case "reset-config": 3643 handleResetConfig(); 3644 break; 3645 case "clear-default-launcher": 3646 handleClearDefaultLauncher(); 3647 break; 3648 case "get-default-launcher": 3649 handleGetDefaultLauncher(); 3650 break; 3651 case "unload-user": 3652 handleUnloadUser(); 3653 break; 3654 case "clear-shortcuts": 3655 handleClearShortcuts(); 3656 break; 3657 case "verify-states": // hidden command to verify various internal states. 3658 handleVerifyStates(); 3659 break; 3660 default: 3661 return handleDefaultCommands(cmd); 3662 } 3663 } catch (CommandException e) { 3664 pw.println("Error: " + e.getMessage()); 3665 return 1; 3666 } 3667 pw.println("Success"); 3668 return 0; 3669 } 3670 3671 @Override 3672 public void onHelp() { 3673 final PrintWriter pw = getOutPrintWriter(); 3674 pw.println("Usage: cmd shortcut COMMAND [options ...]"); 3675 pw.println(); 3676 pw.println("cmd shortcut reset-throttling [--user USER_ID]"); 3677 pw.println(" Reset throttling for all packages and users"); 3678 pw.println(); 3679 pw.println("cmd shortcut reset-all-throttling"); 3680 pw.println(" Reset the throttling state for all users"); 3681 pw.println(); 3682 pw.println("cmd shortcut override-config CONFIG"); 3683 pw.println(" Override the configuration for testing (will last until reboot)"); 3684 pw.println(); 3685 pw.println("cmd shortcut reset-config"); 3686 pw.println(" Reset the configuration set with \"update-config\""); 3687 pw.println(); 3688 pw.println("cmd shortcut clear-default-launcher [--user USER_ID]"); 3689 pw.println(" Clear the cached default launcher"); 3690 pw.println(); 3691 pw.println("cmd shortcut get-default-launcher [--user USER_ID]"); 3692 pw.println(" Show the default launcher"); 3693 pw.println(); 3694 pw.println("cmd shortcut unload-user [--user USER_ID]"); 3695 pw.println(" Unload a user from the memory"); 3696 pw.println(" (This should not affect any observable behavior)"); 3697 pw.println(); 3698 pw.println("cmd shortcut clear-shortcuts [--user USER_ID] PACKAGE"); 3699 pw.println(" Remove all shortcuts from a package, including pinned shortcuts"); 3700 pw.println(); 3701 } 3702 3703 private void handleResetThrottling() throws CommandException { 3704 synchronized (mLock) { 3705 parseOptionsLocked(/* takeUser =*/ true); 3706 3707 Slog.i(TAG, "cmd: handleResetThrottling: user=" + mUserId); 3708 3709 resetThrottlingInner(mUserId); 3710 } 3711 } 3712 3713 private void handleResetAllThrottling() { 3714 Slog.i(TAG, "cmd: handleResetAllThrottling"); 3715 3716 resetAllThrottlingInner(); 3717 } 3718 3719 private void handleOverrideConfig() throws CommandException { 3720 final String config = getNextArgRequired(); 3721 3722 Slog.i(TAG, "cmd: handleOverrideConfig: " + config); 3723 3724 synchronized (mLock) { 3725 if (!updateConfigurationLocked(config)) { 3726 throw new CommandException("override-config failed. See logcat for details."); 3727 } 3728 } 3729 } 3730 3731 private void handleResetConfig() { 3732 Slog.i(TAG, "cmd: handleResetConfig"); 3733 3734 synchronized (mLock) { 3735 loadConfigurationLocked(); 3736 } 3737 } 3738 3739 private void clearLauncher() { 3740 synchronized (mLock) { 3741 getUserShortcutsLocked(mUserId).forceClearLauncher(); 3742 } 3743 } 3744 3745 private void showLauncher() { 3746 synchronized (mLock) { 3747 // This ensures to set the cached launcher. Package name doesn't matter. 3748 hasShortcutHostPermissionInner("-", mUserId); 3749 3750 getOutPrintWriter().println("Launcher: " 3751 + getUserShortcutsLocked(mUserId).getLastKnownLauncher()); 3752 } 3753 } 3754 3755 private void handleClearDefaultLauncher() throws CommandException { 3756 synchronized (mLock) { 3757 parseOptionsLocked(/* takeUser =*/ true); 3758 3759 clearLauncher(); 3760 } 3761 } 3762 3763 private void handleGetDefaultLauncher() throws CommandException { 3764 synchronized (mLock) { 3765 parseOptionsLocked(/* takeUser =*/ true); 3766 3767 clearLauncher(); 3768 showLauncher(); 3769 } 3770 } 3771 3772 private void handleUnloadUser() throws CommandException { 3773 synchronized (mLock) { 3774 parseOptionsLocked(/* takeUser =*/ true); 3775 3776 Slog.i(TAG, "cmd: handleUnloadUser: user=" + mUserId); 3777 3778 ShortcutService.this.handleStopUser(mUserId); 3779 } 3780 } 3781 3782 private void handleClearShortcuts() throws CommandException { 3783 synchronized (mLock) { 3784 parseOptionsLocked(/* takeUser =*/ true); 3785 final String packageName = getNextArgRequired(); 3786 3787 Slog.i(TAG, "cmd: handleClearShortcuts: user" + mUserId + ", " + packageName); 3788 3789 ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId, 3790 /* appStillExists = */ true); 3791 } 3792 } 3793 3794 private void handleVerifyStates() throws CommandException { 3795 try { 3796 verifyStatesForce(); // This will throw when there's an issue. 3797 } catch (Throwable th) { 3798 throw new CommandException(th.getMessage() + "\n" + Log.getStackTraceString(th)); 3799 } 3800 } 3801 } 3802 3803 // === Unit test support === 3804 3805 // Injection point. 3806 @VisibleForTesting 3807 long injectCurrentTimeMillis() { 3808 return System.currentTimeMillis(); 3809 } 3810 3811 @VisibleForTesting 3812 long injectElapsedRealtime() { 3813 return SystemClock.elapsedRealtime(); 3814 } 3815 3816 @VisibleForTesting 3817 long injectUptimeMillis() { 3818 return SystemClock.uptimeMillis(); 3819 } 3820 3821 // Injection point. 3822 @VisibleForTesting 3823 int injectBinderCallingUid() { 3824 return getCallingUid(); 3825 } 3826 3827 private int getCallingUserId() { 3828 return UserHandle.getUserId(injectBinderCallingUid()); 3829 } 3830 3831 // Injection point. 3832 @VisibleForTesting 3833 long injectClearCallingIdentity() { 3834 return Binder.clearCallingIdentity(); 3835 } 3836 3837 // Injection point. 3838 @VisibleForTesting 3839 void injectRestoreCallingIdentity(long token) { 3840 Binder.restoreCallingIdentity(token); 3841 } 3842 3843 // Injection point. 3844 @VisibleForTesting 3845 String injectBuildFingerprint() { 3846 return Build.FINGERPRINT; 3847 } 3848 3849 final void wtf(String message) { 3850 wtf(message, /* exception= */ null); 3851 } 3852 3853 // Injection point. 3854 void wtf(String message, Throwable e) { 3855 if (e == null) { 3856 e = new RuntimeException("Stacktrace"); 3857 } 3858 synchronized (mLock) { 3859 mWtfCount++; 3860 mLastWtfStacktrace = new Exception("Last failure was logged here:"); 3861 } 3862 Slog.wtf(TAG, message, e); 3863 } 3864 3865 @VisibleForTesting 3866 File injectSystemDataPath() { 3867 return Environment.getDataSystemDirectory(); 3868 } 3869 3870 @VisibleForTesting 3871 File injectUserDataPath(@UserIdInt int userId) { 3872 return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER); 3873 } 3874 3875 @VisibleForTesting 3876 boolean injectIsLowRamDevice() { 3877 return ActivityManager.isLowRamDeviceStatic(); 3878 } 3879 3880 @VisibleForTesting 3881 void injectRegisterUidObserver(IUidObserver observer, int which) { 3882 try { 3883 ActivityManager.getService().registerUidObserver(observer, which, 3884 ActivityManager.PROCESS_STATE_UNKNOWN, null); 3885 } catch (RemoteException shouldntHappen) { 3886 } 3887 } 3888 3889 File getUserBitmapFilePath(@UserIdInt int userId) { 3890 return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS); 3891 } 3892 3893 @VisibleForTesting 3894 SparseArray<ShortcutUser> getShortcutsForTest() { 3895 return mUsers; 3896 } 3897 3898 @VisibleForTesting 3899 int getMaxShortcutsForTest() { 3900 return mMaxShortcuts; 3901 } 3902 3903 @VisibleForTesting 3904 int getMaxUpdatesPerIntervalForTest() { 3905 return mMaxUpdatesPerInterval; 3906 } 3907 3908 @VisibleForTesting 3909 long getResetIntervalForTest() { 3910 return mResetInterval; 3911 } 3912 3913 @VisibleForTesting 3914 int getMaxIconDimensionForTest() { 3915 return mMaxIconDimension; 3916 } 3917 3918 @VisibleForTesting 3919 CompressFormat getIconPersistFormatForTest() { 3920 return mIconPersistFormat; 3921 } 3922 3923 @VisibleForTesting 3924 int getIconPersistQualityForTest() { 3925 return mIconPersistQuality; 3926 } 3927 3928 @VisibleForTesting 3929 ShortcutPackage getPackageShortcutForTest(String packageName, int userId) { 3930 synchronized (mLock) { 3931 final ShortcutUser user = mUsers.get(userId); 3932 if (user == null) return null; 3933 3934 return user.getAllPackagesForTest().get(packageName); 3935 } 3936 } 3937 3938 @VisibleForTesting 3939 ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) { 3940 synchronized (mLock) { 3941 final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId); 3942 if (pkg == null) return null; 3943 3944 return pkg.findShortcutById(shortcutId); 3945 } 3946 } 3947 3948 @VisibleForTesting 3949 ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) { 3950 synchronized (mLock) { 3951 final ShortcutUser user = mUsers.get(userId); 3952 if (user == null) return null; 3953 3954 return user.getAllLaunchersForTest().get(PackageWithUser.of(userId, packageName)); 3955 } 3956 } 3957 3958 @VisibleForTesting 3959 ShortcutRequestPinProcessor getShortcutRequestPinProcessorForTest() { 3960 return mShortcutRequestPinProcessor; 3961 } 3962 3963 /** 3964 * Control whether {@link #verifyStates} should be performed. We always perform it during unit 3965 * tests. 3966 */ 3967 @VisibleForTesting 3968 boolean injectShouldPerformVerification() { 3969 return DEBUG; 3970 } 3971 3972 /** 3973 * Check various internal states and throws if there's any inconsistency. 3974 * This is normally only enabled during unit tests. 3975 */ 3976 final void verifyStates() { 3977 if (injectShouldPerformVerification()) { 3978 verifyStatesInner(); 3979 } 3980 } 3981 3982 private final void verifyStatesForce() { 3983 verifyStatesInner(); 3984 } 3985 3986 private void verifyStatesInner() { 3987 synchronized (mLock) { 3988 forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates)); 3989 } 3990 } 3991 3992 @VisibleForTesting 3993 void waitForBitmapSavesForTest() { 3994 synchronized (mLock) { 3995 mShortcutBitmapSaver.waitForAllSavesLocked(); 3996 } 3997 } 3998} 3999