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