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