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