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