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