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 */ 16 package com.android.server.pm; 17 18 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; 19 import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; 20 21 import android.Manifest.permission; 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.UserIdInt; 26 import android.app.ActivityManager; 27 import android.app.ActivityManagerInternal; 28 import android.app.ActivityOptions; 29 import android.app.AppGlobals; 30 import android.app.IUidObserver; 31 import android.app.IUriGrantsManager; 32 import android.app.UidObserver; 33 import android.app.UriGrantsManager; 34 import android.app.role.OnRoleHoldersChangedListener; 35 import android.app.role.RoleManager; 36 import android.app.usage.UsageStatsManagerInternal; 37 import android.appwidget.AppWidgetProviderInfo; 38 import android.content.BroadcastReceiver; 39 import android.content.ComponentName; 40 import android.content.ContentProvider; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.content.IntentFilter; 44 import android.content.IntentSender; 45 import android.content.IntentSender.SendIntentException; 46 import android.content.LocusId; 47 import android.content.pm.ActivityInfo; 48 import android.content.pm.ApplicationInfo; 49 import android.content.pm.ComponentInfo; 50 import android.content.pm.IPackageManager; 51 import android.content.pm.IShortcutService; 52 import android.content.pm.LauncherApps; 53 import android.content.pm.LauncherApps.ShortcutQuery; 54 import android.content.pm.PackageInfo; 55 import android.content.pm.PackageManager; 56 import android.content.pm.PackageManager.NameNotFoundException; 57 import android.content.pm.PackageManagerInternal; 58 import android.content.pm.ParceledListSlice; 59 import android.content.pm.ResolveInfo; 60 import android.content.pm.ShortcutInfo; 61 import android.content.pm.ShortcutManager; 62 import android.content.pm.ShortcutServiceInternal; 63 import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; 64 import android.content.pm.UserPackage; 65 import android.content.res.Resources; 66 import android.content.res.XmlResourceParser; 67 import android.graphics.Bitmap; 68 import android.graphics.Bitmap.CompressFormat; 69 import android.graphics.Canvas; 70 import android.graphics.RectF; 71 import android.graphics.drawable.AdaptiveIconDrawable; 72 import android.graphics.drawable.Icon; 73 import android.multiuser.Flags; 74 import android.net.Uri; 75 import android.os.Binder; 76 import android.os.Build; 77 import android.os.Bundle; 78 import android.os.Environment; 79 import android.os.FileUtils; 80 import android.os.Handler; 81 import android.os.HandlerThread; 82 import android.os.IBinder; 83 import android.os.LocaleList; 84 import android.os.Looper; 85 import android.os.ParcelFileDescriptor; 86 import android.os.PersistableBundle; 87 import android.os.Process; 88 import android.os.RemoteException; 89 import android.os.ResultReceiver; 90 import android.os.SELinux; 91 import android.os.ServiceManager; 92 import android.os.ShellCallback; 93 import android.os.ShellCommand; 94 import android.os.SystemClock; 95 import android.os.Trace; 96 import android.os.UserHandle; 97 import android.provider.DeviceConfig; 98 import android.text.TextUtils; 99 import android.text.format.TimeMigrationUtils; 100 import android.util.ArraySet; 101 import android.util.KeyValueListParser; 102 import android.util.Log; 103 import android.util.Slog; 104 import android.util.SparseArray; 105 import android.util.SparseBooleanArray; 106 import android.util.SparseIntArray; 107 import android.util.SparseLongArray; 108 import android.util.TypedValue; 109 import android.util.Xml; 110 import android.view.IWindowManager; 111 112 import com.android.internal.R; 113 import com.android.internal.annotations.GuardedBy; 114 import com.android.internal.annotations.VisibleForTesting; 115 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 116 import com.android.internal.infra.AndroidFuture; 117 import com.android.internal.logging.MetricsLogger; 118 import com.android.internal.util.CollectionUtils; 119 import com.android.internal.util.DumpUtils; 120 import com.android.internal.util.Preconditions; 121 import com.android.internal.util.StatLogger; 122 import com.android.modules.utils.TypedXmlPullParser; 123 import com.android.modules.utils.TypedXmlSerializer; 124 import com.android.server.LocalServices; 125 import com.android.server.SystemService; 126 import com.android.server.uri.UriGrantsManagerInternal; 127 128 import org.json.JSONArray; 129 import org.json.JSONException; 130 import org.json.JSONObject; 131 import org.xmlpull.v1.XmlPullParser; 132 import org.xmlpull.v1.XmlPullParserException; 133 134 import java.io.ByteArrayInputStream; 135 import java.io.ByteArrayOutputStream; 136 import java.io.File; 137 import java.io.FileDescriptor; 138 import java.io.FileInputStream; 139 import java.io.FileNotFoundException; 140 import java.io.FileOutputStream; 141 import java.io.IOException; 142 import java.io.InputStream; 143 import java.io.OutputStream; 144 import java.io.PrintWriter; 145 import java.lang.annotation.Retention; 146 import java.lang.annotation.RetentionPolicy; 147 import java.net.URISyntaxException; 148 import java.nio.charset.StandardCharsets; 149 import java.util.ArrayList; 150 import java.util.Arrays; 151 import java.util.Collections; 152 import java.util.List; 153 import java.util.Objects; 154 import java.util.concurrent.atomic.AtomicBoolean; 155 import java.util.concurrent.atomic.AtomicLong; 156 import java.util.function.Consumer; 157 import java.util.function.Predicate; 158 import java.util.regex.Pattern; 159 import java.util.stream.Collectors; 160 161 /** 162 * TODO: 163 * - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi. 164 * -> But TypedValue.applyDimension() doesn't differentiate x and y..? 165 * 166 * - Detect when already registered instances are passed to APIs again, which might break 167 * internal bitmap handling. 168 */ 169 public class ShortcutService extends IShortcutService.Stub { 170 static final String TAG = "ShortcutService"; 171 172 static final boolean DEBUG = false; // STOPSHIP if true 173 static final boolean DEBUG_LOAD = false; // STOPSHIP if true 174 static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true 175 static final boolean DEBUG_REBOOT = true; 176 177 @VisibleForTesting 178 static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day 179 180 @VisibleForTesting 181 static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10; 182 183 @VisibleForTesting 184 static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15; 185 186 @VisibleForTesting 187 static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 100; 188 189 @VisibleForTesting 190 static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96; 191 192 @VisibleForTesting 193 static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48; 194 195 @VisibleForTesting 196 static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name(); 197 198 @VisibleForTesting 199 static final int DEFAULT_ICON_PERSIST_QUALITY = 100; 200 201 @VisibleForTesting 202 static final int DEFAULT_SAVE_DELAY_MS = 3000; 203 204 @VisibleForTesting 205 static final String FILENAME_BASE_STATE = "shortcut_service.xml"; 206 207 @VisibleForTesting 208 static final String DIRECTORY_PER_USER = "shortcut_service"; 209 210 @VisibleForTesting 211 static final String DIRECTORY_DUMP = "shortcut_dump"; 212 213 @VisibleForTesting 214 static final String FILENAME_USER_PACKAGES = "shortcuts.xml"; 215 216 @VisibleForTesting 217 static final String FILENAME_USER_PACKAGES_RESERVE_COPY = 218 FILENAME_USER_PACKAGES + ".reservecopy"; 219 220 static final String DIRECTORY_BITMAPS = "bitmaps"; 221 222 private static final String TAG_ROOT = "root"; 223 private static final String TAG_LAST_RESET_TIME = "last_reset_time"; 224 225 private static final String ATTR_VALUE = "value"; 226 227 private static final String LAUNCHER_INTENT_CATEGORY = Intent.CATEGORY_LAUNCHER; 228 229 private static final String KEY_SHORTCUT = "shortcut"; 230 private static final String KEY_LOW_RAM = "lowRam"; 231 private static final String KEY_ICON_SIZE = "iconSize"; 232 233 private static final String DUMMY_MAIN_ACTIVITY = "android.__dummy__"; 234 235 private static final long CALLBACK_DELAY = 100L; 236 237 @VisibleForTesting 238 interface ConfigConstants { 239 /** 240 * Key name for the save delay, in milliseconds. (int) 241 */ 242 String KEY_SAVE_DELAY_MILLIS = "save_delay_ms"; 243 244 /** 245 * Key name for the throttling reset interval, in seconds. (long) 246 */ 247 String KEY_RESET_INTERVAL_SEC = "reset_interval_sec"; 248 249 /** 250 * Key name for the max number of modifying API calls per app for every interval. (int) 251 */ 252 String KEY_MAX_UPDATES_PER_INTERVAL = "max_updates_per_interval"; 253 254 /** 255 * Key name for the max icon dimensions in DP, for non-low-memory devices. 256 */ 257 String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp"; 258 259 /** 260 * Key name for the max icon dimensions in DP, for low-memory devices. 261 */ 262 String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram"; 263 264 /** 265 * Key name for the max dynamic shortcuts per activity. (int) 266 */ 267 String KEY_MAX_SHORTCUTS = "max_shortcuts"; 268 269 /** 270 * Key name for the max shortcuts can be retained in system ram per app. (int) 271 */ 272 String KEY_MAX_SHORTCUTS_PER_APP = "max_shortcuts_per_app"; 273 274 /** 275 * Key name for icon compression quality, 0-100. 276 */ 277 String KEY_ICON_QUALITY = "icon_quality"; 278 279 /** 280 * Key name for icon compression format: "PNG", "JPEG" or "WEBP" 281 */ 282 String KEY_ICON_FORMAT = "icon_format"; 283 } 284 285 private static final int PACKAGE_MATCH_FLAGS = 286 PackageManager.MATCH_DIRECT_BOOT_AWARE 287 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 288 | PackageManager.MATCH_UNINSTALLED_PACKAGES 289 | PackageManager.MATCH_DISABLED_COMPONENTS; 290 291 private static final int SYSTEM_APP_MASK = 292 ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; 293 294 final Context mContext; 295 296 private final Object mServiceLock = new Object(); 297 private final Object mNonPersistentUsersLock = new Object(); 298 private final Object mWtfLock = new Object(); 299 300 private static final List<ResolveInfo> EMPTY_RESOLVE_INFO = new ArrayList<>(0); 301 302 // Temporarily reverted to anonymous inner class form due to: b/32554459 303 private static final Predicate<ResolveInfo> ACTIVITY_NOT_EXPORTED = 304 new Predicate<ResolveInfo>() { 305 public boolean test(ResolveInfo ri) { 306 return !ri.activityInfo.exported; 307 } 308 }; 309 310 private static final Predicate<ResolveInfo> ACTIVITY_NOT_INSTALLED = (ri) -> 311 !isInstalled(ri.activityInfo); 312 313 // Temporarily reverted to anonymous inner class form due to: b/32554459 314 private static final Predicate<PackageInfo> PACKAGE_NOT_INSTALLED = 315 new Predicate<PackageInfo>() { 316 public boolean test(PackageInfo pi) { 317 return !isInstalled(pi); 318 } 319 }; 320 321 private final Handler mHandler; 322 323 @GuardedBy("mServiceLock") 324 private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1); 325 326 @GuardedBy("mServiceLock") 327 private final ArrayList<LauncherApps.ShortcutChangeCallback> mShortcutChangeCallbacks = 328 new ArrayList<>(1); 329 330 private final AtomicLong mRawLastResetTime = new AtomicLong(0); 331 332 /** 333 * User ID -> UserShortcuts 334 */ 335 @GuardedBy("mServiceLock") 336 private final SparseArray<ShortcutUser> mUsers = new SparseArray<>(); 337 338 /** 339 * User ID -> ShortcutNonPersistentUser 340 * 341 * Note we use a fine-grained lock for {@link #mShortcutNonPersistentUsers} due to b/183618378. 342 */ 343 @GuardedBy("mNonPersistentUsersLock") 344 private final SparseArray<ShortcutNonPersistentUser> mShortcutNonPersistentUsers = 345 new SparseArray<>(); 346 347 /** 348 * Max number of dynamic + manifest shortcuts that each activity can have at a time. 349 */ 350 private int mMaxShortcuts; 351 352 /** 353 * Max number of shortcuts that can exists in system ram for each application. 354 */ 355 private int mMaxShortcutsPerApp; 356 357 /** 358 * Max number of updating API calls that each application can make during the interval. 359 */ 360 int mMaxUpdatesPerInterval; 361 362 /** 363 * Actual throttling-reset interval. By default it's a day. 364 */ 365 private long mResetInterval; 366 367 /** 368 * Icon max width/height in pixels. 369 */ 370 private int mMaxIconDimension; 371 372 private CompressFormat mIconPersistFormat; 373 private int mIconPersistQuality; 374 375 int mSaveDelayMillis; 376 377 private final IPackageManager mIPackageManager; 378 private final PackageManagerInternal mPackageManagerInternal; 379 final UserManagerInternal mUserManagerInternal; 380 private final UsageStatsManagerInternal mUsageStatsManagerInternal; 381 private final ActivityManagerInternal mActivityManagerInternal; 382 private final IUriGrantsManager mUriGrantsManager; 383 private final UriGrantsManagerInternal mUriGrantsManagerInternal; 384 private final IBinder mUriPermissionOwner; 385 private final RoleManager mRoleManager; 386 387 private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor; 388 private final ShortcutDumpFiles mShortcutDumpFiles; 389 390 @GuardedBy("mServiceLock") 391 final SparseIntArray mUidState = new SparseIntArray(); 392 393 @GuardedBy("mServiceLock") 394 final SparseLongArray mUidLastForegroundElapsedTime = new SparseLongArray(); 395 396 @GuardedBy("mServiceLock") 397 private List<Integer> mDirtyUserIds = new ArrayList<>(); 398 399 private final AtomicBoolean mBootCompleted = new AtomicBoolean(); 400 private final AtomicBoolean mShutdown = new AtomicBoolean(); 401 402 /** 403 * Note we use a fine-grained lock for {@link #mUnlockedUsers} due to b/64303666. 404 */ 405 @GuardedBy("mUnlockedUsers") 406 final SparseBooleanArray mUnlockedUsers = new SparseBooleanArray(); 407 408 // Stats 409 @VisibleForTesting 410 interface Stats { 411 int GET_DEFAULT_HOME = 0; 412 int GET_PACKAGE_INFO = 1; 413 int GET_PACKAGE_INFO_WITH_SIG = 2; 414 int GET_APPLICATION_INFO = 3; 415 int LAUNCHER_PERMISSION_CHECK = 4; 416 int CLEANUP_DANGLING_BITMAPS = 5; 417 int GET_ACTIVITY_WITH_METADATA = 6; 418 int GET_INSTALLED_PACKAGES = 7; 419 int CHECK_PACKAGE_CHANGES = 8; 420 int GET_APPLICATION_RESOURCES = 9; 421 int RESOURCE_NAME_LOOKUP = 10; 422 int GET_LAUNCHER_ACTIVITY = 11; 423 int CHECK_LAUNCHER_ACTIVITY = 12; 424 int IS_ACTIVITY_ENABLED = 13; 425 int PACKAGE_UPDATE_CHECK = 14; 426 int ASYNC_PRELOAD_USER_DELAY = 15; 427 int GET_DEFAULT_LAUNCHER = 16; 428 429 int COUNT = GET_DEFAULT_LAUNCHER + 1; 430 } 431 432 private final StatLogger mStatLogger = new StatLogger(new String[] { 433 "getHomeActivities()", 434 "Launcher permission check", 435 "getPackageInfo()", 436 "getPackageInfo(SIG)", 437 "getApplicationInfo", 438 "cleanupDanglingBitmaps", 439 "getActivity+metadata", 440 "getInstalledPackages", 441 "checkPackageChanges", 442 "getApplicationResources", 443 "resourceNameLookup", 444 "getLauncherActivity", 445 "checkLauncherActivity", 446 "isActivityEnabled", 447 "packageUpdateCheck", 448 "asyncPreloadUserDelay", 449 "getDefaultLauncher()" 450 }); 451 452 private static final int PROCESS_STATE_FOREGROUND_THRESHOLD = 453 ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; 454 455 static final int OPERATION_SET = 0; 456 static final int OPERATION_ADD = 1; 457 static final int OPERATION_UPDATE = 2; 458 459 /** @hide */ 460 @IntDef(value = { 461 OPERATION_SET, 462 OPERATION_ADD, 463 OPERATION_UPDATE 464 }) 465 @Retention(RetentionPolicy.SOURCE) 466 @interface ShortcutOperation { 467 } 468 469 @GuardedBy("mWtfLock") 470 private int mWtfCount = 0; 471 472 @GuardedBy("mWtfLock") 473 private Exception mLastWtfStacktrace; 474 475 @GuardedBy("mServiceLock") 476 private final MetricsLogger mMetricsLogger = new MetricsLogger(); 477 478 private final boolean mIsAppSearchEnabled; 479 480 private ComponentName mChooserActivity; 481 482 static class InvalidFileFormatException extends Exception { InvalidFileFormatException(String message, Throwable cause)483 public InvalidFileFormatException(String message, Throwable cause) { 484 super(message, cause); 485 } 486 } 487 ShortcutService(Context context)488 public ShortcutService(Context context) { 489 this(context, getBgLooper(), /*onyForPackgeManagerApis*/ false); 490 } 491 getBgLooper()492 private static Looper getBgLooper() { 493 final HandlerThread handlerThread = new HandlerThread("shortcut", 494 android.os.Process.THREAD_PRIORITY_BACKGROUND); 495 handlerThread.start(); 496 return handlerThread.getLooper(); 497 } 498 499 @VisibleForTesting ShortcutService(Context context, Looper looper, boolean onlyForPackageManagerApis)500 ShortcutService(Context context, Looper looper, boolean onlyForPackageManagerApis) { 501 mContext = Objects.requireNonNull(context); 502 LocalServices.addService(ShortcutServiceInternal.class, new LocalService()); 503 mHandler = new Handler(looper); 504 mIPackageManager = AppGlobals.getPackageManager(); 505 mPackageManagerInternal = Objects.requireNonNull( 506 LocalServices.getService(PackageManagerInternal.class)); 507 mUserManagerInternal = Objects.requireNonNull( 508 LocalServices.getService(UserManagerInternal.class)); 509 mUsageStatsManagerInternal = Objects.requireNonNull( 510 LocalServices.getService(UsageStatsManagerInternal.class)); 511 mActivityManagerInternal = Objects.requireNonNull( 512 LocalServices.getService(ActivityManagerInternal.class)); 513 514 mUriGrantsManager = Objects.requireNonNull(UriGrantsManager.getService()); 515 mUriGrantsManagerInternal = Objects.requireNonNull( 516 LocalServices.getService(UriGrantsManagerInternal.class)); 517 mUriPermissionOwner = mUriGrantsManagerInternal.newUriPermissionOwner(TAG); 518 mRoleManager = Objects.requireNonNull(mContext.getSystemService(RoleManager.class)); 519 520 mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mServiceLock); 521 mShortcutDumpFiles = new ShortcutDumpFiles(this); 522 mIsAppSearchEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, 523 SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, false) 524 && !injectIsLowRamDevice(); 525 526 if (onlyForPackageManagerApis) { 527 return; // Don't do anything further. For unit tests only. 528 } 529 530 // Register receivers. 531 532 // We need to set a priority, so let's just not use PackageMonitor for now. 533 // TODO Refactor PackageMonitor to support priorities. 534 final IntentFilter packageFilter = new IntentFilter(); 535 packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 536 packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 537 packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); 538 packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); 539 packageFilter.addDataScheme("package"); 540 packageFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 541 mContext.registerReceiverAsUser(mPackageMonitor, UserHandle.ALL, 542 packageFilter, null, mHandler); 543 544 final IntentFilter localeFilter = new IntentFilter(); 545 localeFilter.addAction(Intent.ACTION_LOCALE_CHANGED); 546 localeFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 547 mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, 548 localeFilter, null, mHandler); 549 550 IntentFilter shutdownFilter = new IntentFilter(); 551 shutdownFilter.addAction(Intent.ACTION_SHUTDOWN); 552 shutdownFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 553 mContext.registerReceiverAsUser(mShutdownReceiver, UserHandle.SYSTEM, 554 shutdownFilter, null, mHandler); 555 556 injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE 557 | ActivityManager.UID_OBSERVER_GONE); 558 559 injectRegisterRoleHoldersListener(mOnRoleHoldersChangedListener); 560 } 561 isAppSearchEnabled()562 boolean isAppSearchEnabled() { 563 return mIsAppSearchEnabled; 564 } 565 getStatStartTime()566 long getStatStartTime() { 567 return mStatLogger.getTime(); 568 } 569 logDurationStat(int statId, long start)570 void logDurationStat(int statId, long start) { 571 mStatLogger.logDurationStat(statId, start); 572 } 573 injectGetLocaleTagsForUser(@serIdInt int userId)574 public String injectGetLocaleTagsForUser(@UserIdInt int userId) { 575 // TODO This should get the per-user locale. b/30123329 b/30119489 576 return LocaleList.getDefault().toLanguageTags(); 577 } 578 579 private final OnRoleHoldersChangedListener mOnRoleHoldersChangedListener = 580 new OnRoleHoldersChangedListener() { 581 @Override 582 public void onRoleHoldersChanged(String roleName, UserHandle user) { 583 if (RoleManager.ROLE_HOME.equals(roleName)) { 584 injectPostToHandler(() -> handleOnDefaultLauncherChanged(user.getIdentifier())); 585 } 586 } 587 }; 588 handleOnDefaultLauncherChanged(int userId)589 void handleOnDefaultLauncherChanged(int userId) { 590 if (DEBUG) { 591 Slog.v(TAG, "Default launcher changed for user: " + userId); 592 } 593 594 // Default launcher is removed or changed, revoke all URI permissions. 595 mUriGrantsManagerInternal.revokeUriPermissionFromOwner(mUriPermissionOwner, null, ~0, 0); 596 597 synchronized (mServiceLock) { 598 // Clear the launcher cache for this user. It will be set again next time the default 599 // launcher is read from RoleManager. 600 if (isUserLoadedLocked(userId)) { 601 getUserShortcutsLocked(userId).setCachedLauncher(null); 602 } 603 } 604 } 605 606 final private IUidObserver mUidObserver = new UidObserver() { 607 @Override 608 public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { 609 injectPostToHandler(() -> handleOnUidStateChanged(uid, procState)); 610 } 611 612 @Override 613 public void onUidGone(int uid, boolean disabled) { 614 injectPostToHandler(() -> 615 handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT)); 616 } 617 }; 618 handleOnUidStateChanged(int uid, int procState)619 void handleOnUidStateChanged(int uid, int procState) { 620 if (DEBUG_PROCSTATE) { 621 Slog.d(TAG, "onUidStateChanged: uid=" + uid + " state=" + procState); 622 } 623 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutHandleOnUidStateChanged"); 624 synchronized (mServiceLock) { 625 mUidState.put(uid, procState); 626 627 // We need to keep track of last time an app comes to foreground. 628 // See ShortcutPackage.getApiCallCount() for how it's used. 629 // It doesn't have to be persisted, but it needs to be the elapsed time. 630 if (isProcessStateForeground(procState)) { 631 mUidLastForegroundElapsedTime.put(uid, injectElapsedRealtime()); 632 } 633 } 634 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 635 } 636 isProcessStateForeground(int processState)637 private boolean isProcessStateForeground(int processState) { 638 return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD; 639 } 640 641 @GuardedBy("mServiceLock") isUidForegroundLocked(int uid)642 boolean isUidForegroundLocked(int uid) { 643 if (uid == Process.SYSTEM_UID) { 644 // IUidObserver doesn't report the state of SYSTEM, but it always has bound services, 645 // so it's foreground anyway. 646 return true; 647 } 648 // First, check with the local cache. 649 if (isProcessStateForeground(mUidState.get(uid, ActivityManager.MAX_PROCESS_STATE))) { 650 return true; 651 } 652 // If the cache says background, reach out to AM. Since it'll internally need to hold 653 // the AM lock, we use it as a last resort. 654 return isProcessStateForeground(mActivityManagerInternal.getUidProcessState(uid)); 655 } 656 657 @GuardedBy("mServiceLock") getUidLastForegroundElapsedTimeLocked(int uid)658 long getUidLastForegroundElapsedTimeLocked(int uid) { 659 return mUidLastForegroundElapsedTime.get(uid); 660 } 661 662 /** 663 * System service lifecycle. 664 */ 665 public static final class Lifecycle extends SystemService { 666 final ShortcutService mService; 667 Lifecycle(Context context)668 public Lifecycle(Context context) { 669 super(context); 670 if (DEBUG) { 671 Binder.LOG_RUNTIME_EXCEPTION = true; 672 } 673 mService = new ShortcutService(context); 674 } 675 676 @Override onStart()677 public void onStart() { 678 publishBinderService(Context.SHORTCUT_SERVICE, mService); 679 } 680 681 @Override onBootPhase(int phase)682 public void onBootPhase(int phase) { 683 mService.onBootPhase(phase); 684 } 685 686 @Override onUserStopping(@onNull TargetUser user)687 public void onUserStopping(@NonNull TargetUser user) { 688 mService.handleStopUser(user.getUserIdentifier()); 689 } 690 691 @Override onUserUnlocking(@onNull TargetUser user)692 public void onUserUnlocking(@NonNull TargetUser user) { 693 mService.handleUnlockUser(user.getUserIdentifier()); 694 } 695 } 696 697 /** lifecycle event */ onBootPhase(int phase)698 void onBootPhase(int phase) { 699 if (DEBUG || DEBUG_REBOOT) { 700 Slog.d(TAG, "onBootPhase: " + phase); 701 } 702 switch (phase) { 703 case SystemService.PHASE_LOCK_SETTINGS_READY: 704 initialize(); 705 break; 706 case SystemService.PHASE_BOOT_COMPLETED: 707 mBootCompleted.set(true); 708 break; 709 } 710 } 711 712 /** lifecycle event */ handleUnlockUser(int userId)713 void handleUnlockUser(int userId) { 714 if (DEBUG || DEBUG_REBOOT) { 715 Slog.d(TAG, "handleUnlockUser: user=" + userId); 716 } 717 synchronized (mUnlockedUsers) { 718 mUnlockedUsers.put(userId, true); 719 } 720 721 // Preload the user data. 722 // Note, we don't use mHandler here but instead just start a new thread. 723 // This is because mHandler (which uses com.android.internal.os.BackgroundThread) is very 724 // busy at this point and this could take hundreds of milliseconds, which would be too 725 // late since the launcher would already have started. 726 // So we just create a new thread. This code runs rarely, so we don't use a thread pool 727 // or anything. 728 final long start = getStatStartTime(); 729 injectRunOnNewThread(() -> { 730 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutHandleUnlockUser"); 731 synchronized (mServiceLock) { 732 logDurationStat(Stats.ASYNC_PRELOAD_USER_DELAY, start); 733 getUserShortcutsLocked(userId); 734 } 735 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 736 }); 737 } 738 739 /** lifecycle event */ handleStopUser(int userId)740 void handleStopUser(int userId) { 741 if (DEBUG || DEBUG_REBOOT) { 742 Slog.d(TAG, "handleStopUser: user=" + userId); 743 } 744 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutHandleStopUser"); 745 synchronized (mServiceLock) { 746 unloadUserLocked(userId); 747 748 synchronized (mUnlockedUsers) { 749 mUnlockedUsers.put(userId, false); 750 } 751 } 752 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 753 } 754 755 @GuardedBy("mServiceLock") unloadUserLocked(int userId)756 private void unloadUserLocked(int userId) { 757 if (DEBUG || DEBUG_REBOOT) { 758 Slog.d(TAG, "unloadUserLocked: user=" + userId); 759 } 760 // Cancel any ongoing background tasks. 761 getUserShortcutsLocked(userId).cancelAllInFlightTasks(); 762 763 // Save all dirty information. 764 saveDirtyInfo(); 765 766 // Unload 767 mUsers.delete(userId); 768 } 769 770 /** Return the base state file name */ getBaseStateFile()771 final ResilientAtomicFile getBaseStateFile() { 772 File mainFile = new File(injectSystemDataPath(), FILENAME_BASE_STATE); 773 File temporaryBackup = new File(injectSystemDataPath(), 774 FILENAME_BASE_STATE + ".backup"); 775 File reserveCopy = new File(injectSystemDataPath(), 776 FILENAME_BASE_STATE + ".reservecopy"); 777 int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH; 778 return new ResilientAtomicFile(mainFile, temporaryBackup, reserveCopy, fileMode, 779 "base shortcut", null); 780 } 781 782 /** 783 * Init the instance. (load the state file, etc) 784 */ initialize()785 private void initialize() { 786 synchronized (mServiceLock) { 787 loadConfigurationLocked(); 788 loadBaseStateLocked(); 789 } 790 } 791 792 /** 793 * Load the configuration from Settings. 794 */ loadConfigurationLocked()795 private void loadConfigurationLocked() { 796 updateConfigurationLocked(injectShortcutManagerConstants()); 797 } 798 799 /** 800 * Load the configuration from Settings. 801 */ 802 @VisibleForTesting updateConfigurationLocked(String config)803 boolean updateConfigurationLocked(String config) { 804 boolean result = true; 805 806 final KeyValueListParser parser = new KeyValueListParser(','); 807 try { 808 parser.setString(config); 809 } catch (IllegalArgumentException e) { 810 // Failed to parse the settings string, log this and move on 811 // with defaults. 812 Slog.e(TAG, "Bad shortcut manager settings", e); 813 result = false; 814 } 815 816 mSaveDelayMillis = Math.max(0, (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS, 817 DEFAULT_SAVE_DELAY_MS)); 818 819 mResetInterval = Math.max(1, parser.getLong( 820 ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC) 821 * 1000L); 822 823 mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong( 824 ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL)); 825 826 mMaxShortcuts = Math.max(0, (int) parser.getLong( 827 ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY)); 828 829 mMaxShortcutsPerApp = Math.max(0, (int) parser.getLong( 830 ConfigConstants.KEY_MAX_SHORTCUTS_PER_APP, DEFAULT_MAX_SHORTCUTS_PER_APP)); 831 832 final int iconDimensionDp = Math.max(1, injectIsLowRamDevice() 833 ? (int) parser.getLong( 834 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM, 835 DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP) 836 : (int) parser.getLong( 837 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP, 838 DEFAULT_MAX_ICON_DIMENSION_DP)); 839 840 mMaxIconDimension = injectDipToPixel(iconDimensionDp); 841 842 mIconPersistFormat = CompressFormat.valueOf( 843 parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT)); 844 845 mIconPersistQuality = (int) parser.getLong( 846 ConfigConstants.KEY_ICON_QUALITY, 847 DEFAULT_ICON_PERSIST_QUALITY); 848 849 return result; 850 } 851 852 @VisibleForTesting injectShortcutManagerConstants()853 String injectShortcutManagerConstants() { 854 return android.provider.Settings.Global.getString( 855 mContext.getContentResolver(), 856 android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS); 857 } 858 859 @VisibleForTesting injectDipToPixel(int dip)860 int injectDipToPixel(int dip) { 861 return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, 862 mContext.getResources().getDisplayMetrics()); 863 } 864 865 // === Persisting === 866 867 @Nullable parseStringAttribute(TypedXmlPullParser parser, String attribute)868 static String parseStringAttribute(TypedXmlPullParser parser, String attribute) { 869 return parser.getAttributeValue(null, attribute); 870 } 871 parseBooleanAttribute(TypedXmlPullParser parser, String attribute)872 static boolean parseBooleanAttribute(TypedXmlPullParser parser, String attribute) { 873 return parseLongAttribute(parser, attribute) == 1; 874 } 875 parseBooleanAttribute(TypedXmlPullParser parser, String attribute, boolean def)876 static boolean parseBooleanAttribute(TypedXmlPullParser parser, String attribute, boolean def) { 877 return parseLongAttribute(parser, attribute, (def ? 1 : 0)) == 1; 878 } 879 parseIntAttribute(TypedXmlPullParser parser, String attribute)880 static int parseIntAttribute(TypedXmlPullParser parser, String attribute) { 881 return (int) parseLongAttribute(parser, attribute); 882 } 883 parseIntAttribute(TypedXmlPullParser parser, String attribute, int def)884 static int parseIntAttribute(TypedXmlPullParser parser, String attribute, int def) { 885 return (int) parseLongAttribute(parser, attribute, def); 886 } 887 parseLongAttribute(TypedXmlPullParser parser, String attribute)888 static long parseLongAttribute(TypedXmlPullParser parser, String attribute) { 889 return parseLongAttribute(parser, attribute, 0); 890 } 891 parseLongAttribute(TypedXmlPullParser parser, String attribute, long def)892 static long parseLongAttribute(TypedXmlPullParser parser, String attribute, long def) { 893 final String value = parseStringAttribute(parser, attribute); 894 if (TextUtils.isEmpty(value)) { 895 return def; 896 } 897 try { 898 return Long.parseLong(value); 899 } catch (NumberFormatException e) { 900 Slog.e(TAG, "Error parsing long " + value); 901 return def; 902 } 903 } 904 905 @Nullable parseComponentNameAttribute(TypedXmlPullParser parser, String attribute)906 static ComponentName parseComponentNameAttribute(TypedXmlPullParser parser, String attribute) { 907 final String value = parseStringAttribute(parser, attribute); 908 if (TextUtils.isEmpty(value)) { 909 return null; 910 } 911 return ComponentName.unflattenFromString(value); 912 } 913 914 @Nullable parseIntentAttributeNoDefault(TypedXmlPullParser parser, String attribute)915 static Intent parseIntentAttributeNoDefault(TypedXmlPullParser parser, String attribute) { 916 final String value = parseStringAttribute(parser, attribute); 917 Intent parsed = null; 918 if (!TextUtils.isEmpty(value)) { 919 try { 920 parsed = Intent.parseUri(value, /* flags =*/ 0); 921 } catch (URISyntaxException e) { 922 Slog.e(TAG, "Error parsing intent", e); 923 } 924 } 925 return parsed; 926 } 927 928 @Nullable parseIntentAttribute(TypedXmlPullParser parser, String attribute)929 static Intent parseIntentAttribute(TypedXmlPullParser parser, String attribute) { 930 Intent parsed = parseIntentAttributeNoDefault(parser, attribute); 931 if (parsed == null) { 932 // Default intent. 933 parsed = new Intent(Intent.ACTION_VIEW); 934 } 935 return parsed; 936 } 937 writeTagValue(TypedXmlSerializer out, String tag, String value)938 static void writeTagValue(TypedXmlSerializer out, String tag, String value) throws IOException { 939 if (TextUtils.isEmpty(value)) return; 940 941 out.startTag(null, tag); 942 out.attribute(null, ATTR_VALUE, value); 943 out.endTag(null, tag); 944 } 945 writeTagValue(TypedXmlSerializer out, String tag, long value)946 static void writeTagValue(TypedXmlSerializer out, String tag, long value) throws IOException { 947 writeTagValue(out, tag, Long.toString(value)); 948 } 949 writeTagValue(TypedXmlSerializer out, String tag, ComponentName name)950 static void writeTagValue(TypedXmlSerializer out, String tag, ComponentName name) 951 throws IOException { 952 if (name == null) return; 953 writeTagValue(out, tag, name.flattenToString()); 954 } 955 writeTagExtra(TypedXmlSerializer out, String tag, PersistableBundle bundle)956 static void writeTagExtra(TypedXmlSerializer out, String tag, PersistableBundle bundle) 957 throws IOException, XmlPullParserException { 958 if (bundle == null) return; 959 960 out.startTag(null, tag); 961 bundle.saveToXml(out); 962 out.endTag(null, tag); 963 } 964 writeAttr(TypedXmlSerializer out, String name, CharSequence value)965 static void writeAttr(TypedXmlSerializer out, String name, CharSequence value) 966 throws IOException { 967 if (TextUtils.isEmpty(value)) return; 968 969 out.attribute(null, name, value.toString()); 970 } 971 writeAttr(TypedXmlSerializer out, String name, long value)972 static void writeAttr(TypedXmlSerializer out, String name, long value) throws IOException { 973 writeAttr(out, name, String.valueOf(value)); 974 } 975 writeAttr(TypedXmlSerializer out, String name, boolean value)976 static void writeAttr(TypedXmlSerializer out, String name, boolean value) throws IOException { 977 if (value) { 978 writeAttr(out, name, "1"); 979 } else { 980 writeAttr(out, name, "0"); 981 } 982 } 983 writeAttr(TypedXmlSerializer out, String name, ComponentName comp)984 static void writeAttr(TypedXmlSerializer out, String name, ComponentName comp) 985 throws IOException { 986 if (comp == null) return; 987 writeAttr(out, name, comp.flattenToString()); 988 } 989 writeAttr(TypedXmlSerializer out, String name, Intent intent)990 static void writeAttr(TypedXmlSerializer out, String name, Intent intent) throws IOException { 991 if (intent == null) return; 992 993 writeAttr(out, name, intent.toUri(/* flags =*/ 0)); 994 } 995 996 @VisibleForTesting saveBaseState()997 void saveBaseState() { 998 try (ResilientAtomicFile file = getBaseStateFile()) { 999 if (DEBUG || DEBUG_REBOOT) { 1000 Slog.d(TAG, "Saving to " + file.getBaseFile()); 1001 } 1002 1003 FileOutputStream outs = null; 1004 try { 1005 synchronized (mServiceLock) { 1006 outs = file.startWrite(); 1007 } 1008 1009 // Write to XML 1010 TypedXmlSerializer out = Xml.resolveSerializer(outs); 1011 out.startDocument(null, true); 1012 out.startTag(null, TAG_ROOT); 1013 1014 // Body. 1015 // No locking required. Ok to add lock later if we save more data. 1016 writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime.get()); 1017 1018 // Epilogue. 1019 out.endTag(null, TAG_ROOT); 1020 out.endDocument(); 1021 1022 // Close. 1023 file.finishWrite(outs); 1024 } catch (IOException e) { 1025 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); 1026 file.failWrite(outs); 1027 } 1028 } 1029 } 1030 1031 @GuardedBy("mServiceLock") loadBaseStateLocked()1032 private void loadBaseStateLocked() { 1033 mRawLastResetTime.set(0); 1034 1035 try (ResilientAtomicFile file = getBaseStateFile()) { 1036 if (DEBUG || DEBUG_REBOOT) { 1037 Slog.d(TAG, "Loading from " + file.getBaseFile()); 1038 } 1039 FileInputStream in = null; 1040 try { 1041 in = file.openRead(); 1042 if (in == null) { 1043 throw new FileNotFoundException(file.getBaseFile().getAbsolutePath()); 1044 } 1045 1046 TypedXmlPullParser parser = Xml.resolvePullParser(in); 1047 1048 int type; 1049 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 1050 if (type != XmlPullParser.START_TAG) { 1051 continue; 1052 } 1053 final int depth = parser.getDepth(); 1054 // Check the root tag 1055 final String tag = parser.getName(); 1056 if (depth == 1) { 1057 if (!TAG_ROOT.equals(tag)) { 1058 Slog.e(TAG, "Invalid root tag: " + tag); 1059 return; 1060 } 1061 continue; 1062 } 1063 // Assume depth == 2 1064 switch (tag) { 1065 case TAG_LAST_RESET_TIME: 1066 mRawLastResetTime.set(parseLongAttribute(parser, ATTR_VALUE)); 1067 break; 1068 default: 1069 Slog.e(TAG, "Invalid tag: " + tag); 1070 break; 1071 } 1072 } 1073 } catch (FileNotFoundException e) { 1074 // Use the default 1075 } catch (IOException | XmlPullParserException e) { 1076 // Remove corrupted file and retry. 1077 file.failRead(in, e); 1078 loadBaseStateLocked(); 1079 return; 1080 } 1081 } 1082 // Adjust the last reset time. 1083 getLastResetTimeLocked(); 1084 } 1085 1086 @VisibleForTesting getUserFile(@serIdInt int userId)1087 final ResilientAtomicFile getUserFile(@UserIdInt int userId) { 1088 File mainFile = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); 1089 File temporaryBackup = new File(injectUserDataPath(userId), 1090 FILENAME_USER_PACKAGES + ".backup"); 1091 File reserveCopy = new File(injectUserDataPath(userId), 1092 FILENAME_USER_PACKAGES_RESERVE_COPY); 1093 int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH; 1094 return new ResilientAtomicFile(mainFile, temporaryBackup, reserveCopy, fileMode, 1095 "user shortcut", null); 1096 } 1097 saveUser(@serIdInt int userId)1098 private void saveUser(@UserIdInt int userId) { 1099 try (ResilientAtomicFile file = getUserFile(userId)) { 1100 FileOutputStream os = null; 1101 try { 1102 if (DEBUG || DEBUG_REBOOT) { 1103 Slog.d(TAG, "Saving to " + file); 1104 } 1105 1106 synchronized (mServiceLock) { 1107 os = file.startWrite(); 1108 saveUserInternalLocked(userId, os, /* forBackup= */ false); 1109 } 1110 1111 file.finishWrite(os); 1112 1113 // Remove all dangling bitmap files. 1114 cleanupDanglingBitmapDirectoriesLocked(userId); 1115 } catch (XmlPullParserException | IOException e) { 1116 Slog.e(TAG, "Failed to write to file " + file, e); 1117 file.failWrite(os); 1118 } 1119 } 1120 1121 getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger); 1122 } 1123 1124 @GuardedBy("mServiceLock") saveUserInternalLocked(@serIdInt int userId, OutputStream os, boolean forBackup)1125 private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os, 1126 boolean forBackup) throws IOException, XmlPullParserException { 1127 1128 // Write to XML 1129 final TypedXmlSerializer out; 1130 if (forBackup) { 1131 out = Xml.newFastSerializer(); 1132 out.setOutput(os, StandardCharsets.UTF_8.name()); 1133 } else { 1134 out = Xml.resolveSerializer(os); 1135 } 1136 out.startDocument(null, true); 1137 1138 getUserShortcutsLocked(userId).saveToXml(out, forBackup); 1139 1140 out.endDocument(); 1141 1142 os.flush(); 1143 } 1144 throwForInvalidTag(int depth, String tag)1145 static IOException throwForInvalidTag(int depth, String tag) throws IOException { 1146 throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth)); 1147 } 1148 warnForInvalidTag(int depth, String tag)1149 static void warnForInvalidTag(int depth, String tag) throws IOException { 1150 Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth)); 1151 } 1152 1153 @Nullable loadUserLocked(@serIdInt int userId)1154 private ShortcutUser loadUserLocked(@UserIdInt int userId) { 1155 try (ResilientAtomicFile file = getUserFile(userId)) { 1156 FileInputStream in = null; 1157 try { 1158 if (DEBUG || DEBUG_REBOOT) { 1159 Slog.d(TAG, "Loading from " + file); 1160 } 1161 in = file.openRead(); 1162 if (in == null) { 1163 if (DEBUG || DEBUG_REBOOT) { 1164 Slog.d(TAG, "Not found " + file); 1165 } 1166 return null; 1167 } 1168 return loadUserInternal(userId, in, /* forBackup= */ false); 1169 } catch (Exception e) { 1170 // Remove corrupted file and retry. 1171 file.failRead(in, e); 1172 return loadUserLocked(userId); 1173 } 1174 } 1175 } 1176 loadUserInternal(@serIdInt int userId, InputStream is, boolean fromBackup)1177 private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, 1178 boolean fromBackup) throws XmlPullParserException, IOException, 1179 InvalidFileFormatException { 1180 1181 ShortcutUser ret = null; 1182 TypedXmlPullParser parser; 1183 if (fromBackup) { 1184 parser = Xml.newFastPullParser(); 1185 parser.setInput(is, StandardCharsets.UTF_8.name()); 1186 } else { 1187 parser = Xml.resolvePullParser(is); 1188 } 1189 1190 int type; 1191 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 1192 if (type != XmlPullParser.START_TAG) { 1193 continue; 1194 } 1195 final int depth = parser.getDepth(); 1196 1197 final String tag = parser.getName(); 1198 if (DEBUG_LOAD || DEBUG_REBOOT) { 1199 Slog.d(TAG, String.format("depth=%d type=%d name=%s", 1200 depth, type, tag)); 1201 } 1202 if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) { 1203 ret = ShortcutUser.loadFromXml(this, parser, userId, fromBackup); 1204 continue; 1205 } 1206 throwForInvalidTag(depth, tag); 1207 } 1208 return ret; 1209 } 1210 scheduleSaveBaseState()1211 private void scheduleSaveBaseState() { 1212 scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state. 1213 } 1214 scheduleSaveUser(@serIdInt int userId)1215 void scheduleSaveUser(@UserIdInt int userId) { 1216 scheduleSaveInner(userId); 1217 } 1218 1219 // In order to re-schedule, we need to reuse the same instance, so keep it in final. 1220 private final Runnable mSaveDirtyInfoRunner = this::saveDirtyInfo; 1221 scheduleSaveInner(@serIdInt int userId)1222 private void scheduleSaveInner(@UserIdInt int userId) { 1223 if (DEBUG || DEBUG_REBOOT) { 1224 Slog.d(TAG, "Scheduling to save for " + userId); 1225 } 1226 synchronized (mServiceLock) { 1227 if (!mDirtyUserIds.contains(userId)) { 1228 mDirtyUserIds.add(userId); 1229 } 1230 } 1231 // If already scheduled, remove that and re-schedule in N seconds. 1232 mHandler.removeCallbacks(mSaveDirtyInfoRunner); 1233 mHandler.postDelayed(mSaveDirtyInfoRunner, mSaveDelayMillis); 1234 } 1235 1236 @VisibleForTesting saveDirtyInfo()1237 void saveDirtyInfo() { 1238 if (DEBUG || DEBUG_REBOOT) { 1239 Slog.d(TAG, "saveDirtyInfo"); 1240 } 1241 if (mShutdown.get()) { 1242 return; 1243 } 1244 try { 1245 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutSaveDirtyInfo"); 1246 List<Integer> dirtyUserIds = new ArrayList<>(); 1247 synchronized (mServiceLock) { 1248 List<Integer> tmp = mDirtyUserIds; 1249 mDirtyUserIds = dirtyUserIds; 1250 dirtyUserIds = tmp; 1251 } 1252 for (int i = dirtyUserIds.size() - 1; i >= 0; i--) { 1253 final int userId = dirtyUserIds.get(i); 1254 if (userId == UserHandle.USER_NULL) { // USER_NULL for base state. 1255 saveBaseState(); 1256 } else { 1257 saveUser(userId); 1258 } 1259 } 1260 } catch (Exception e) { 1261 wtf("Exception in saveDirtyInfo", e); 1262 } finally { 1263 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 1264 } 1265 } 1266 1267 /** Return the last reset time. */ 1268 @GuardedBy("mServiceLock") getLastResetTimeLocked()1269 long getLastResetTimeLocked() { 1270 updateTimesLocked(); 1271 return mRawLastResetTime.get(); 1272 } 1273 1274 /** Return the next reset time. */ 1275 @GuardedBy("mServiceLock") getNextResetTimeLocked()1276 long getNextResetTimeLocked() { 1277 updateTimesLocked(); 1278 return mRawLastResetTime.get() + mResetInterval; 1279 } 1280 isClockValid(long time)1281 static boolean isClockValid(long time) { 1282 return time >= 1420070400; // Thu, 01 Jan 2015 00:00:00 GMT 1283 } 1284 1285 /** 1286 * Update the last reset time. 1287 */ 1288 @GuardedBy("mServiceLock") updateTimesLocked()1289 private void updateTimesLocked() { 1290 1291 final long now = injectCurrentTimeMillis(); 1292 1293 final long prevLastResetTime = mRawLastResetTime.get(); 1294 long newLastResetTime = prevLastResetTime; 1295 1296 if (newLastResetTime == 0) { // first launch. 1297 // TODO Randomize?? 1298 newLastResetTime = now; 1299 } else if (now < newLastResetTime) { 1300 // Clock rewound. 1301 if (isClockValid(now)) { 1302 Slog.w(TAG, "Clock rewound"); 1303 // TODO Randomize?? 1304 newLastResetTime = now; 1305 } 1306 } else if ((newLastResetTime + mResetInterval) <= now) { 1307 final long offset = newLastResetTime % mResetInterval; 1308 newLastResetTime = ((now / mResetInterval) * mResetInterval) + offset; 1309 } 1310 1311 mRawLastResetTime.set(newLastResetTime); 1312 if (prevLastResetTime != newLastResetTime) { 1313 scheduleSaveBaseState(); 1314 } 1315 } 1316 1317 // Requires mServiceLock held, but "Locked" prefix would look weird so we just say "L". isUserUnlockedL(@serIdInt int userId)1318 protected boolean isUserUnlockedL(@UserIdInt int userId) { 1319 // First, check the local copy. 1320 synchronized (mUnlockedUsers) { 1321 if (mUnlockedUsers.get(userId)) { 1322 return true; 1323 } 1324 } 1325 1326 // If the local copy says the user is locked, check with AM for the actual state, since 1327 // the user might just have been unlocked. 1328 // Note we just don't use isUserUnlockingOrUnlocked() here, because it'll return false 1329 // when the user is STOPPING, which we still want to consider as "unlocked". 1330 return mUserManagerInternal.isUserUnlockingOrUnlocked(userId); 1331 } 1332 1333 // Requires mServiceLock held, but "Locked" prefix would look weird so we just say "L". throwIfUserLockedL(@serIdInt int userId)1334 void throwIfUserLockedL(@UserIdInt int userId) { 1335 if (!isUserUnlockedL(userId)) { 1336 throw new IllegalStateException("User " + userId + " is locked or not running"); 1337 } 1338 } 1339 1340 @GuardedBy("mServiceLock") 1341 @NonNull isUserLoadedLocked(@serIdInt int userId)1342 private boolean isUserLoadedLocked(@UserIdInt int userId) { 1343 return mUsers.get(userId) != null; 1344 } 1345 1346 private int mLastLockedUser = -1; 1347 1348 /** Return the per-user state. */ 1349 @GuardedBy("mServiceLock") 1350 @NonNull getUserShortcutsLocked(@serIdInt int userId)1351 ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) { 1352 if (!isUserUnlockedL(userId)) { 1353 // Only do wtf once for each user. (until the user is unlocked) 1354 if (userId != mLastLockedUser) { 1355 wtf("User still locked"); 1356 mLastLockedUser = userId; 1357 } 1358 } else { 1359 mLastLockedUser = -1; 1360 } 1361 1362 ShortcutUser userPackages = mUsers.get(userId); 1363 if (userPackages == null) { 1364 userPackages = loadUserLocked(userId); 1365 if (userPackages == null) { 1366 userPackages = new ShortcutUser(this, userId); 1367 } 1368 mUsers.put(userId, userPackages); 1369 1370 // Also when a user's data is first accessed, scan all packages. 1371 checkPackageChanges(userId); 1372 } 1373 return userPackages; 1374 } 1375 1376 /** Return the non-persistent per-user state. */ 1377 @GuardedBy("mNonPersistentUsersLock") 1378 @NonNull getNonPersistentUserLocked(@serIdInt int userId)1379 ShortcutNonPersistentUser getNonPersistentUserLocked(@UserIdInt int userId) { 1380 ShortcutNonPersistentUser ret = mShortcutNonPersistentUsers.get(userId); 1381 if (ret == null) { 1382 ret = new ShortcutNonPersistentUser(userId); 1383 mShortcutNonPersistentUsers.put(userId, ret); 1384 } 1385 return ret; 1386 } 1387 1388 @GuardedBy("mServiceLock") forEachLoadedUserLocked(@onNull Consumer<ShortcutUser> c)1389 void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) { 1390 for (int i = mUsers.size() - 1; i >= 0; i--) { 1391 c.accept(mUsers.valueAt(i)); 1392 } 1393 } 1394 1395 /** 1396 * Return the per-user per-package state. If the caller is a publisher, use 1397 * {@link #getPackageShortcutsForPublisherLocked} instead. 1398 */ 1399 @GuardedBy("mServiceLock") 1400 @NonNull getPackageShortcutsLocked( @onNull String packageName, @UserIdInt int userId)1401 ShortcutPackage getPackageShortcutsLocked( 1402 @NonNull String packageName, @UserIdInt int userId) { 1403 return getUserShortcutsLocked(userId).getPackageShortcuts(packageName); 1404 } 1405 1406 /** Return the per-user per-package state. Use this when the caller is a publisher. */ 1407 @GuardedBy("mServiceLock") 1408 @NonNull getPackageShortcutsForPublisherLocked( @onNull String packageName, @UserIdInt int userId)1409 ShortcutPackage getPackageShortcutsForPublisherLocked( 1410 @NonNull String packageName, @UserIdInt int userId) { 1411 final ShortcutPackage ret = getUserShortcutsLocked(userId).getPackageShortcuts(packageName); 1412 ret.getUser().onCalledByPublisher(packageName); 1413 return ret; 1414 } 1415 1416 @GuardedBy("mServiceLock") 1417 @NonNull getLauncherShortcutsLocked( @onNull String packageName, @UserIdInt int ownerUserId, @UserIdInt int launcherUserId)1418 ShortcutLauncher getLauncherShortcutsLocked( 1419 @NonNull String packageName, @UserIdInt int ownerUserId, 1420 @UserIdInt int launcherUserId) { 1421 return getUserShortcutsLocked(ownerUserId) 1422 .getLauncherShortcuts(packageName, launcherUserId); 1423 } 1424 1425 // === Caller validation === 1426 cleanupBitmapsForPackage(@serIdInt int userId, String packageName)1427 public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) { 1428 final File packagePath = new File(getUserBitmapFilePath(userId), packageName); 1429 if (!packagePath.isDirectory()) { 1430 return; 1431 } 1432 // ShortcutPackage is already removed at this point, we can safely remove the folder. 1433 if (!(FileUtils.deleteContents(packagePath) && packagePath.delete())) { 1434 Slog.w(TAG, "Unable to remove directory " + packagePath); 1435 } 1436 } 1437 1438 /** 1439 * Remove dangling bitmap files for a user. 1440 * 1441 * Note this method must be called with the lock held after calling 1442 * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap 1443 * saves are going on. 1444 */ 1445 @GuardedBy("mServiceLock") cleanupDanglingBitmapDirectoriesLocked(@serIdInt int userId)1446 private void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) { 1447 if (DEBUG) { 1448 Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId); 1449 } 1450 final long start = getStatStartTime(); 1451 1452 final ShortcutUser user = getUserShortcutsLocked(userId); 1453 1454 final File bitmapDir = getUserBitmapFilePath(userId); 1455 final File[] children = bitmapDir.listFiles(); 1456 if (children == null) { 1457 return; 1458 } 1459 for (File child : children) { 1460 if (!child.isDirectory()) { 1461 continue; 1462 } 1463 final String packageName = child.getName(); 1464 if (DEBUG) { 1465 Slog.d(TAG, "cleanupDanglingBitmaps: Found directory=" + packageName); 1466 } 1467 if (!user.hasPackage(packageName)) { 1468 if (DEBUG) { 1469 Slog.d(TAG, "Removing dangling bitmap directory: " + packageName); 1470 } 1471 cleanupBitmapsForPackage(userId, packageName); 1472 } else { 1473 user.getPackageShortcuts(packageName).cleanupDanglingBitmapFiles(child); 1474 } 1475 } 1476 logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start); 1477 } 1478 1479 @VisibleForTesting 1480 static class FileOutputStreamWithPath extends FileOutputStream { 1481 private final File mFile; 1482 FileOutputStreamWithPath(File file)1483 public FileOutputStreamWithPath(File file) throws FileNotFoundException { 1484 super(file); 1485 mFile = file; 1486 } 1487 getFile()1488 public File getFile() { 1489 return mFile; 1490 } 1491 } 1492 1493 /** 1494 * Build the cached bitmap filename for a shortcut icon. 1495 * 1496 * The filename will be based on the ID, except certain characters will be escaped. 1497 */ openIconFileForWrite(@serIdInt int userId, ShortcutInfo shortcut)1498 FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut) 1499 throws IOException { 1500 final File packagePath = new File(getUserBitmapFilePath(userId), 1501 shortcut.getPackage()); 1502 if (!packagePath.isDirectory()) { 1503 packagePath.mkdirs(); 1504 if (!packagePath.isDirectory()) { 1505 throw new IOException("Unable to create directory " + packagePath); 1506 } 1507 SELinux.restorecon(packagePath); 1508 } 1509 1510 final String baseName = String.valueOf(injectCurrentTimeMillis()); 1511 for (int suffix = 0; ; suffix++) { 1512 final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png"; 1513 final File file = new File(packagePath, filename); 1514 if (!file.exists()) { 1515 if (DEBUG) { 1516 Slog.d(TAG, "Saving icon to " + file.getAbsolutePath()); 1517 } 1518 return new FileOutputStreamWithPath(file); 1519 } 1520 } 1521 } 1522 saveIconAndFixUpShortcutLocked(ShortcutPackage p, ShortcutInfo shortcut)1523 void saveIconAndFixUpShortcutLocked(ShortcutPackage p, ShortcutInfo shortcut) { 1524 if (shortcut.hasIconFile() || shortcut.hasIconResource() || shortcut.hasIconUri()) { 1525 return; 1526 } 1527 1528 final long token = injectClearCallingIdentity(); 1529 try { 1530 // Clear icon info on the shortcut. 1531 p.removeIcon(shortcut); 1532 1533 final Icon icon = shortcut.getIcon(); 1534 if (icon == null) { 1535 return; // has no icon 1536 } 1537 int maxIconDimension = mMaxIconDimension; 1538 Bitmap bitmap; 1539 try { 1540 switch (icon.getType()) { 1541 case Icon.TYPE_RESOURCE: { 1542 injectValidateIconResPackage(shortcut, icon); 1543 1544 shortcut.setIconResourceId(icon.getResId()); 1545 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES); 1546 return; 1547 } 1548 case Icon.TYPE_URI: 1549 shortcut.setIconUri(icon.getUriString()); 1550 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_URI); 1551 return; 1552 case Icon.TYPE_URI_ADAPTIVE_BITMAP: 1553 shortcut.setIconUri(icon.getUriString()); 1554 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_URI 1555 | ShortcutInfo.FLAG_ADAPTIVE_BITMAP); 1556 return; 1557 case Icon.TYPE_BITMAP: 1558 bitmap = icon.getBitmap(); // Don't recycle in this case. 1559 break; 1560 case Icon.TYPE_ADAPTIVE_BITMAP: { 1561 bitmap = icon.getBitmap(); // Don't recycle in this case. 1562 maxIconDimension *= (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction()); 1563 break; 1564 } 1565 default: 1566 // This shouldn't happen because we've already validated the icon, but 1567 // just in case. 1568 throw ShortcutInfo.getInvalidIconException(); 1569 } 1570 p.saveBitmap(shortcut, maxIconDimension, mIconPersistFormat, mIconPersistQuality); 1571 } finally { 1572 // Once saved, we won't use the original icon information, so null it out. 1573 shortcut.clearIcon(); 1574 } 1575 } finally { 1576 injectRestoreCallingIdentity(token); 1577 } 1578 } 1579 1580 // Unfortunately we can't do this check in unit tests because we fake creator package names, 1581 // so override in unit tests. 1582 // TODO CTS this case. injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon)1583 void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) { 1584 if (!shortcut.getPackage().equals(icon.getResPackage())) { 1585 throw new IllegalArgumentException( 1586 "Icon resource must reside in shortcut owner package"); 1587 } 1588 } 1589 shrinkBitmap(Bitmap in, int maxSize)1590 static Bitmap shrinkBitmap(Bitmap in, int maxSize) { 1591 // Original width/height. 1592 final int ow = in.getWidth(); 1593 final int oh = in.getHeight(); 1594 if ((ow <= maxSize) && (oh <= maxSize)) { 1595 if (DEBUG) { 1596 Slog.d(TAG, String.format("Icon size %dx%d, no need to shrink", ow, oh)); 1597 } 1598 return in; 1599 } 1600 final int longerDimension = Math.max(ow, oh); 1601 1602 // New width and height. 1603 final int nw = ow * maxSize / longerDimension; 1604 final int nh = oh * maxSize / longerDimension; 1605 if (DEBUG) { 1606 Slog.d(TAG, String.format("Icon size %dx%d, shrinking to %dx%d", 1607 ow, oh, nw, nh)); 1608 } 1609 1610 final Bitmap scaledBitmap = Bitmap.createBitmap(nw, nh, Bitmap.Config.ARGB_8888); 1611 final Canvas c = new Canvas(scaledBitmap); 1612 1613 final RectF dst = new RectF(0, 0, nw, nh); 1614 1615 c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null); 1616 1617 return scaledBitmap; 1618 } 1619 1620 /** 1621 * For a shortcut, update all resource names from resource IDs, and also update all 1622 * resource-based strings. 1623 */ fixUpShortcutResourceNamesAndValues(ShortcutInfo si)1624 void fixUpShortcutResourceNamesAndValues(ShortcutInfo si) { 1625 final Resources publisherRes = injectGetResourcesForApplicationAsUser( 1626 si.getPackage(), si.getUserId()); 1627 if (publisherRes != null) { 1628 final long start = getStatStartTime(); 1629 try { 1630 si.lookupAndFillInResourceNames(publisherRes); 1631 } finally { 1632 logDurationStat(Stats.RESOURCE_NAME_LOOKUP, start); 1633 } 1634 si.resolveResourceStrings(publisherRes); 1635 } 1636 } 1637 1638 // === Caller validation === 1639 isCallerSystem()1640 private boolean isCallerSystem() { 1641 final int callingUid = injectBinderCallingUid(); 1642 return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID); 1643 } 1644 isCallerShell()1645 private boolean isCallerShell() { 1646 final int callingUid = injectBinderCallingUid(); 1647 return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID; 1648 } 1649 1650 @VisibleForTesting injectChooserActivity()1651 ComponentName injectChooserActivity() { 1652 if (mChooserActivity == null) { 1653 mChooserActivity = ComponentName.unflattenFromString( 1654 mContext.getResources().getString(R.string.config_chooserActivity)); 1655 } 1656 return mChooserActivity; 1657 } 1658 isCallerChooserActivity()1659 private boolean isCallerChooserActivity() { 1660 // TODO(b/228975502): Migrate this check to a proper permission or role check 1661 final int callingUid = injectBinderCallingUid(); 1662 ComponentName systemChooser = injectChooserActivity(); 1663 if (systemChooser == null) { 1664 return false; 1665 } 1666 int uid = injectGetPackageUid(systemChooser.getPackageName(), UserHandle.USER_SYSTEM); 1667 return UserHandle.getAppId(uid) == UserHandle.getAppId(callingUid); 1668 } 1669 enforceSystemOrShell()1670 private void enforceSystemOrShell() { 1671 if (!(isCallerSystem() || isCallerShell())) { 1672 throw new SecurityException("Caller must be system or shell"); 1673 } 1674 } 1675 enforceShell()1676 private void enforceShell() { 1677 if (!isCallerShell()) { 1678 throw new SecurityException("Caller must be shell"); 1679 } 1680 } 1681 enforceSystem()1682 private void enforceSystem() { 1683 if (!isCallerSystem()) { 1684 throw new SecurityException("Caller must be system"); 1685 } 1686 } 1687 enforceResetThrottlingPermission()1688 private void enforceResetThrottlingPermission() { 1689 if (isCallerSystem()) { 1690 return; 1691 } 1692 enforceCallingOrSelfPermission( 1693 android.Manifest.permission.RESET_SHORTCUT_MANAGER_THROTTLING, null); 1694 } 1695 enforceCallingOrSelfPermission( @onNull String permission, @Nullable String message)1696 private void enforceCallingOrSelfPermission( 1697 @NonNull String permission, @Nullable String message) { 1698 if (isCallerSystem()) { 1699 return; 1700 } 1701 injectEnforceCallingPermission(permission, message); 1702 } 1703 1704 /** 1705 * Somehow overriding ServiceContext.enforceCallingPermission() in the unit tests would confuse 1706 * mockito. So instead we extracted it here and override it in the tests. 1707 */ 1708 @VisibleForTesting injectEnforceCallingPermission( @onNull String permission, @Nullable String message)1709 void injectEnforceCallingPermission( 1710 @NonNull String permission, @Nullable String message) { 1711 mContext.enforceCallingPermission(permission, message); 1712 } 1713 verifyCallerUserId(@serIdInt int userId)1714 private void verifyCallerUserId(@UserIdInt int userId) { 1715 if (isCallerSystem()) { 1716 return; // no check 1717 } 1718 1719 final int callingUid = injectBinderCallingUid(); 1720 1721 // Otherwise, make sure the arguments are valid. 1722 if (UserHandle.getUserId(callingUid) != userId) { 1723 throw new SecurityException("Invalid user-ID"); 1724 } 1725 } 1726 verifyCaller(@onNull String packageName, @UserIdInt int userId)1727 private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) { 1728 Preconditions.checkStringNotEmpty(packageName, "packageName"); 1729 1730 if (isCallerSystem()) { 1731 return; // no check 1732 } 1733 1734 final int callingUid = injectBinderCallingUid(); 1735 1736 // Otherwise, make sure the arguments are valid. 1737 if (UserHandle.getUserId(callingUid) != userId) { 1738 throw new SecurityException("Invalid user-ID"); 1739 } 1740 if (injectGetPackageUid(packageName, userId) != callingUid) { 1741 throw new SecurityException("Calling package name mismatch"); 1742 } 1743 Preconditions.checkState(!isEphemeralApp(packageName, userId), 1744 "Ephemeral apps can't use ShortcutManager"); 1745 } 1746 verifyShortcutInfoPackage(String callerPackage, ShortcutInfo si)1747 private void verifyShortcutInfoPackage(String callerPackage, ShortcutInfo si) { 1748 if (si == null) { 1749 return; 1750 } 1751 1752 if (!Objects.equals(callerPackage, si.getPackage())) { 1753 android.util.EventLog.writeEvent(0x534e4554, "109824443", -1, ""); 1754 throw new SecurityException("Shortcut package name mismatch"); 1755 } 1756 final int callingUid = injectBinderCallingUid(); 1757 if (UserHandle.getUserId(callingUid) != si.getUserId()) { 1758 throw new SecurityException("User-ID in shortcut doesn't match the caller"); 1759 } 1760 } 1761 verifyShortcutInfoPackages( String callerPackage, List<ShortcutInfo> list)1762 private void verifyShortcutInfoPackages( 1763 String callerPackage, List<ShortcutInfo> list) { 1764 final int size = list.size(); 1765 for (int i = 0; i < size; i++) { 1766 verifyShortcutInfoPackage(callerPackage, list.get(i)); 1767 } 1768 } 1769 1770 // Overridden in unit tests to execute r synchronously. injectPostToHandler(Runnable r)1771 void injectPostToHandler(Runnable r) { 1772 mHandler.post(r); 1773 } 1774 injectRunOnNewThread(Runnable r)1775 void injectRunOnNewThread(Runnable r) { 1776 new Thread(r).start(); 1777 } 1778 injectPostToHandlerDebounced(@onNull final Object token, @NonNull final Runnable r)1779 void injectPostToHandlerDebounced(@NonNull final Object token, @NonNull final Runnable r) { 1780 Objects.requireNonNull(token); 1781 Objects.requireNonNull(r); 1782 synchronized (mServiceLock) { 1783 mHandler.removeCallbacksAndMessages(token); 1784 mHandler.postDelayed(r, token, CALLBACK_DELAY); 1785 } 1786 } 1787 1788 /** 1789 * @throws IllegalArgumentException if {@code numShortcuts} is bigger than 1790 * {@link #getMaxActivityShortcuts()}. 1791 */ enforceMaxActivityShortcuts(int numShortcuts)1792 void enforceMaxActivityShortcuts(int numShortcuts) { 1793 if (numShortcuts > mMaxShortcuts) { 1794 throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded"); 1795 } 1796 } 1797 1798 /** 1799 * Return the max number of dynamic + manifest shortcuts for each launcher icon. 1800 */ getMaxActivityShortcuts()1801 int getMaxActivityShortcuts() { 1802 return mMaxShortcuts; 1803 } 1804 1805 /** 1806 * Return the max number of shortcuts can be retaiend in system ram for each application. 1807 */ getMaxAppShortcuts()1808 int getMaxAppShortcuts() { 1809 return mMaxShortcutsPerApp; 1810 } 1811 1812 /** 1813 * - Sends a notification to LauncherApps 1814 * - Write to file 1815 */ packageShortcutsChanged( @onNull final ShortcutPackage sp, @Nullable final List<ShortcutInfo> changedShortcuts, @Nullable final List<ShortcutInfo> removedShortcuts)1816 void packageShortcutsChanged( 1817 @NonNull final ShortcutPackage sp, 1818 @Nullable final List<ShortcutInfo> changedShortcuts, 1819 @Nullable final List<ShortcutInfo> removedShortcuts) { 1820 Objects.requireNonNull(sp); 1821 final String packageName = sp.getPackageName(); 1822 final int userId = sp.getPackageUserId(); 1823 if (DEBUG) { 1824 Slog.d(TAG, String.format( 1825 "Shortcut changes: package=%s, user=%d", packageName, userId)); 1826 } 1827 injectPostToHandlerDebounced(sp, notifyListenerRunnable(packageName, userId)); 1828 notifyShortcutChangeCallbacks(packageName, userId, changedShortcuts, removedShortcuts); 1829 sp.scheduleSave(); 1830 } 1831 notifyListeners(@onNull final String packageName, @UserIdInt final int userId)1832 private void notifyListeners(@NonNull final String packageName, @UserIdInt final int userId) { 1833 if (DEBUG) { 1834 Slog.d(TAG, String.format( 1835 "Shortcut changes: package=%s, user=%d", packageName, userId)); 1836 } 1837 injectPostToHandler(notifyListenerRunnable(packageName, userId)); 1838 } 1839 notifyListenerRunnable(@onNull final String packageName, @UserIdInt final int userId)1840 private Runnable notifyListenerRunnable(@NonNull final String packageName, 1841 @UserIdInt final int userId) { 1842 return () -> { 1843 try { 1844 final ArrayList<ShortcutChangeListener> copy; 1845 synchronized (mServiceLock) { 1846 if (!isUserUnlockedL(userId)) { 1847 return; 1848 } 1849 1850 copy = new ArrayList<>(mListeners); 1851 } 1852 // Note onShortcutChanged() needs to be called with the system service permissions. 1853 for (int i = copy.size() - 1; i >= 0; i--) { 1854 copy.get(i).onShortcutChanged(packageName, userId); 1855 } 1856 } catch (Exception ignore) { 1857 } 1858 }; 1859 } 1860 1861 private void notifyShortcutChangeCallbacks(@NonNull String packageName, @UserIdInt int userId, 1862 @Nullable final List<ShortcutInfo> changedShortcuts, 1863 @Nullable final List<ShortcutInfo> removedShortcuts) { 1864 final List<ShortcutInfo> changedList = removeNonKeyFields(changedShortcuts); 1865 final List<ShortcutInfo> removedList = removeNonKeyFields(removedShortcuts); 1866 1867 final UserHandle user = UserHandle.of(userId); 1868 injectPostToHandler(() -> { 1869 try { 1870 final ArrayList<LauncherApps.ShortcutChangeCallback> copy; 1871 synchronized (mServiceLock) { 1872 if (!isUserUnlockedL(userId)) { 1873 return; 1874 } 1875 1876 copy = new ArrayList<>(mShortcutChangeCallbacks); 1877 } 1878 for (int i = copy.size() - 1; i >= 0; i--) { 1879 if (!CollectionUtils.isEmpty(changedList)) { 1880 copy.get(i).onShortcutsAddedOrUpdated(packageName, changedList, user); 1881 } 1882 if (!CollectionUtils.isEmpty(removedList)) { 1883 copy.get(i).onShortcutsRemoved(packageName, removedList, user); 1884 } 1885 } 1886 } catch (Exception ignore) { 1887 } 1888 }); 1889 } 1890 1891 private List<ShortcutInfo> removeNonKeyFields(@Nullable List<ShortcutInfo> shortcutInfos) { 1892 if (CollectionUtils.isEmpty(shortcutInfos)) { 1893 return shortcutInfos; 1894 } 1895 1896 final int size = shortcutInfos.size(); 1897 List<ShortcutInfo> keyFieldOnlyShortcuts = new ArrayList<>(size); 1898 1899 for (int i = 0; i < size; i++) { 1900 final ShortcutInfo si = shortcutInfos.get(i); 1901 if (si.hasKeyFieldsOnly()) { 1902 keyFieldOnlyShortcuts.add(si); 1903 } else { 1904 keyFieldOnlyShortcuts.add(si.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO)); 1905 } 1906 } 1907 return keyFieldOnlyShortcuts; 1908 } 1909 1910 /** 1911 * Clean up / validate an incoming shortcut. 1912 * - Make sure all mandatory fields are set. 1913 * - Make sure the intent's extras are persistable, and them to set 1914 * {@link ShortcutInfo#mIntentPersistableExtrases}. Also clear its extras. 1915 * - Clear flags. 1916 */ 1917 private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate, 1918 boolean forPinRequest) { 1919 if (shortcut.isReturnedByServer()) { 1920 Log.w(TAG, 1921 "Re-publishing ShortcutInfo returned by server is not supported." 1922 + " Some information such as icon may lost from shortcut."); 1923 } 1924 Objects.requireNonNull(shortcut, "Null shortcut detected"); 1925 if (shortcut.getActivity() != null) { 1926 Preconditions.checkState( 1927 shortcut.getPackage().equals(shortcut.getActivity().getPackageName()), 1928 "Cannot publish shortcut: activity " + shortcut.getActivity() + " does not" 1929 + " belong to package " + shortcut.getPackage()); 1930 Preconditions.checkState( 1931 injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()), 1932 "Cannot publish shortcut: activity " + shortcut.getActivity() + " is not" 1933 + " main activity"); 1934 } 1935 1936 if (!forUpdate) { 1937 shortcut.enforceMandatoryFields(/* forPinned= */ forPinRequest); 1938 if (!forPinRequest) { 1939 Preconditions.checkState(shortcut.getActivity() != null, 1940 "Cannot publish shortcut: target activity is not set"); 1941 } 1942 } 1943 if (shortcut.getIcon() != null) { 1944 ShortcutInfo.validateIcon(shortcut.getIcon()); 1945 validateIconURI(shortcut); 1946 } 1947 1948 shortcut.replaceFlags(shortcut.getFlags() & ShortcutInfo.FLAG_LONG_LIVED); 1949 } 1950 1951 // Validates the calling process has permission to access shortcut icon's image uri 1952 private void validateIconURI(@NonNull final ShortcutInfo si) { 1953 final int callingUid = injectBinderCallingUid(); 1954 final Icon icon = si.getIcon(); 1955 if (icon == null) { 1956 // There's no icon in this shortcut, nothing to validate here. 1957 return; 1958 } 1959 int iconType = icon.getType(); 1960 if (iconType != Icon.TYPE_URI && iconType != Icon.TYPE_URI_ADAPTIVE_BITMAP) { 1961 // The icon is not URI-based, nothing to validate. 1962 return; 1963 } 1964 final Uri uri = icon.getUri(); 1965 mUriGrantsManagerInternal.checkGrantUriPermission(callingUid, si.getPackage(), 1966 ContentProvider.getUriWithoutUserId(uri), 1967 Intent.FLAG_GRANT_READ_URI_PERMISSION, 1968 ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(callingUid))); 1969 } 1970 1971 private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) { 1972 fixUpIncomingShortcutInfo(shortcut, forUpdate, /*forPinRequest=*/ false); 1973 } 1974 1975 public void validateShortcutForPinRequest(@NonNull ShortcutInfo shortcut) { 1976 fixUpIncomingShortcutInfo(shortcut, /* forUpdate= */ false, /*forPinRequest=*/ true); 1977 } 1978 1979 /** 1980 * When a shortcut has no target activity, set the default one from the package. 1981 */ 1982 private void fillInDefaultActivity(List<ShortcutInfo> shortcuts) { 1983 ComponentName defaultActivity = null; 1984 for (int i = shortcuts.size() - 1; i >= 0; i--) { 1985 final ShortcutInfo si = shortcuts.get(i); 1986 if (si.getActivity() == null) { 1987 if (defaultActivity == null) { 1988 defaultActivity = injectGetDefaultMainActivity( 1989 si.getPackage(), si.getUserId()); 1990 Preconditions.checkState(defaultActivity != null, 1991 "Launcher activity not found for package " + si.getPackage()); 1992 } 1993 si.setActivity(defaultActivity); 1994 } 1995 } 1996 } 1997 1998 private void assignImplicitRanks(List<ShortcutInfo> shortcuts) { 1999 for (int i = shortcuts.size() - 1; i >= 0; i--) { 2000 shortcuts.get(i).setImplicitRank(i); 2001 } 2002 } 2003 2004 private List<ShortcutInfo> setReturnedByServer(List<ShortcutInfo> shortcuts) { 2005 for (int i = shortcuts.size() - 1; i >= 0; i--) { 2006 shortcuts.get(i).setReturnedByServer(); 2007 } 2008 return shortcuts; 2009 } 2010 2011 // === APIs === 2012 2013 @Override 2014 public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, 2015 @UserIdInt int userId) { 2016 verifyCaller(packageName, userId); 2017 2018 final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( 2019 injectBinderCallingPid(), injectBinderCallingUid()); 2020 final List<ShortcutInfo> newShortcuts = 2021 (List<ShortcutInfo>) shortcutInfoList.getList(); 2022 verifyShortcutInfoPackages(packageName, newShortcuts); 2023 final int size = newShortcuts.size(); 2024 2025 List<ShortcutInfo> changedShortcuts = null; 2026 List<ShortcutInfo> removedShortcuts = null; 2027 final ShortcutPackage ps; 2028 2029 synchronized (mServiceLock) { 2030 throwIfUserLockedL(userId); 2031 2032 ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2033 2034 ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); 2035 ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); 2036 2037 fillInDefaultActivity(newShortcuts); 2038 2039 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET); 2040 2041 // Throttling. 2042 if (!ps.tryApiCall(unlimited)) { 2043 return false; 2044 } 2045 2046 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 2047 ps.clearAllImplicitRanks(); 2048 assignImplicitRanks(newShortcuts); 2049 2050 for (int i = 0; i < size; i++) { 2051 fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false); 2052 } 2053 2054 ArrayList<ShortcutInfo> cachedOrPinned = new ArrayList<>(); 2055 ps.findAll(cachedOrPinned, 2056 (ShortcutInfo si) -> si.isVisibleToPublisher() 2057 && si.isDynamic() && (si.isCached() || si.isPinned()), 2058 ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); 2059 2060 // First, remove all un-pinned and non-cached; dynamic shortcuts 2061 removedShortcuts = ps.deleteAllDynamicShortcuts(); 2062 2063 // Then, add/update all. We need to make sure to take over "pinned" flag. 2064 for (int i = 0; i < size; i++) { 2065 final ShortcutInfo newShortcut = newShortcuts.get(i); 2066 ps.addOrReplaceDynamicShortcut(newShortcut); 2067 } 2068 2069 // Lastly, adjust the ranks. 2070 ps.adjustRanks(); 2071 2072 changedShortcuts = prepareChangedShortcuts( 2073 cachedOrPinned, newShortcuts, removedShortcuts, ps); 2074 } 2075 2076 packageShortcutsChanged(ps, changedShortcuts, removedShortcuts); 2077 2078 verifyStates(); 2079 2080 return true; 2081 } 2082 2083 @Override 2084 public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, 2085 @UserIdInt int userId) { 2086 verifyCaller(packageName, userId); 2087 2088 final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( 2089 injectBinderCallingPid(), injectBinderCallingUid()); 2090 final List<ShortcutInfo> newShortcuts = 2091 (List<ShortcutInfo>) shortcutInfoList.getList(); 2092 verifyShortcutInfoPackages(packageName, newShortcuts); 2093 final int size = newShortcuts.size(); 2094 2095 final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1); 2096 final ShortcutPackage ps; 2097 2098 synchronized (mServiceLock) { 2099 throwIfUserLockedL(userId); 2100 2101 ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2102 2103 ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); 2104 ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); 2105 ps.ensureAllShortcutsVisibleToLauncher(newShortcuts); 2106 2107 // For update, don't fill in the default activity. Having null activity means 2108 // "don't update the activity" here. 2109 2110 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE); 2111 2112 // Throttling. 2113 if (!ps.tryApiCall(unlimited)) { 2114 return false; 2115 } 2116 2117 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 2118 ps.clearAllImplicitRanks(); 2119 assignImplicitRanks(newShortcuts); 2120 2121 for (int i = 0; i < size; i++) { 2122 final ShortcutInfo source = newShortcuts.get(i); 2123 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true); 2124 2125 ps.mutateShortcut(source.getId(), null, target -> { 2126 // Invisible shortcuts can't be updated. 2127 if (target == null || !target.isVisibleToPublisher()) { 2128 return; 2129 } 2130 2131 if (target.isEnabled() != source.isEnabled()) { 2132 Slog.w(TAG, "ShortcutInfo.enabled cannot be changed with" 2133 + " updateShortcuts()"); 2134 } 2135 2136 if (target.isLongLived() != source.isLongLived()) { 2137 Slog.w(TAG, 2138 "ShortcutInfo.longLived cannot be changed with" 2139 + " updateShortcuts()"); 2140 } 2141 2142 // When updating the rank, we need to insert between existing ranks, 2143 // so set this setRankChanged, and also copy the implicit rank fo 2144 // adjustRanks(). 2145 if (source.hasRank()) { 2146 target.setRankChanged(); 2147 target.setImplicitRank(source.getImplicitRank()); 2148 } 2149 2150 final boolean replacingIcon = (source.getIcon() != null); 2151 if (replacingIcon) { 2152 ps.removeIcon(target); 2153 } 2154 2155 // Note copyNonNullFieldsFrom() does the "updatable with?" check too. 2156 target.copyNonNullFieldsFrom(source); 2157 target.setTimestamp(injectCurrentTimeMillis()); 2158 2159 if (replacingIcon) { 2160 saveIconAndFixUpShortcutLocked(ps, target); 2161 } 2162 2163 // When we're updating any resource related fields, re-extract the res 2164 // names and the values. 2165 if (replacingIcon || source.hasStringResources()) { 2166 fixUpShortcutResourceNamesAndValues(target); 2167 } 2168 2169 changedShortcuts.add(target); 2170 }); 2171 } 2172 2173 // Lastly, adjust the ranks. 2174 ps.adjustRanks(); 2175 } 2176 packageShortcutsChanged(ps, changedShortcuts.isEmpty() ? null : changedShortcuts, null); 2177 2178 verifyStates(); 2179 2180 return true; 2181 } 2182 2183 @Override 2184 public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, 2185 @UserIdInt int userId) { 2186 verifyCaller(packageName, userId); 2187 2188 final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( 2189 injectBinderCallingPid(), injectBinderCallingUid()); 2190 final List<ShortcutInfo> newShortcuts = 2191 (List<ShortcutInfo>) shortcutInfoList.getList(); 2192 verifyShortcutInfoPackages(packageName, newShortcuts); 2193 final int size = newShortcuts.size(); 2194 2195 List<ShortcutInfo> changedShortcuts = null; 2196 final ShortcutPackage ps; 2197 2198 synchronized (mServiceLock) { 2199 throwIfUserLockedL(userId); 2200 2201 ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2202 2203 ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true); 2204 ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts); 2205 2206 fillInDefaultActivity(newShortcuts); 2207 2208 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD); 2209 2210 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 2211 ps.clearAllImplicitRanks(); 2212 assignImplicitRanks(newShortcuts); 2213 2214 // Throttling. 2215 if (!ps.tryApiCall(unlimited)) { 2216 return false; 2217 } 2218 for (int i = 0; i < size; i++) { 2219 final ShortcutInfo newShortcut = newShortcuts.get(i); 2220 2221 // Validate the shortcut. 2222 fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false); 2223 2224 // When ranks are changing, we need to insert between ranks, so set the 2225 // "rank changed" flag. 2226 newShortcut.setRankChanged(); 2227 2228 // Add it. 2229 ps.addOrReplaceDynamicShortcut(newShortcut); 2230 2231 if (changedShortcuts == null) { 2232 changedShortcuts = new ArrayList<>(1); 2233 } 2234 changedShortcuts.add(newShortcut); 2235 } 2236 2237 // Lastly, adjust the ranks. 2238 ps.adjustRanks(); 2239 } 2240 packageShortcutsChanged(ps, changedShortcuts, null); 2241 verifyStates(); 2242 return true; 2243 } 2244 2245 @Override 2246 public void pushDynamicShortcut(String packageName, ShortcutInfo shortcut, 2247 @UserIdInt int userId) { 2248 verifyCaller(packageName, userId); 2249 verifyShortcutInfoPackage(packageName, shortcut); 2250 2251 List<ShortcutInfo> changedShortcuts = new ArrayList<>(); 2252 List<ShortcutInfo> removedShortcuts = null; 2253 final ShortcutPackage ps; 2254 2255 synchronized (mServiceLock) { 2256 throwIfUserLockedL(userId); 2257 2258 ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2259 2260 ps.ensureNotImmutable(shortcut.getId(), /*ignoreInvisible=*/ true); 2261 fillInDefaultActivity(Arrays.asList(shortcut)); 2262 2263 if (!shortcut.hasRank()) { 2264 shortcut.setRank(0); 2265 } 2266 // Initialize the implicit ranks for ShortcutPackage.adjustRanks(). 2267 ps.clearAllImplicitRanks(); 2268 shortcut.setImplicitRank(0); 2269 2270 // Validate the shortcut. 2271 fixUpIncomingShortcutInfo(shortcut, /* forUpdate= */ false); 2272 2273 // When ranks are changing, we need to insert between ranks, so set the 2274 // "rank changed" flag. 2275 shortcut.setRankChanged(); 2276 2277 // Push it. 2278 boolean deleted = ps.pushDynamicShortcut(shortcut, changedShortcuts); 2279 2280 if (deleted) { 2281 if (changedShortcuts.isEmpty()) { 2282 return; // Failed to push. 2283 } 2284 removedShortcuts = Collections.singletonList(changedShortcuts.get(0)); 2285 changedShortcuts.clear(); 2286 } 2287 changedShortcuts.add(shortcut); 2288 2289 // Lastly, adjust the ranks. 2290 ps.adjustRanks(); 2291 } 2292 2293 packageShortcutsChanged(ps, changedShortcuts, removedShortcuts); 2294 2295 ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcut.getId()); 2296 2297 verifyStates(); 2298 } 2299 2300 @Override 2301 public void requestPinShortcut(String packageName, ShortcutInfo shortcut, 2302 IntentSender resultIntent, int userId, AndroidFuture<String> ret) { 2303 Objects.requireNonNull(shortcut); 2304 Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); 2305 Preconditions.checkArgument( 2306 !shortcut.isExcludedFromSurfaces(ShortcutInfo.SURFACE_LAUNCHER), 2307 "Shortcut excluded from launcher cannot be pinned"); 2308 ret.complete(String.valueOf(requestPinItem( 2309 packageName, userId, shortcut, null, null, resultIntent))); 2310 } 2311 2312 @Override 2313 public void createShortcutResultIntent(String packageName, ShortcutInfo shortcut, int userId, 2314 AndroidFuture<Intent> ret) throws RemoteException { 2315 Objects.requireNonNull(shortcut); 2316 Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled"); 2317 verifyCaller(packageName, userId); 2318 verifyShortcutInfoPackage(packageName, shortcut); 2319 final Intent intent; 2320 synchronized (mServiceLock) { 2321 throwIfUserLockedL(userId); 2322 // Send request to the launcher, if supported. 2323 intent = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, userId); 2324 } 2325 verifyStates(); 2326 ret.complete(intent); 2327 } 2328 2329 /** 2330 * Handles {@link #requestPinShortcut} and {@link ShortcutServiceInternal#requestPinAppWidget}. 2331 * After validating the caller, it passes the request to {@link #mShortcutRequestPinProcessor}. 2332 * Either {@param shortcut} or {@param appWidget} should be non-null. 2333 */ 2334 private boolean requestPinItem(String callingPackage, int userId, ShortcutInfo shortcut, 2335 AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent) { 2336 return requestPinItem(callingPackage, userId, shortcut, appWidget, extras, resultIntent, 2337 injectBinderCallingPid(), injectBinderCallingUid()); 2338 } 2339 2340 private boolean requestPinItem(String callingPackage, int userId, ShortcutInfo shortcut, 2341 AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent, 2342 int callingPid, int callingUid) { 2343 verifyCaller(callingPackage, userId); 2344 if (shortcut == null || !injectHasAccessShortcutsPermission( 2345 callingPid, callingUid)) { 2346 // Verify if caller is the shortcut owner, only if caller doesn't have ACCESS_SHORTCUTS. 2347 verifyShortcutInfoPackage(callingPackage, shortcut); 2348 } 2349 2350 final boolean ret; 2351 synchronized (mServiceLock) { 2352 throwIfUserLockedL(userId); 2353 2354 Preconditions.checkState(isUidForegroundLocked(callingUid), 2355 "Calling application must have a foreground activity or a foreground service"); 2356 2357 // If it's a pin shortcut request, and there's already a shortcut with the same ID 2358 // that's not visible to the caller (i.e. restore-blocked; meaning it's pinned by 2359 // someone already), then we just replace the existing one with this new one, 2360 // and then proceed the rest of the process. 2361 if (shortcut != null) { 2362 final String shortcutPackage = shortcut.getPackage(); 2363 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked( 2364 shortcutPackage, userId); 2365 final String id = shortcut.getId(); 2366 if (ps.isShortcutExistsAndInvisibleToPublisher(id)) { 2367 2368 ps.updateInvisibleShortcutForPinRequestWith(shortcut); 2369 2370 packageShortcutsChanged(ps, Collections.singletonList(shortcut), null); 2371 } 2372 } 2373 2374 // Send request to the launcher, if supported. 2375 ret = mShortcutRequestPinProcessor.requestPinItemLocked(shortcut, appWidget, extras, 2376 userId, resultIntent); 2377 } 2378 2379 verifyStates(); 2380 2381 return ret; 2382 } 2383 2384 @Override 2385 public void disableShortcuts(String packageName, List shortcutIds, 2386 CharSequence disabledMessage, int disabledMessageResId, @UserIdInt int userId) { 2387 verifyCaller(packageName, userId); 2388 Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); 2389 List<ShortcutInfo> changedShortcuts = null; 2390 List<ShortcutInfo> removedShortcuts = null; 2391 final ShortcutPackage ps; 2392 synchronized (mServiceLock) { 2393 throwIfUserLockedL(userId); 2394 ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2395 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, 2396 /*ignoreInvisible=*/ true); 2397 final String disabledMessageString = 2398 (disabledMessage == null) ? null : disabledMessage.toString(); 2399 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 2400 final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)); 2401 if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { 2402 continue; 2403 } 2404 final ShortcutInfo deleted = ps.disableWithId(id, 2405 disabledMessageString, disabledMessageResId, 2406 /* overrideImmutable=*/ false, /*ignoreInvisible=*/ true, 2407 ShortcutInfo.DISABLED_REASON_BY_APP); 2408 if (deleted == null) { 2409 if (changedShortcuts == null) { 2410 changedShortcuts = new ArrayList<>(1); 2411 } 2412 changedShortcuts.add(ps.findShortcutById(id)); 2413 } else { 2414 if (removedShortcuts == null) { 2415 removedShortcuts = new ArrayList<>(1); 2416 } 2417 removedShortcuts.add(deleted); 2418 } 2419 } 2420 // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks. 2421 ps.adjustRanks(); 2422 } 2423 packageShortcutsChanged(ps, changedShortcuts, removedShortcuts); 2424 verifyStates(); 2425 } 2426 2427 @Override 2428 public void enableShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) { 2429 verifyCaller(packageName, userId); 2430 Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); 2431 List<ShortcutInfo> changedShortcuts = null; 2432 final ShortcutPackage ps; 2433 synchronized (mServiceLock) { 2434 throwIfUserLockedL(userId); 2435 ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2436 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, 2437 /*ignoreInvisible=*/ true); 2438 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 2439 final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)); 2440 if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { 2441 continue; 2442 } 2443 ps.enableWithId(id); 2444 if (changedShortcuts == null) { 2445 changedShortcuts = new ArrayList<>(1); 2446 } 2447 changedShortcuts.add(ps.findShortcutById(id)); 2448 } 2449 } 2450 packageShortcutsChanged(ps, changedShortcuts, null); 2451 verifyStates(); 2452 } 2453 2454 2455 @Override 2456 public void removeDynamicShortcuts(String packageName, List<String> shortcutIds, 2457 @UserIdInt int userId) { 2458 verifyCaller(packageName, userId); 2459 Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); 2460 List<ShortcutInfo> changedShortcuts = null; 2461 List<ShortcutInfo> removedShortcuts = null; 2462 final ShortcutPackage ps; 2463 synchronized (mServiceLock) { 2464 throwIfUserLockedL(userId); 2465 ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2466 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, 2467 /*ignoreInvisible=*/ true); 2468 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 2469 final String id = Preconditions.checkStringNotEmpty( 2470 (String) shortcutIds.get(i)); 2471 if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { 2472 continue; 2473 } 2474 ShortcutInfo removed = ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true, 2475 /*wasPushedOut*/ false); 2476 if (removed == null) { 2477 if (changedShortcuts == null) { 2478 changedShortcuts = new ArrayList<>(1); 2479 } 2480 changedShortcuts.add(ps.findShortcutById(id)); 2481 } else { 2482 if (removedShortcuts == null) { 2483 removedShortcuts = new ArrayList<>(1); 2484 } 2485 removedShortcuts.add(removed); 2486 } 2487 } 2488 // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks. 2489 ps.adjustRanks(); 2490 } 2491 packageShortcutsChanged(ps, changedShortcuts, removedShortcuts); 2492 verifyStates(); 2493 } 2494 2495 @Override 2496 public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) { 2497 verifyCaller(packageName, userId); 2498 List<ShortcutInfo> changedShortcuts = new ArrayList<>(); 2499 List<ShortcutInfo> removedShortcuts = null; 2500 final ShortcutPackage ps; 2501 synchronized (mServiceLock) { 2502 throwIfUserLockedL(userId); 2503 ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2504 // Dynamic shortcuts that are either cached or pinned will not get deleted. 2505 ps.findAll(changedShortcuts, 2506 (ShortcutInfo si) -> si.isVisibleToPublisher() 2507 && si.isDynamic() && (si.isCached() || si.isPinned()), 2508 ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); 2509 removedShortcuts = ps.deleteAllDynamicShortcuts(); 2510 changedShortcuts = prepareChangedShortcuts( 2511 changedShortcuts, null, removedShortcuts, ps); 2512 } 2513 packageShortcutsChanged(ps, changedShortcuts, removedShortcuts); 2514 verifyStates(); 2515 } 2516 2517 @Override 2518 public void removeLongLivedShortcuts(String packageName, List shortcutIds, 2519 @UserIdInt int userId) { 2520 verifyCaller(packageName, userId); 2521 Objects.requireNonNull(shortcutIds, "shortcutIds must be provided"); 2522 List<ShortcutInfo> changedShortcuts = null; 2523 List<ShortcutInfo> removedShortcuts = null; 2524 final ShortcutPackage ps; 2525 synchronized (mServiceLock) { 2526 throwIfUserLockedL(userId); 2527 ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2528 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds, 2529 /*ignoreInvisible=*/ true); 2530 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 2531 final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)); 2532 if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { 2533 continue; 2534 } 2535 ShortcutInfo removed = ps.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true); 2536 if (removed != null) { 2537 if (removedShortcuts == null) { 2538 removedShortcuts = new ArrayList<>(1); 2539 } 2540 removedShortcuts.add(removed); 2541 } else { 2542 if (changedShortcuts == null) { 2543 changedShortcuts = new ArrayList<>(1); 2544 } 2545 changedShortcuts.add(ps.findShortcutById(id)); 2546 } 2547 } 2548 // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks. 2549 ps.adjustRanks(); 2550 } 2551 packageShortcutsChanged(ps, changedShortcuts, removedShortcuts); 2552 verifyStates(); 2553 } 2554 2555 @Override 2556 public ParceledListSlice<ShortcutInfo> getShortcuts(String packageName, 2557 @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId) { 2558 verifyCaller(packageName, userId); 2559 synchronized (mServiceLock) { 2560 throwIfUserLockedL(userId); 2561 final boolean matchDynamic = (matchFlags & ShortcutManager.FLAG_MATCH_DYNAMIC) != 0; 2562 final boolean matchPinned = (matchFlags & ShortcutManager.FLAG_MATCH_PINNED) != 0; 2563 final boolean matchManifest = (matchFlags & ShortcutManager.FLAG_MATCH_MANIFEST) != 0; 2564 final boolean matchCached = (matchFlags & ShortcutManager.FLAG_MATCH_CACHED) != 0; 2565 final int shortcutFlags = (matchDynamic ? ShortcutInfo.FLAG_DYNAMIC : 0) 2566 | (matchPinned ? ShortcutInfo.FLAG_PINNED : 0) 2567 | (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0) 2568 | (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0); 2569 return getShortcutsWithQueryLocked( 2570 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, 2571 (ShortcutInfo si) -> 2572 si.isVisibleToPublisher() 2573 && (si.getFlags() & shortcutFlags) != 0); 2574 } 2575 } 2576 2577 @Override 2578 public ParceledListSlice getShareTargets(String packageName, 2579 IntentFilter filter, @UserIdInt int userId) { 2580 Preconditions.checkStringNotEmpty(packageName, "packageName"); 2581 Objects.requireNonNull(filter, "intentFilter"); 2582 if (!isCallerChooserActivity()) { 2583 verifyCaller(packageName, userId); 2584 } 2585 enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS, 2586 "getShareTargets"); 2587 final ComponentName chooser = injectChooserActivity(); 2588 final String pkg = chooser != null ? chooser.getPackageName() : mContext.getPackageName(); 2589 synchronized (mServiceLock) { 2590 throwIfUserLockedL(userId); 2591 final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = new ArrayList<>(); 2592 final ShortcutUser user = getUserShortcutsLocked(userId); 2593 user.forAllPackages(p -> shortcutInfoList.addAll( 2594 p.getMatchingShareTargets(filter, pkg))); 2595 return new ParceledListSlice<>(shortcutInfoList); 2596 } 2597 } 2598 2599 @Override 2600 public boolean hasShareTargets(String packageName, String packageToCheck, 2601 @UserIdInt int userId) { 2602 verifyCaller(packageName, userId); 2603 enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS, 2604 "hasShareTargets"); 2605 2606 synchronized (mServiceLock) { 2607 throwIfUserLockedL(userId); 2608 2609 return getPackageShortcutsLocked(packageToCheck, userId).hasShareTargets(); 2610 } 2611 } 2612 2613 public boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage, 2614 @NonNull String packageName, @NonNull String shortcutId, int userId, 2615 @NonNull IntentFilter filter) { 2616 verifyCaller(callingPackage, callingUserId); 2617 enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS, 2618 "isSharingShortcut"); 2619 2620 synchronized (mServiceLock) { 2621 throwIfUserLockedL(userId); 2622 throwIfUserLockedL(callingUserId); 2623 2624 final List<ShortcutManager.ShareShortcutInfo> matchedTargets = 2625 getPackageShortcutsLocked(packageName, userId) 2626 .getMatchingShareTargets(filter); 2627 final int matchedSize = matchedTargets.size(); 2628 for (int i = 0; i < matchedSize; i++) { 2629 if (matchedTargets.get(i).getShortcutInfo().getId().equals(shortcutId)) { 2630 return true; 2631 } 2632 } 2633 } 2634 return false; 2635 } 2636 2637 @GuardedBy("mServiceLock") 2638 private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName, 2639 @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> filter) { 2640 2641 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 2642 2643 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2644 ps.findAll(ret, filter, cloneFlags); 2645 return new ParceledListSlice<>(setReturnedByServer(ret)); 2646 } 2647 2648 @Override 2649 public int getMaxShortcutCountPerActivity(String packageName, @UserIdInt int userId) 2650 throws RemoteException { 2651 verifyCaller(packageName, userId); 2652 2653 return mMaxShortcuts; 2654 } 2655 2656 @Override 2657 public int getRemainingCallCount(String packageName, @UserIdInt int userId) { 2658 verifyCaller(packageName, userId); 2659 2660 final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission( 2661 injectBinderCallingPid(), injectBinderCallingUid()); 2662 2663 synchronized (mServiceLock) { 2664 throwIfUserLockedL(userId); 2665 2666 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2667 return mMaxUpdatesPerInterval - ps.getApiCallCount(unlimited); 2668 } 2669 } 2670 2671 @Override 2672 public long getRateLimitResetTime(String packageName, @UserIdInt int userId) { 2673 verifyCaller(packageName, userId); 2674 2675 synchronized (mServiceLock) { 2676 throwIfUserLockedL(userId); 2677 2678 return getNextResetTimeLocked(); 2679 } 2680 } 2681 2682 @Override 2683 public int getIconMaxDimensions(String packageName, int userId) { 2684 verifyCaller(packageName, userId); 2685 2686 synchronized (mServiceLock) { 2687 return mMaxIconDimension; 2688 } 2689 } 2690 2691 @Override 2692 public void reportShortcutUsed(String packageName, String shortcutId, int userId) { 2693 verifyCaller(packageName, userId); 2694 Objects.requireNonNull(shortcutId); 2695 if (DEBUG) { 2696 Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d", 2697 shortcutId, packageName, userId)); 2698 } 2699 final ShortcutPackage ps; 2700 synchronized (mServiceLock) { 2701 throwIfUserLockedL(userId); 2702 ps = getPackageShortcutsForPublisherLocked(packageName, userId); 2703 if (ps.findShortcutById(shortcutId) == null) { 2704 Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s", 2705 packageName, shortcutId)); 2706 return; 2707 } 2708 } 2709 ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcutId); 2710 } 2711 2712 @Override 2713 public boolean isRequestPinItemSupported(int callingUserId, int requestType) { 2714 verifyCallerUserId(callingUserId); 2715 2716 final long token = injectClearCallingIdentity(); 2717 try { 2718 return mShortcutRequestPinProcessor 2719 .isRequestPinItemSupported(callingUserId, requestType); 2720 } finally { 2721 injectRestoreCallingIdentity(token); 2722 } 2723 } 2724 2725 /** 2726 * Reset all throttling, for developer options and command line. Only system/shell can call 2727 * it. 2728 */ 2729 @Override 2730 public void resetThrottling() { 2731 enforceSystemOrShell(); 2732 2733 resetThrottlingInner(getCallingUserId()); 2734 } 2735 2736 void resetThrottlingInner(@UserIdInt int userId) { 2737 synchronized (mServiceLock) { 2738 if (!isUserUnlockedL(userId)) { 2739 Log.w(TAG, "User " + userId + " is locked or not running"); 2740 return; 2741 } 2742 2743 getUserShortcutsLocked(userId).resetThrottling(); 2744 } 2745 scheduleSaveUser(userId); 2746 Slog.i(TAG, "ShortcutManager: throttling counter reset for user " + userId); 2747 } 2748 2749 void resetAllThrottlingInner() { 2750 mRawLastResetTime.set(injectCurrentTimeMillis()); 2751 scheduleSaveBaseState(); 2752 Slog.i(TAG, "ShortcutManager: throttling counter reset for all users"); 2753 } 2754 2755 @Override 2756 public void onApplicationActive(String packageName, int userId) { 2757 if (DEBUG) { 2758 Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId); 2759 } 2760 enforceResetThrottlingPermission(); 2761 synchronized (mServiceLock) { 2762 if (!isUserUnlockedL(userId)) { 2763 // This is called by system UI, so no need to throw. Just ignore. 2764 return; 2765 } 2766 getPackageShortcutsLocked(packageName, userId) 2767 .resetRateLimitingForCommandLineNoSaving(); 2768 } 2769 saveUser(userId); 2770 } 2771 2772 // We override this method in unit tests to do a simpler check. 2773 boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId, 2774 int callingPid, int callingUid) { 2775 if (canSeeAnyPinnedShortcut(callingPackage, userId, callingPid, callingUid)) { 2776 return true; 2777 } 2778 final long start = getStatStartTime(); 2779 try { 2780 return hasShortcutHostPermissionInner(callingPackage, userId); 2781 } finally { 2782 logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start); 2783 } 2784 } 2785 2786 boolean canSeeAnyPinnedShortcut(@NonNull String callingPackage, int userId, 2787 int callingPid, int callingUid) { 2788 if (injectHasAccessShortcutsPermission(callingPid, callingUid)) { 2789 return true; 2790 } 2791 synchronized (mNonPersistentUsersLock) { 2792 return getNonPersistentUserLocked(userId).hasHostPackage(callingPackage); 2793 } 2794 } 2795 2796 /** 2797 * Returns true if the caller has the "ACCESS_SHORTCUTS" permission. 2798 */ 2799 @VisibleForTesting 2800 boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) { 2801 return mContext.checkPermission(android.Manifest.permission.ACCESS_SHORTCUTS, 2802 callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; 2803 } 2804 2805 /** 2806 * Returns true if the caller has the "UNLIMITED_SHORTCUTS_API_CALLS" permission. 2807 */ 2808 @VisibleForTesting 2809 boolean injectHasUnlimitedShortcutsApiCallsPermission(int callingPid, int callingUid) { 2810 return mContext.checkPermission(permission.UNLIMITED_SHORTCUTS_API_CALLS, 2811 callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; 2812 } 2813 2814 // This method is extracted so we can directly call this method from unit tests, 2815 // even when hasShortcutPermission() is overridden. 2816 @VisibleForTesting 2817 boolean hasShortcutHostPermissionInner(@NonNull String packageName, int userId) { 2818 synchronized (mServiceLock) { 2819 throwIfUserLockedL(userId); 2820 2821 final String defaultLauncher = getDefaultLauncher(userId); 2822 2823 if (defaultLauncher != null) { 2824 if (DEBUG) { 2825 Slog.v(TAG, "Detected launcher: " + defaultLauncher + " user: " + userId); 2826 } 2827 return defaultLauncher.equals(packageName); 2828 } else { 2829 return false; 2830 } 2831 } 2832 } 2833 2834 @VisibleForTesting 2835 boolean areShortcutsSupportedOnHomeScreen(@UserIdInt int userId) { 2836 if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome() 2837 || !android.multiuser.Flags.enablePrivateSpaceFeatures()) { 2838 return true; 2839 } 2840 final long start = getStatStartTime(); 2841 final long token = injectClearCallingIdentity(); 2842 boolean isSupported; 2843 try { 2844 synchronized (mServiceLock) { 2845 isSupported = !mUserManagerInternal.getUserProperties(userId) 2846 .areItemsRestrictedOnHomeScreen(); 2847 } 2848 } finally { 2849 injectRestoreCallingIdentity(token); 2850 logDurationStat(Stats.GET_DEFAULT_LAUNCHER, start); 2851 } 2852 return isSupported; 2853 } 2854 2855 @Nullable 2856 String getDefaultLauncher(@UserIdInt int userId) { 2857 final long start = getStatStartTime(); 2858 final long token = injectClearCallingIdentity(); 2859 try { 2860 synchronized (mServiceLock) { 2861 throwIfUserLockedL(userId); 2862 2863 final ShortcutUser user = getUserShortcutsLocked(userId); 2864 String cachedLauncher = user.getCachedLauncher(); 2865 if (cachedLauncher != null) { 2866 return cachedLauncher; 2867 } 2868 2869 // Default launcher from role manager. 2870 final long startGetHomeRoleHoldersAsUser = getStatStartTime(); 2871 final String defaultLauncher = injectGetHomeRoleHolderAsUser( 2872 getParentOrSelfUserId(userId)); 2873 logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeRoleHoldersAsUser); 2874 2875 if (defaultLauncher != null) { 2876 if (DEBUG) { 2877 Slog.v(TAG, "Default launcher from RoleManager: " + defaultLauncher 2878 + " user: " + userId); 2879 } 2880 user.setCachedLauncher(defaultLauncher); 2881 } else { 2882 Slog.e(TAG, "Default launcher not found." + " user: " + userId); 2883 } 2884 2885 return defaultLauncher; 2886 } 2887 } finally { 2888 injectRestoreCallingIdentity(token); 2889 logDurationStat(Stats.GET_DEFAULT_LAUNCHER, start); 2890 } 2891 } 2892 2893 public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName, 2894 int userId) { 2895 synchronized (mNonPersistentUsersLock) { 2896 getNonPersistentUserLocked(userId).setShortcutHostPackage(type, packageName); 2897 } 2898 } 2899 2900 // === House keeping === 2901 2902 private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId, 2903 boolean appStillExists) { 2904 synchronized (mServiceLock) { 2905 forEachLoadedUserLocked(user -> 2906 cleanUpPackageLocked(packageName, user.getUserId(), packageUserId, 2907 appStillExists)); 2908 } 2909 } 2910 2911 /** 2912 * Remove all the information associated with a package. This will really remove all the 2913 * information, including the restore information (i.e. it'll remove packages even if they're 2914 * shadow). 2915 * 2916 * This is called when an app is uninstalled, or an app gets "clear data"ed. 2917 */ 2918 @GuardedBy("mServiceLock") 2919 @VisibleForTesting 2920 void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId, 2921 boolean appStillExists) { 2922 final boolean wasUserLoaded = isUserLoadedLocked(owningUserId); 2923 2924 final ShortcutUser user = getUserShortcutsLocked(owningUserId); 2925 boolean doNotify = false; 2926 // First, remove the package from the package list (if the package is a publisher). 2927 final ShortcutPackage sp = (packageUserId == owningUserId) 2928 ? user.removePackage(packageName) : null; 2929 if (sp != null) { 2930 doNotify = true; 2931 } 2932 2933 // Also remove from the launcher list (if the package is a launcher). 2934 user.removeLauncher(packageUserId, packageName); 2935 2936 // Then remove pinned shortcuts from all launchers. 2937 user.forAllLaunchers(l -> l.cleanUpPackage(packageName, packageUserId)); 2938 2939 // Now there may be orphan shortcuts because we removed pinned shortcuts at the previous 2940 // step. Remove them too. 2941 user.forAllPackages(p -> p.refreshPinnedFlags()); 2942 2943 if (doNotify) { 2944 notifyListeners(packageName, owningUserId); 2945 } 2946 2947 // If the app still exists (i.e. data cleared), we need to re-publish manifest shortcuts. 2948 if (appStillExists && (packageUserId == owningUserId)) { 2949 // This will do the notification and save when needed, so do it after the above 2950 // notifyListeners. 2951 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 2952 } 2953 if (!appStillExists && (packageUserId == owningUserId) && sp != null) { 2954 // If the app is removed altogether, we can get rid of the xml as well 2955 injectPostToHandler(() -> sp.removeShortcutPackageItem()); 2956 } 2957 2958 if (!wasUserLoaded) { 2959 // Note this will execute the scheduled save. 2960 unloadUserLocked(owningUserId); 2961 } 2962 } 2963 2964 /** 2965 * Entry point from {@link LauncherApps}. 2966 */ 2967 private class LocalService extends ShortcutServiceInternal { 2968 2969 @Override 2970 public List<ShortcutInfo> getShortcuts(int launcherUserId, 2971 @NonNull String callingPackage, long changedSince, 2972 @Nullable String packageName, @Nullable List<String> shortcutIds, 2973 @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName, 2974 int queryFlags, int userId, int callingPid, int callingUid) { 2975 if (DEBUG_REBOOT) { 2976 Slog.d(TAG, "Getting shortcuts for launcher= " + callingPackage 2977 + "user=" + userId + " pkg=" + packageName); 2978 } 2979 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 2980 2981 int flags = ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER; 2982 if ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0) { 2983 flags = ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO; 2984 } else if ((queryFlags & ShortcutQuery.FLAG_GET_PERSONS_DATA) != 0) { 2985 flags &= ~ShortcutInfo.CLONE_REMOVE_PERSON; 2986 } 2987 final int cloneFlag = flags; 2988 2989 if (packageName == null) { 2990 shortcutIds = null; // LauncherAppsService already threw for it though. 2991 } 2992 2993 synchronized (mServiceLock) { 2994 throwIfUserLockedL(userId); 2995 throwIfUserLockedL(launcherUserId); 2996 2997 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 2998 .attemptToRestoreIfNeededAndSave(); 2999 3000 if (packageName != null) { 3001 getShortcutsInnerLocked(launcherUserId, 3002 callingPackage, packageName, shortcutIds, locusIds, changedSince, 3003 componentName, queryFlags, userId, ret, cloneFlag, 3004 callingPid, callingUid); 3005 } else { 3006 final List<String> shortcutIdsF = shortcutIds; 3007 final List<LocusId> locusIdsF = locusIds; 3008 getUserShortcutsLocked(userId).forAllPackages(p -> { 3009 getShortcutsInnerLocked(launcherUserId, 3010 callingPackage, p.getPackageName(), shortcutIdsF, locusIdsF, 3011 changedSince, componentName, queryFlags, userId, ret, cloneFlag, 3012 callingPid, callingUid); 3013 }); 3014 } 3015 } 3016 return setReturnedByServer(ret); 3017 } 3018 3019 @GuardedBy("ShortcutService.this.mServiceLock") 3020 private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage, 3021 @Nullable String packageName, @Nullable List<String> shortcutIds, 3022 @Nullable List<LocusId> locusIds, long changedSince, 3023 @Nullable ComponentName componentName, int queryFlags, 3024 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag, 3025 int callingPid, int callingUid) { 3026 final ArraySet<String> ids = shortcutIds == null ? null 3027 : new ArraySet<>(shortcutIds); 3028 3029 final ShortcutUser user = getUserShortcutsLocked(userId); 3030 final ShortcutPackage p = user.getPackageShortcutsIfExists(packageName); 3031 if (p == null) { 3032 if (DEBUG_REBOOT) { 3033 Log.d(TAG, "getShortcutsInnerLocked() returned empty results because " 3034 + packageName + " isn't loaded"); 3035 } 3036 return; // No need to instantiate ShortcutPackage. 3037 } 3038 3039 final boolean canAccessAllShortcuts = 3040 canSeeAnyPinnedShortcut(callingPackage, launcherUserId, callingPid, callingUid); 3041 3042 final boolean getPinnedByAnyLauncher = 3043 canAccessAllShortcuts && 3044 ((queryFlags & ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER) != 0); 3045 queryFlags |= (getPinnedByAnyLauncher ? ShortcutQuery.FLAG_MATCH_PINNED : 0); 3046 3047 final Predicate<ShortcutInfo> filter = getFilterFromQuery(ids, locusIds, changedSince, 3048 componentName, queryFlags, getPinnedByAnyLauncher); 3049 p.findAll(ret, filter, cloneFlag, callingPackage, launcherUserId, 3050 getPinnedByAnyLauncher); 3051 } 3052 3053 private Predicate<ShortcutInfo> getFilterFromQuery(@Nullable ArraySet<String> ids, 3054 @Nullable List<LocusId> locusIds, long changedSince, 3055 @Nullable ComponentName componentName, int queryFlags, 3056 boolean getPinnedByAnyLauncher) { 3057 final ArraySet<LocusId> locIds = locusIds == null ? null 3058 : new ArraySet<>(locusIds); 3059 3060 final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0; 3061 final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0; 3062 final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0; 3063 final boolean matchCached = (queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) != 0; 3064 return si -> { 3065 if (si.getLastChangedTimestamp() < changedSince) { 3066 return false; 3067 } 3068 if (ids != null && !ids.contains(si.getId())) { 3069 return false; 3070 } 3071 if (locIds != null && !locIds.contains(si.getLocusId())) { 3072 return false; 3073 } 3074 if (componentName != null) { 3075 if (si.getActivity() != null 3076 && !si.getActivity().equals(componentName)) { 3077 return false; 3078 } 3079 } 3080 if (matchDynamic && si.isDynamic()) { 3081 return true; 3082 } 3083 if ((matchPinned || getPinnedByAnyLauncher) && si.isPinned()) { 3084 return true; 3085 } 3086 if (matchManifest && si.isDeclaredInManifest()) { 3087 return true; 3088 } 3089 if (matchCached && si.isCached()) { 3090 return true; 3091 } 3092 return false; 3093 }; 3094 } 3095 3096 @Override 3097 public void getShortcutsAsync(int launcherUserId, 3098 @NonNull String callingPackage, long changedSince, 3099 @Nullable String packageName, @Nullable List<String> shortcutIds, 3100 @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName, 3101 int queryFlags, int userId, int callingPid, int callingUid, 3102 @NonNull AndroidFuture<List<ShortcutInfo>> cb) { 3103 final List<ShortcutInfo> ret = getShortcuts(launcherUserId, callingPackage, 3104 changedSince, packageName, shortcutIds, locusIds, componentName, queryFlags, 3105 userId, callingPid, callingUid); 3106 if (shortcutIds == null || packageName == null || ret.size() >= shortcutIds.size()) { 3107 // skip persistence layer if not querying by id in a specific package or all 3108 // shortcuts have already been found. 3109 cb.complete(ret); 3110 return; 3111 } 3112 final ShortcutPackage p; 3113 synchronized (mServiceLock) { 3114 p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName); 3115 } 3116 if (p == null) { 3117 cb.complete(ret); 3118 return; // Bail-out directly if package doesn't exist. 3119 } 3120 // fetch remaining shortcuts from persistence layer 3121 final ArraySet<String> ids = new ArraySet<>(shortcutIds); 3122 // remove the ids that are already fetched 3123 ret.stream().map(ShortcutInfo::getId).collect(Collectors.toList()).forEach(ids::remove); 3124 3125 int flags = ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER; 3126 if ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0) { 3127 flags = ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO; 3128 } else if ((queryFlags & ShortcutQuery.FLAG_GET_PERSONS_DATA) != 0) { 3129 flags &= ~ShortcutInfo.CLONE_REMOVE_PERSON; 3130 } 3131 final int cloneFlag = flags; 3132 3133 p.getShortcutByIdsAsync(ids, shortcuts -> { 3134 if (shortcuts != null) { 3135 shortcuts.stream().map(si -> si.clone(cloneFlag)).forEach(ret::add); 3136 } 3137 cb.complete(ret); 3138 }); 3139 } 3140 3141 @Override 3142 public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, 3143 @NonNull String packageName, @NonNull String shortcutId, int userId) { 3144 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3145 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 3146 3147 synchronized (mServiceLock) { 3148 throwIfUserLockedL(userId); 3149 throwIfUserLockedL(launcherUserId); 3150 3151 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3152 .attemptToRestoreIfNeededAndSave(); 3153 3154 final ShortcutInfo si = getShortcutInfoLocked( 3155 launcherUserId, callingPackage, packageName, shortcutId, userId, 3156 /*getPinnedByAnyLauncher=*/ false); 3157 return si != null && si.isPinned(); 3158 } 3159 } 3160 3161 @GuardedBy("ShortcutService.this.mServiceLock") 3162 private ShortcutInfo getShortcutInfoLocked( 3163 int launcherUserId, @NonNull String callingPackage, 3164 @NonNull String packageName, @NonNull String shortcutId, int userId, 3165 boolean getPinnedByAnyLauncher) { 3166 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3167 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 3168 3169 throwIfUserLockedL(userId); 3170 throwIfUserLockedL(launcherUserId); 3171 3172 final ShortcutPackage p = getUserShortcutsLocked(userId) 3173 .getPackageShortcutsIfExists(packageName); 3174 if (p == null) { 3175 return null; 3176 } 3177 3178 final ArrayList<ShortcutInfo> list = new ArrayList<>(1); 3179 p.findAll(list, (ShortcutInfo si) -> shortcutId.equals(si.getId()), 3180 /* clone flags=*/ 0, callingPackage, launcherUserId, getPinnedByAnyLauncher); 3181 return list.size() == 0 ? null : list.get(0); 3182 } 3183 3184 private void getShortcutInfoAsync( 3185 int launcherUserId, @NonNull String packageName, @NonNull String shortcutId, 3186 int userId, @NonNull Consumer<ShortcutInfo> cb) { 3187 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3188 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 3189 3190 throwIfUserLockedL(userId); 3191 throwIfUserLockedL(launcherUserId); 3192 3193 final ShortcutPackage p; 3194 synchronized (mServiceLock) { 3195 p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName); 3196 } 3197 if (p == null) { 3198 cb.accept(null); 3199 return; 3200 } 3201 p.getShortcutByIdsAsync(Collections.singleton(shortcutId), shortcuts -> 3202 cb.accept(shortcuts == null || shortcuts.isEmpty() ? null : shortcuts.get(0))); 3203 } 3204 3205 @Override 3206 public void pinShortcuts(int launcherUserId, 3207 @NonNull String callingPackage, @NonNull String packageName, 3208 @NonNull List<String> shortcutIds, int userId) { 3209 // Calling permission must be checked by LauncherAppsImpl. 3210 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3211 Objects.requireNonNull(shortcutIds, "shortcutIds"); 3212 3213 List<ShortcutInfo> changedShortcuts = null; 3214 List<ShortcutInfo> removedShortcuts = null; 3215 final ShortcutPackage sp; 3216 synchronized (mServiceLock) { 3217 throwIfUserLockedL(userId); 3218 throwIfUserLockedL(launcherUserId); 3219 3220 final ShortcutLauncher launcher = 3221 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId); 3222 launcher.attemptToRestoreIfNeededAndSave(); 3223 3224 sp = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName); 3225 if (sp != null) { 3226 // List the shortcuts that are pinned only, these will get removed. 3227 removedShortcuts = new ArrayList<>(); 3228 sp.findAll(removedShortcuts, (ShortcutInfo si) -> si.isVisibleToPublisher() 3229 && si.isPinned() && !si.isCached() && !si.isDynamic() 3230 && !si.isDeclaredInManifest(), 3231 ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO, 3232 callingPackage, launcherUserId, false); 3233 } 3234 // Get list of shortcuts that will get unpinned. 3235 ArraySet<String> oldPinnedIds = launcher.getPinnedShortcutIds(packageName, userId); 3236 3237 launcher.pinShortcuts(userId, packageName, shortcutIds, /*forPinRequest=*/ false); 3238 3239 if (oldPinnedIds != null && removedShortcuts != null) { 3240 for (int i = 0; i < removedShortcuts.size(); i++) { 3241 oldPinnedIds.remove(removedShortcuts.get(i).getId()); 3242 } 3243 } 3244 changedShortcuts = prepareChangedShortcuts( 3245 oldPinnedIds, new ArraySet<>(shortcutIds), removedShortcuts, sp); 3246 } 3247 3248 if (sp != null) { 3249 packageShortcutsChanged(sp, changedShortcuts, removedShortcuts); 3250 } 3251 3252 verifyStates(); 3253 } 3254 3255 @Override 3256 public void cacheShortcuts(int launcherUserId, 3257 @NonNull String callingPackage, @NonNull String packageName, 3258 @NonNull List<String> shortcutIds, int userId, int cacheFlags) { 3259 updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds, 3260 userId, cacheFlags, /* doCache= */ true); 3261 } 3262 3263 @Override 3264 public void uncacheShortcuts(int launcherUserId, 3265 @NonNull String callingPackage, @NonNull String packageName, 3266 @NonNull List<String> shortcutIds, int userId, int cacheFlags) { 3267 updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds, 3268 userId, cacheFlags, /* doCache= */ false); 3269 } 3270 3271 @Override 3272 public List<ShortcutManager.ShareShortcutInfo> getShareTargets( 3273 @NonNull String callingPackage, @NonNull IntentFilter intentFilter, int userId) { 3274 return ShortcutService.this.getShareTargets( 3275 callingPackage, intentFilter, userId).getList(); 3276 } 3277 3278 @Override 3279 public boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage, 3280 @NonNull String packageName, @NonNull String shortcutId, int userId, 3281 @NonNull IntentFilter filter) { 3282 Preconditions.checkStringNotEmpty(callingPackage, "callingPackage"); 3283 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3284 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 3285 3286 return ShortcutService.this.isSharingShortcut(callingUserId, callingPackage, 3287 packageName, shortcutId, userId, filter); 3288 } 3289 3290 private void updateCachedShortcutsInternal(int launcherUserId, 3291 @NonNull String callingPackage, @NonNull String packageName, 3292 @NonNull List<String> shortcutIds, int userId, int cacheFlags, boolean doCache) { 3293 // Calling permission must be checked by LauncherAppsImpl. 3294 Preconditions.checkStringNotEmpty(packageName, "packageName"); 3295 Objects.requireNonNull(shortcutIds, "shortcutIds"); 3296 Preconditions.checkState( 3297 (cacheFlags & ShortcutInfo.FLAG_CACHED_ALL) != 0, "invalid cacheFlags"); 3298 3299 List<ShortcutInfo> changedShortcuts = null; 3300 List<ShortcutInfo> removedShortcuts = null; 3301 final ShortcutPackage sp; 3302 synchronized (mServiceLock) { 3303 throwIfUserLockedL(userId); 3304 throwIfUserLockedL(launcherUserId); 3305 3306 final int idSize = shortcutIds.size(); 3307 sp = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName); 3308 if (idSize == 0 || sp == null) { 3309 return; 3310 } 3311 3312 for (int i = 0; i < idSize; i++) { 3313 final String id = Preconditions.checkStringNotEmpty(shortcutIds.get(i)); 3314 final ShortcutInfo si = sp.findShortcutById(id); 3315 if (si == null || doCache == si.hasFlags(cacheFlags)) { 3316 continue; 3317 } 3318 3319 if (doCache) { 3320 if (si.isLongLived()) { 3321 si.addFlags(cacheFlags); 3322 if (changedShortcuts == null) { 3323 changedShortcuts = new ArrayList<>(1); 3324 } 3325 changedShortcuts.add(si); 3326 } else { 3327 Log.w(TAG, "Only long lived shortcuts can get cached. Ignoring id " 3328 + si.getId()); 3329 } 3330 } else { 3331 ShortcutInfo removed = null; 3332 si.clearFlags(cacheFlags); 3333 if (!si.isDynamic() && !si.isCached()) { 3334 removed = sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true); 3335 } 3336 if (removed == null) { 3337 if (changedShortcuts == null) { 3338 changedShortcuts = new ArrayList<>(1); 3339 } 3340 changedShortcuts.add(si); 3341 } else { 3342 if (removedShortcuts == null) { 3343 removedShortcuts = new ArrayList<>(1); 3344 } 3345 removedShortcuts.add(removed); 3346 } 3347 } 3348 } 3349 } 3350 packageShortcutsChanged(sp, changedShortcuts, removedShortcuts); 3351 3352 verifyStates(); 3353 } 3354 3355 @Override 3356 public Intent[] createShortcutIntents(int launcherUserId, 3357 @NonNull String callingPackage, 3358 @NonNull String packageName, @NonNull String shortcutId, int userId, 3359 int callingPid, int callingUid) { 3360 // Calling permission must be checked by LauncherAppsImpl. 3361 Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty"); 3362 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty"); 3363 3364 synchronized (mServiceLock) { 3365 throwIfUserLockedL(userId); 3366 throwIfUserLockedL(launcherUserId); 3367 3368 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3369 .attemptToRestoreIfNeededAndSave(); 3370 3371 final boolean getPinnedByAnyLauncher = 3372 canSeeAnyPinnedShortcut(callingPackage, launcherUserId, 3373 callingPid, callingUid); 3374 3375 // Make sure the shortcut is actually visible to the launcher. 3376 final ShortcutInfo si = getShortcutInfoLocked( 3377 launcherUserId, callingPackage, packageName, shortcutId, userId, 3378 getPinnedByAnyLauncher); 3379 // "si == null" should suffice here, but check the flags too just to make sure. 3380 if (si == null || !si.isEnabled() || !(si.isAlive() || getPinnedByAnyLauncher)) { 3381 Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled"); 3382 return null; 3383 } 3384 return si.getIntents(); 3385 } 3386 } 3387 3388 @Override 3389 public void createShortcutIntentsAsync(int launcherUserId, 3390 @NonNull String callingPackage, @NonNull String packageName, 3391 @NonNull String shortcutId, int userId, int callingPid, 3392 int callingUid, @NonNull AndroidFuture<Intent[]> cb) { 3393 // Calling permission must be checked by LauncherAppsImpl. 3394 Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty"); 3395 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty"); 3396 3397 // Check in memory shortcut first 3398 synchronized (mServiceLock) { 3399 throwIfUserLockedL(userId); 3400 throwIfUserLockedL(launcherUserId); 3401 3402 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3403 .attemptToRestoreIfNeededAndSave(); 3404 3405 final boolean getPinnedByAnyLauncher = 3406 canSeeAnyPinnedShortcut(callingPackage, launcherUserId, 3407 callingPid, callingUid); 3408 3409 // Make sure the shortcut is actually visible to the launcher. 3410 final ShortcutInfo si = getShortcutInfoLocked( 3411 launcherUserId, callingPackage, packageName, shortcutId, userId, 3412 getPinnedByAnyLauncher); 3413 if (si != null) { 3414 if (!si.isEnabled() || !(si.isAlive() || getPinnedByAnyLauncher)) { 3415 Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled"); 3416 cb.complete(null); 3417 return; 3418 } 3419 cb.complete(si.getIntents()); 3420 return; 3421 } 3422 } 3423 3424 // Otherwise check persisted shortcuts 3425 getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> { 3426 cb.complete(si == null ? null : si.getIntents()); 3427 }); 3428 } 3429 3430 @Override 3431 public void addListener(@NonNull ShortcutChangeListener listener) { 3432 synchronized (mServiceLock) { 3433 mListeners.add(Objects.requireNonNull(listener)); 3434 } 3435 } 3436 3437 @Override 3438 public void addShortcutChangeCallback( 3439 @NonNull LauncherApps.ShortcutChangeCallback callback) { 3440 synchronized (mServiceLock) { 3441 mShortcutChangeCallbacks.add(Objects.requireNonNull(callback)); 3442 } 3443 } 3444 3445 @Override 3446 public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage, 3447 @NonNull String packageName, @NonNull String shortcutId, int userId) { 3448 Objects.requireNonNull(callingPackage, "callingPackage"); 3449 Objects.requireNonNull(packageName, "packageName"); 3450 Objects.requireNonNull(shortcutId, "shortcutId"); 3451 3452 synchronized (mServiceLock) { 3453 throwIfUserLockedL(userId); 3454 throwIfUserLockedL(launcherUserId); 3455 3456 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3457 .attemptToRestoreIfNeededAndSave(); 3458 3459 final ShortcutPackage p = getUserShortcutsLocked(userId) 3460 .getPackageShortcutsIfExists(packageName); 3461 if (p == null) { 3462 return 0; 3463 } 3464 3465 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 3466 return (shortcutInfo != null && shortcutInfo.hasIconResource()) 3467 ? shortcutInfo.getIconResourceId() : 0; 3468 } 3469 } 3470 3471 @Override 3472 @Nullable 3473 public String getShortcutStartingThemeResName(int launcherUserId, 3474 @NonNull String callingPackage, @NonNull String packageName, 3475 @NonNull String shortcutId, int userId) { 3476 Objects.requireNonNull(callingPackage, "callingPackage"); 3477 Objects.requireNonNull(packageName, "packageName"); 3478 Objects.requireNonNull(shortcutId, "shortcutId"); 3479 3480 synchronized (mServiceLock) { 3481 throwIfUserLockedL(userId); 3482 throwIfUserLockedL(launcherUserId); 3483 3484 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3485 .attemptToRestoreIfNeededAndSave(); 3486 3487 final ShortcutPackage p = getUserShortcutsLocked(userId) 3488 .getPackageShortcutsIfExists(packageName); 3489 if (p == null) { 3490 return null; 3491 } 3492 3493 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 3494 return shortcutInfo != null ? shortcutInfo.getStartingThemeResName() : null; 3495 } 3496 } 3497 3498 @Override 3499 public ParcelFileDescriptor getShortcutIconFd(int launcherUserId, 3500 @NonNull String callingPackage, @NonNull String packageName, 3501 @NonNull String shortcutId, int userId) { 3502 Objects.requireNonNull(callingPackage, "callingPackage"); 3503 Objects.requireNonNull(packageName, "packageName"); 3504 Objects.requireNonNull(shortcutId, "shortcutId"); 3505 3506 synchronized (mServiceLock) { 3507 throwIfUserLockedL(userId); 3508 throwIfUserLockedL(launcherUserId); 3509 3510 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3511 .attemptToRestoreIfNeededAndSave(); 3512 3513 final ShortcutPackage p = getUserShortcutsLocked(userId) 3514 .getPackageShortcutsIfExists(packageName); 3515 if (p == null) { 3516 return null; 3517 } 3518 3519 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 3520 if (shortcutInfo == null) { 3521 return null; 3522 } 3523 return getShortcutIconParcelFileDescriptor(p, shortcutInfo); 3524 } 3525 } 3526 3527 @Override 3528 public void getShortcutIconFdAsync(int launcherUserId, @NonNull String callingPackage, 3529 @NonNull String packageName, @NonNull String shortcutId, int userId, 3530 @NonNull AndroidFuture<ParcelFileDescriptor> cb) { 3531 Objects.requireNonNull(callingPackage, "callingPackage"); 3532 Objects.requireNonNull(packageName, "packageName"); 3533 Objects.requireNonNull(shortcutId, "shortcutId"); 3534 3535 // Checks shortcuts in memory first 3536 final ShortcutPackage p; 3537 synchronized (mServiceLock) { 3538 throwIfUserLockedL(userId); 3539 throwIfUserLockedL(launcherUserId); 3540 3541 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 3542 .attemptToRestoreIfNeededAndSave(); 3543 3544 p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName); 3545 if (p == null) { 3546 cb.complete(null); 3547 return; 3548 } 3549 3550 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 3551 if (shortcutInfo != null) { 3552 cb.complete(getShortcutIconParcelFileDescriptor(p, shortcutInfo)); 3553 return; 3554 } 3555 } 3556 3557 // Otherwise check persisted shortcuts 3558 getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> 3559 cb.complete(getShortcutIconParcelFileDescriptor(p, si))); 3560 } 3561 3562 @Nullable 3563 private ParcelFileDescriptor getShortcutIconParcelFileDescriptor( 3564 @Nullable final ShortcutPackage p, @Nullable final ShortcutInfo shortcutInfo) { 3565 if (p == null || shortcutInfo == null || !shortcutInfo.hasIconFile()) { 3566 return null; 3567 } 3568 final String path = p.getBitmapPathMayWait(shortcutInfo); 3569 if (path == null) { 3570 Slog.w(TAG, "null bitmap detected in getShortcutIconFd()"); 3571 return null; 3572 } 3573 try { 3574 return ParcelFileDescriptor.open( 3575 new File(path), 3576 ParcelFileDescriptor.MODE_READ_ONLY); 3577 } catch (FileNotFoundException e) { 3578 Slog.e(TAG, "Icon file not found: " + path); 3579 return null; 3580 } 3581 } 3582 3583 @Override 3584 public String getShortcutIconUri(int launcherUserId, @NonNull String launcherPackage, 3585 @NonNull String packageName, @NonNull String shortcutId, int userId) { 3586 Objects.requireNonNull(launcherPackage, "launcherPackage"); 3587 Objects.requireNonNull(packageName, "packageName"); 3588 Objects.requireNonNull(shortcutId, "shortcutId"); 3589 3590 synchronized (mServiceLock) { 3591 throwIfUserLockedL(userId); 3592 throwIfUserLockedL(launcherUserId); 3593 3594 getLauncherShortcutsLocked(launcherPackage, userId, launcherUserId) 3595 .attemptToRestoreIfNeededAndSave(); 3596 3597 final ShortcutPackage p = getUserShortcutsLocked(userId) 3598 .getPackageShortcutsIfExists(packageName); 3599 if (p == null) { 3600 return null; 3601 } 3602 3603 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 3604 if (shortcutInfo == null) { 3605 return null; 3606 } 3607 return getShortcutIconUriInternal(launcherUserId, launcherPackage, 3608 packageName, shortcutInfo, userId); 3609 } 3610 } 3611 3612 @Override 3613 public void getShortcutIconUriAsync(int launcherUserId, @NonNull String launcherPackage, 3614 @NonNull String packageName, @NonNull String shortcutId, int userId, 3615 @NonNull AndroidFuture<String> cb) { 3616 Objects.requireNonNull(launcherPackage, "launcherPackage"); 3617 Objects.requireNonNull(packageName, "packageName"); 3618 Objects.requireNonNull(shortcutId, "shortcutId"); 3619 3620 // Checks shortcuts in memory first 3621 synchronized (mServiceLock) { 3622 throwIfUserLockedL(userId); 3623 throwIfUserLockedL(launcherUserId); 3624 3625 getLauncherShortcutsLocked(launcherPackage, userId, launcherUserId) 3626 .attemptToRestoreIfNeededAndSave(); 3627 3628 final ShortcutPackage p = getUserShortcutsLocked(userId) 3629 .getPackageShortcutsIfExists(packageName); 3630 if (p == null) { 3631 cb.complete(null); 3632 return; 3633 } 3634 3635 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 3636 if (shortcutInfo != null) { 3637 cb.complete(getShortcutIconUriInternal(launcherUserId, launcherPackage, 3638 packageName, shortcutInfo, userId)); 3639 return; 3640 } 3641 } 3642 3643 // Otherwise check persisted shortcuts 3644 getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> { 3645 cb.complete(si == null ? null : getShortcutIconUriInternal(launcherUserId, 3646 launcherPackage, packageName, si, userId)); 3647 }); 3648 } 3649 3650 private String getShortcutIconUriInternal(int launcherUserId, 3651 @NonNull String launcherPackage, @NonNull String packageName, 3652 @NonNull ShortcutInfo shortcutInfo, int userId) { 3653 if (!shortcutInfo.hasIconUri()) { 3654 return null; 3655 } 3656 String uri = shortcutInfo.getIconUri(); 3657 if (uri == null) { 3658 Slog.w(TAG, "null uri detected in getShortcutIconUri()"); 3659 return null; 3660 } 3661 3662 final long token = Binder.clearCallingIdentity(); 3663 try { 3664 int packageUid = mPackageManagerInternal.getPackageUid(packageName, 3665 PackageManager.MATCH_DIRECT_BOOT_AUTO, userId); 3666 // Grant read uri permission to the caller on behalf of the shortcut owner. All 3667 // granted permissions are revoked when the default launcher changes, or when 3668 // device is rebooted. 3669 mUriGrantsManager.grantUriPermissionFromOwner(mUriPermissionOwner, packageUid, 3670 launcherPackage, Uri.parse(uri), Intent.FLAG_GRANT_READ_URI_PERMISSION, 3671 userId, launcherUserId); 3672 } catch (Exception e) { 3673 Slog.e(TAG, "Failed to grant uri access to " + launcherPackage + " for " + uri, 3674 e); 3675 uri = null; 3676 } finally { 3677 Binder.restoreCallingIdentity(token); 3678 } 3679 return uri; 3680 } 3681 3682 @Override 3683 public boolean hasShortcutHostPermission(int launcherUserId, 3684 @NonNull String callingPackage, int callingPid, int callingUid) { 3685 return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId, 3686 callingPid, callingUid); 3687 } 3688 3689 public boolean areShortcutsSupportedOnHomeScreen(@UserIdInt int userId) { 3690 return ShortcutService.this.areShortcutsSupportedOnHomeScreen(userId); 3691 } 3692 3693 @Override 3694 public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName, 3695 int userId) { 3696 ShortcutService.this.setShortcutHostPackage(type, packageName, userId); 3697 } 3698 3699 @Override 3700 public boolean requestPinAppWidget(@NonNull String callingPackage, 3701 @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras, 3702 @Nullable IntentSender resultIntent, int userId) { 3703 Objects.requireNonNull(appWidget); 3704 return requestPinItem(callingPackage, userId, null, appWidget, extras, resultIntent); 3705 } 3706 3707 @Override 3708 public boolean isRequestPinItemSupported(int callingUserId, int requestType) { 3709 return ShortcutService.this.isRequestPinItemSupported(callingUserId, requestType); 3710 } 3711 3712 @Override 3713 public boolean isForegroundDefaultLauncher(@NonNull String callingPackage, int callingUid) { 3714 Objects.requireNonNull(callingPackage); 3715 3716 final int userId = UserHandle.getUserId(callingUid); 3717 final String defaultLauncher = getDefaultLauncher(userId); 3718 if (defaultLauncher == null) { 3719 return false; 3720 } 3721 if (!callingPackage.equals(defaultLauncher)) { 3722 return false; 3723 } 3724 synchronized (mServiceLock) { 3725 if (!isUidForegroundLocked(callingUid)) { 3726 return false; 3727 } 3728 } 3729 return true; 3730 } 3731 } 3732 3733 final BroadcastReceiver mReceiver = new BroadcastReceiver() { 3734 @Override 3735 public void onReceive(Context context, Intent intent) { 3736 if (!mBootCompleted.get()) { 3737 return; // Boot not completed, ignore the broadcast. 3738 } 3739 try { 3740 if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) { 3741 handleLocaleChanged(); 3742 } 3743 } catch (Exception e) { 3744 wtf("Exception in mReceiver.onReceive", e); 3745 } 3746 } 3747 }; 3748 3749 void handleLocaleChanged() { 3750 if (DEBUG) { 3751 Slog.d(TAG, "handleLocaleChanged"); 3752 } 3753 scheduleSaveBaseState(); 3754 3755 synchronized (mServiceLock) { 3756 final long token = injectClearCallingIdentity(); 3757 try { 3758 forEachLoadedUserLocked(user -> user.detectLocaleChange()); 3759 } finally { 3760 injectRestoreCallingIdentity(token); 3761 } 3762 } 3763 } 3764 3765 /** 3766 * Package event callbacks. 3767 */ 3768 @VisibleForTesting 3769 final BroadcastReceiver mPackageMonitor = new BroadcastReceiver() { 3770 @Override 3771 public void onReceive(Context context, Intent intent) { 3772 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); 3773 if (userId == UserHandle.USER_NULL) { 3774 Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent); 3775 return; 3776 } 3777 3778 final String action = intent.getAction(); 3779 3780 // This is normally called on Handler, so clearCallingIdentity() isn't needed, 3781 // but we still check it in unit tests. 3782 final long token = injectClearCallingIdentity(); 3783 try { 3784 synchronized (mServiceLock) { 3785 if (!isUserUnlockedL(userId)) { 3786 if (DEBUG) { 3787 Slog.d(TAG, "Ignoring package broadcast " + action 3788 + " for locked/stopped user " + userId); 3789 } 3790 return; 3791 } 3792 } 3793 3794 final Uri intentUri = intent.getData(); 3795 final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart() 3796 : null; 3797 if (packageName == null) { 3798 Slog.w(TAG, "Intent broadcast does not contain package name: " + intent); 3799 return; 3800 } 3801 3802 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); 3803 final boolean archival = intent.getBooleanExtra(Intent.EXTRA_ARCHIVAL, false); 3804 3805 Slog.d(TAG, "received package broadcast intent: " + intent); 3806 switch (action) { 3807 case Intent.ACTION_PACKAGE_ADDED: 3808 if (replacing) { 3809 Slog.d(TAG, "replacing package: " + packageName + " userId" + userId); 3810 handlePackageUpdateFinished(packageName, userId); 3811 } else { 3812 Slog.d(TAG, "adding package: " + packageName + " userId" + userId); 3813 handlePackageAdded(packageName, userId); 3814 } 3815 break; 3816 case Intent.ACTION_PACKAGE_REMOVED: 3817 if (!replacing || (replacing && archival)) { 3818 if (!replacing) { 3819 Slog.d(TAG, "removing package: " 3820 + packageName + " userId" + userId); 3821 } else if (archival) { 3822 Slog.d(TAG, "archiving package: " 3823 + packageName + " userId" + userId); 3824 } 3825 handlePackageRemoved(packageName, userId); 3826 } 3827 break; 3828 case Intent.ACTION_PACKAGE_CHANGED: 3829 Slog.d(TAG, "changing package: " + packageName + " userId" + userId); 3830 handlePackageChanged(packageName, userId); 3831 break; 3832 case Intent.ACTION_PACKAGE_DATA_CLEARED: 3833 Slog.d(TAG, "clearing data for package: " 3834 + packageName + " userId" + userId); 3835 handlePackageDataCleared(packageName, userId); 3836 break; 3837 } 3838 } catch (Exception e) { 3839 wtf("Exception in mPackageMonitor.onReceive", e); 3840 } finally { 3841 injectRestoreCallingIdentity(token); 3842 } 3843 } 3844 }; 3845 3846 private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() { 3847 @Override 3848 public void onReceive(Context context, Intent intent) { 3849 if (DEBUG || DEBUG_REBOOT) { 3850 Slog.d(TAG, "Shutdown broadcast received."); 3851 } 3852 // Since it cleans up the shortcut directory and rewrite the ShortcutPackageItems 3853 // in odrder during saveToXml(), it could lead to shortcuts missing when shutdown. 3854 // We need it so that it can finish up saving before shutdown. 3855 synchronized (mServiceLock) { 3856 if (mHandler.hasCallbacks(mSaveDirtyInfoRunner)) { 3857 mHandler.removeCallbacks(mSaveDirtyInfoRunner); 3858 forEachLoadedUserLocked(ShortcutUser::cancelAllInFlightTasks); 3859 saveDirtyInfo(); 3860 } 3861 mShutdown.set(true); 3862 } 3863 } 3864 }; 3865 3866 /** 3867 * Called when a user is unlocked. 3868 * - Check all known packages still exist, and otherwise perform cleanup. 3869 * - If a package still exists, check the version code. If it's been updated, may need to 3870 * update timestamps of its shortcuts. 3871 */ 3872 @VisibleForTesting 3873 void checkPackageChanges(@UserIdInt int ownerUserId) { 3874 if (DEBUG || DEBUG_REBOOT) { 3875 Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId); 3876 } 3877 if (injectIsSafeModeEnabled()) { 3878 Slog.i(TAG, "Safe mode, skipping checkPackageChanges()"); 3879 return; 3880 } 3881 3882 final long start = getStatStartTime(); 3883 try { 3884 final ArrayList<UserPackage> gonePackages = new ArrayList<>(); 3885 3886 synchronized (mServiceLock) { 3887 final ShortcutUser user = getUserShortcutsLocked(ownerUserId); 3888 3889 // Find packages that have been uninstalled. 3890 user.forAllPackageItems(spi -> { 3891 if (spi.getPackageInfo().isShadow()) { 3892 return; // Don't delete shadow information. 3893 } 3894 if (!isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) { 3895 if (DEBUG) { 3896 Slog.d(TAG, "Uninstalled: " + spi.getPackageName() 3897 + " user " + spi.getPackageUserId()); 3898 } 3899 gonePackages.add( 3900 UserPackage.of(spi.getPackageUserId(), spi.getPackageName())); 3901 } 3902 }); 3903 if (gonePackages.size() > 0) { 3904 for (int i = gonePackages.size() - 1; i >= 0; i--) { 3905 final UserPackage up = gonePackages.get(i); 3906 cleanUpPackageLocked(up.packageName, ownerUserId, up.userId, 3907 /* appStillExists = */ false); 3908 } 3909 } 3910 3911 rescanUpdatedPackagesLocked(ownerUserId, user.getLastAppScanTime()); 3912 } 3913 } finally { 3914 logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start); 3915 } 3916 verifyStates(); 3917 } 3918 3919 @GuardedBy("mServiceLock") 3920 private void rescanUpdatedPackagesLocked(@UserIdInt int userId, long lastScanTime) { 3921 if (DEBUG_REBOOT) { 3922 Slog.d(TAG, "rescan updated package user=" + userId + " last scanned=" + lastScanTime); 3923 } 3924 final ShortcutUser user = getUserShortcutsLocked(userId); 3925 3926 // Note after each OTA, we'll need to rescan all system apps, as their lastUpdateTime 3927 // is not reliable. 3928 final long now = injectCurrentTimeMillis(); 3929 final boolean afterOta = 3930 !injectBuildFingerprint().equals(user.getLastAppScanOsFingerprint()); 3931 3932 // Then for each installed app, publish manifest shortcuts when needed. 3933 forUpdatedPackages(userId, lastScanTime, afterOta, ai -> { 3934 user.attemptToRestoreIfNeededAndSave(this, ai.packageName, userId); 3935 3936 user.rescanPackageIfNeeded(ai.packageName, /* forceRescan= */ true); 3937 }); 3938 3939 // Write the time just before the scan, because there may be apps that have just 3940 // been updated, and we want to catch them in the next time. 3941 user.setLastAppScanTime(now); 3942 user.setLastAppScanOsFingerprint(injectBuildFingerprint()); 3943 scheduleSaveUser(userId); 3944 } 3945 3946 private void handlePackageAdded(String packageName, @UserIdInt int userId) { 3947 if (DEBUG || DEBUG_REBOOT) { 3948 Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId)); 3949 } 3950 synchronized (mServiceLock) { 3951 final ShortcutUser user = getUserShortcutsLocked(userId); 3952 user.attemptToRestoreIfNeededAndSave(this, packageName, userId); 3953 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 3954 } 3955 verifyStates(); 3956 } 3957 3958 private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) { 3959 if (DEBUG || DEBUG_REBOOT) { 3960 Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d", 3961 packageName, userId)); 3962 } 3963 synchronized (mServiceLock) { 3964 final ShortcutUser user = getUserShortcutsLocked(userId); 3965 user.attemptToRestoreIfNeededAndSave(this, packageName, userId); 3966 3967 if (isPackageInstalled(packageName, userId)) { 3968 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 3969 } 3970 } 3971 verifyStates(); 3972 } 3973 3974 private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) { 3975 if (DEBUG || DEBUG_REBOOT) { 3976 Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, 3977 packageUserId)); 3978 } 3979 cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ false); 3980 3981 verifyStates(); 3982 } 3983 3984 private void handlePackageDataCleared(String packageName, int packageUserId) { 3985 if (DEBUG || DEBUG_REBOOT) { 3986 Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName, 3987 packageUserId)); 3988 } 3989 cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ true); 3990 3991 verifyStates(); 3992 } 3993 3994 private void handlePackageChanged(String packageName, int packageUserId) { 3995 if (!isPackageInstalled(packageName, packageUserId)) { 3996 // Probably disabled, which is the same thing as uninstalled. 3997 handlePackageRemoved(packageName, packageUserId); 3998 return; 3999 } 4000 if (DEBUG || DEBUG_REBOOT) { 4001 Slog.d(TAG, String.format("handlePackageChanged: %s user=%d", packageName, 4002 packageUserId)); 4003 } 4004 4005 // Activities may be disabled or enabled. Just rescan the package. 4006 synchronized (mServiceLock) { 4007 final ShortcutUser user = getUserShortcutsLocked(packageUserId); 4008 4009 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true); 4010 } 4011 4012 verifyStates(); 4013 } 4014 4015 // === PackageManager interaction === 4016 4017 /** 4018 * Returns {@link PackageInfo} unless it's uninstalled or disabled. 4019 */ 4020 @Nullable 4021 final PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) { 4022 return getPackageInfo(packageName, userId, true); 4023 } 4024 4025 /** 4026 * Returns {@link PackageInfo} unless it's uninstalled or disabled. 4027 */ 4028 @Nullable 4029 final PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) { 4030 return getPackageInfo(packageName, userId, false); 4031 } 4032 4033 int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) { 4034 final long token = injectClearCallingIdentity(); 4035 try { 4036 return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS, userId); 4037 } catch (RemoteException e) { 4038 // Shouldn't happen. 4039 Slog.wtf(TAG, "RemoteException", e); 4040 return -1; 4041 } finally { 4042 injectRestoreCallingIdentity(token); 4043 } 4044 } 4045 4046 /** 4047 * Returns {@link PackageInfo} unless it's uninstalled or disabled. 4048 */ 4049 @Nullable 4050 @VisibleForTesting 4051 final PackageInfo getPackageInfo(String packageName, @UserIdInt int userId, 4052 boolean getSignatures) { 4053 return isInstalledOrNull(injectPackageInfoWithUninstalled( 4054 packageName, userId, getSignatures)); 4055 } 4056 4057 /** 4058 * Do not use directly; this returns uninstalled packages too. 4059 */ 4060 @Nullable 4061 @VisibleForTesting 4062 PackageInfo injectPackageInfoWithUninstalled(String packageName, @UserIdInt int userId, 4063 boolean getSignatures) { 4064 final long start = getStatStartTime(); 4065 final long token = injectClearCallingIdentity(); 4066 try { 4067 return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS 4068 | (getSignatures ? PackageManager.GET_SIGNING_CERTIFICATES : 0), userId); 4069 } catch (RemoteException e) { 4070 // Shouldn't happen. 4071 Slog.wtf(TAG, "RemoteException", e); 4072 return null; 4073 } finally { 4074 injectRestoreCallingIdentity(token); 4075 4076 logDurationStat( 4077 (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO), 4078 start); 4079 } 4080 } 4081 4082 /** 4083 * Returns {@link ApplicationInfo} unless it's uninstalled or disabled. 4084 */ 4085 @Nullable 4086 @VisibleForTesting 4087 final ApplicationInfo getApplicationInfo(String packageName, @UserIdInt int userId) { 4088 return isInstalledOrNull(injectApplicationInfoWithUninstalled(packageName, userId)); 4089 } 4090 4091 /** 4092 * Do not use directly; this returns uninstalled packages too. 4093 */ 4094 @Nullable 4095 @VisibleForTesting 4096 ApplicationInfo injectApplicationInfoWithUninstalled( 4097 String packageName, @UserIdInt int userId) { 4098 final long start = getStatStartTime(); 4099 final long token = injectClearCallingIdentity(); 4100 try { 4101 return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId); 4102 } catch (RemoteException e) { 4103 // Shouldn't happen. 4104 Slog.wtf(TAG, "RemoteException", e); 4105 return null; 4106 } finally { 4107 injectRestoreCallingIdentity(token); 4108 4109 logDurationStat(Stats.GET_APPLICATION_INFO, start); 4110 } 4111 } 4112 4113 /** 4114 * Returns {@link ActivityInfo} with its metadata unless it's uninstalled or disabled. 4115 */ 4116 @Nullable 4117 final ActivityInfo getActivityInfoWithMetadata(ComponentName activity, @UserIdInt int userId) { 4118 return isInstalledOrNull(injectGetActivityInfoWithMetadataWithUninstalled( 4119 activity, userId)); 4120 } 4121 4122 /** 4123 * Do not use directly; this returns uninstalled packages too. 4124 */ 4125 @Nullable 4126 @VisibleForTesting 4127 ActivityInfo injectGetActivityInfoWithMetadataWithUninstalled( 4128 ComponentName activity, @UserIdInt int userId) { 4129 final long start = getStatStartTime(); 4130 final long token = injectClearCallingIdentity(); 4131 try { 4132 return mIPackageManager.getActivityInfo(activity, 4133 PACKAGE_MATCH_FLAGS | PackageManager.GET_META_DATA, userId); 4134 } catch (RemoteException e) { 4135 // Shouldn't happen. 4136 Slog.wtf(TAG, "RemoteException", e); 4137 return null; 4138 } finally { 4139 injectRestoreCallingIdentity(token); 4140 4141 logDurationStat(Stats.GET_ACTIVITY_WITH_METADATA, start); 4142 } 4143 } 4144 4145 /** 4146 * Return all installed and enabled packages. 4147 */ 4148 @NonNull 4149 @VisibleForTesting 4150 final List<PackageInfo> getInstalledPackages(@UserIdInt int userId) { 4151 final long start = getStatStartTime(); 4152 final long token = injectClearCallingIdentity(); 4153 try { 4154 final List<PackageInfo> all = injectGetPackagesWithUninstalled(userId); 4155 4156 all.removeIf(PACKAGE_NOT_INSTALLED); 4157 4158 return all; 4159 } catch (RemoteException e) { 4160 // Shouldn't happen. 4161 Slog.wtf(TAG, "RemoteException", e); 4162 return null; 4163 } finally { 4164 injectRestoreCallingIdentity(token); 4165 4166 logDurationStat(Stats.GET_INSTALLED_PACKAGES, start); 4167 } 4168 } 4169 4170 /** 4171 * Do not use directly; this returns uninstalled packages too. 4172 */ 4173 @NonNull 4174 @VisibleForTesting 4175 List<PackageInfo> injectGetPackagesWithUninstalled(@UserIdInt int userId) 4176 throws RemoteException { 4177 final ParceledListSlice<PackageInfo> parceledList = 4178 mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId); 4179 if (parceledList == null) { 4180 return Collections.emptyList(); 4181 } 4182 return parceledList.getList(); 4183 } 4184 4185 private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime, boolean afterOta, 4186 Consumer<ApplicationInfo> callback) { 4187 if (DEBUG || DEBUG_REBOOT) { 4188 Slog.d(TAG, "forUpdatedPackages for user " + userId + ", lastScanTime=" + lastScanTime 4189 + " afterOta=" + afterOta); 4190 } 4191 final List<PackageInfo> list = getInstalledPackages(userId); 4192 for (int i = list.size() - 1; i >= 0; i--) { 4193 final PackageInfo pi = list.get(i); 4194 4195 // If the package has been updated since the last scan time, then scan it. 4196 // Also if it's right after an OTA, always re-scan all apps anyway, since the 4197 // shortcut parser might have changed. 4198 if (afterOta || (pi.lastUpdateTime >= lastScanTime)) { 4199 if (DEBUG || DEBUG_REBOOT) { 4200 Slog.d(TAG, "Found updated package " + pi.packageName 4201 + " updateTime=" + pi.lastUpdateTime); 4202 } 4203 callback.accept(pi.applicationInfo); 4204 } 4205 } 4206 } 4207 4208 private boolean isApplicationFlagSet(@NonNull String packageName, int userId, int flags) { 4209 final ApplicationInfo ai = injectApplicationInfoWithUninstalled(packageName, userId); 4210 return (ai != null) && ((ai.flags & flags) == flags); 4211 } 4212 4213 // Due to b/38267327, ActivityInfo.enabled may not reflect the current state of the component 4214 // and we need to check the enabled state via PackageManager.getComponentEnabledSetting. 4215 private boolean isEnabled(@Nullable ActivityInfo ai, int userId) { 4216 if (ai == null) { 4217 return false; 4218 } 4219 4220 int enabledFlag; 4221 final long token = injectClearCallingIdentity(); 4222 try { 4223 enabledFlag = mIPackageManager.getComponentEnabledSetting( 4224 ai.getComponentName(), userId); 4225 } catch (RemoteException e) { 4226 // Shouldn't happen. 4227 Slog.wtf(TAG, "RemoteException", e); 4228 return false; 4229 } finally { 4230 injectRestoreCallingIdentity(token); 4231 } 4232 4233 if ((enabledFlag == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && ai.enabled) 4234 || enabledFlag == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { 4235 return true; 4236 } 4237 return false; 4238 } 4239 4240 private static boolean isSystem(@Nullable ActivityInfo ai) { 4241 return (ai != null) && isSystem(ai.applicationInfo); 4242 } 4243 4244 private static boolean isSystem(@Nullable ApplicationInfo ai) { 4245 return (ai != null) && (ai.flags & SYSTEM_APP_MASK) != 0; 4246 } 4247 4248 private static boolean isInstalled(@Nullable ApplicationInfo ai) { 4249 return (ai != null) && ai.enabled && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0; 4250 } 4251 4252 private static boolean isEphemeralApp(@Nullable ApplicationInfo ai) { 4253 return (ai != null) && ai.isInstantApp(); 4254 } 4255 4256 private static boolean isInstalled(@Nullable PackageInfo pi) { 4257 return (pi != null) && isInstalled(pi.applicationInfo); 4258 } 4259 4260 private static boolean isInstalled(@Nullable ActivityInfo ai) { 4261 return (ai != null) && isInstalled(ai.applicationInfo); 4262 } 4263 4264 private static ApplicationInfo isInstalledOrNull(ApplicationInfo ai) { 4265 return isInstalled(ai) ? ai : null; 4266 } 4267 4268 private static PackageInfo isInstalledOrNull(PackageInfo pi) { 4269 return isInstalled(pi) ? pi : null; 4270 } 4271 4272 private static ActivityInfo isInstalledOrNull(ActivityInfo ai) { 4273 return isInstalled(ai) ? ai : null; 4274 } 4275 4276 boolean isPackageInstalled(String packageName, int userId) { 4277 return getApplicationInfo(packageName, userId) != null; 4278 } 4279 4280 boolean isEphemeralApp(String packageName, int userId) { 4281 return isEphemeralApp(getApplicationInfo(packageName, userId)); 4282 } 4283 4284 @Nullable 4285 XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) { 4286 return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key); 4287 } 4288 4289 @Nullable 4290 Resources injectGetResourcesForApplicationAsUser(String packageName, int userId) { 4291 final long start = getStatStartTime(); 4292 final long token = injectClearCallingIdentity(); 4293 try { 4294 return mContext.createContextAsUser(UserHandle.of(userId), /* flags */ 0) 4295 .getPackageManager().getResourcesForApplication(packageName); 4296 } catch (NameNotFoundException e) { 4297 Slog.e(TAG, "Resources of package " + packageName + " for user " + userId 4298 + " not found"); 4299 return null; 4300 } finally { 4301 injectRestoreCallingIdentity(token); 4302 4303 logDurationStat(Stats.GET_APPLICATION_RESOURCES, start); 4304 } 4305 } 4306 4307 private Intent getMainActivityIntent() { 4308 final Intent intent = new Intent(Intent.ACTION_MAIN); 4309 intent.addCategory(LAUNCHER_INTENT_CATEGORY); 4310 return intent; 4311 } 4312 4313 /** 4314 * Same as queryIntentActivitiesAsUser, except it makes sure the package is installed, 4315 * and only returns exported activities. 4316 */ 4317 @NonNull 4318 @VisibleForTesting 4319 List<ResolveInfo> queryActivities(@NonNull Intent baseIntent, 4320 @NonNull String packageName, @Nullable ComponentName activity, int userId) { 4321 4322 baseIntent.setPackage(Objects.requireNonNull(packageName)); 4323 if (activity != null) { 4324 baseIntent.setComponent(activity); 4325 } 4326 return queryActivities(baseIntent, userId, /* exportedOnly =*/ true); 4327 } 4328 4329 @NonNull 4330 List<ResolveInfo> queryActivities(@NonNull Intent intent, int userId, 4331 boolean exportedOnly) { 4332 final List<ResolveInfo> resolved; 4333 final long token = injectClearCallingIdentity(); 4334 try { 4335 resolved = mContext.getPackageManager().queryIntentActivitiesAsUser(intent, 4336 PACKAGE_MATCH_FLAGS | PackageManager.MATCH_DISABLED_COMPONENTS, userId); 4337 } finally { 4338 injectRestoreCallingIdentity(token); 4339 } 4340 if (resolved == null || resolved.size() == 0) { 4341 return EMPTY_RESOLVE_INFO; 4342 } 4343 // Make sure the package is installed. 4344 resolved.removeIf(ACTIVITY_NOT_INSTALLED); 4345 resolved.removeIf((ri) -> { 4346 final ActivityInfo ai = ri.activityInfo; 4347 return !isSystem(ai) && !isEnabled(ai, userId); 4348 }); 4349 if (exportedOnly) { 4350 resolved.removeIf(ACTIVITY_NOT_EXPORTED); 4351 } 4352 return resolved; 4353 } 4354 4355 /** 4356 * Return the main activity that is exported and, for non-system apps, enabled. If multiple 4357 * activities are found, return the first one. 4358 */ 4359 @Nullable 4360 ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) { 4361 final long start = getStatStartTime(); 4362 try { 4363 final List<ResolveInfo> resolved = 4364 queryActivities(getMainActivityIntent(), packageName, null, userId); 4365 return resolved.size() == 0 ? null : resolved.get(0).activityInfo.getComponentName(); 4366 } finally { 4367 logDurationStat(Stats.GET_LAUNCHER_ACTIVITY, start); 4368 } 4369 } 4370 4371 /** 4372 * Return whether an activity is main, exported and, for non-system apps, enabled. 4373 */ 4374 boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) { 4375 final long start = getStatStartTime(); 4376 try { 4377 if (activity == null) { 4378 wtf("null activity detected"); 4379 return false; 4380 } 4381 if (DUMMY_MAIN_ACTIVITY.equals(activity.getClassName())) { 4382 return true; 4383 } 4384 final List<ResolveInfo> resolved = queryActivities( 4385 getMainActivityIntent(), activity.getPackageName(), activity, userId); 4386 return resolved.size() > 0; 4387 } finally { 4388 logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start); 4389 } 4390 } 4391 4392 /** 4393 * Create a placeholder "main activity" component name which is used to create a dynamic shortcut 4394 * with no main activity temporarily. 4395 */ 4396 @NonNull 4397 ComponentName getDummyMainActivity(@NonNull String packageName) { 4398 return new ComponentName(packageName, DUMMY_MAIN_ACTIVITY); 4399 } 4400 4401 boolean isDummyMainActivity(@Nullable ComponentName name) { 4402 return name != null && DUMMY_MAIN_ACTIVITY.equals(name.getClassName()); 4403 } 4404 4405 /** 4406 * Return all the main activities that are exported and, for non-system apps, enabled, from a 4407 * package. 4408 */ 4409 @NonNull 4410 List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) { 4411 final long start = getStatStartTime(); 4412 try { 4413 return queryActivities(getMainActivityIntent(), packageName, null, userId); 4414 } finally { 4415 logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start); 4416 } 4417 } 4418 4419 /** 4420 * Return whether an activity is enabled and exported. 4421 */ 4422 @VisibleForTesting 4423 boolean injectIsActivityEnabledAndExported( 4424 @NonNull ComponentName activity, @UserIdInt int userId) { 4425 final long start = getStatStartTime(); 4426 try { 4427 return queryActivities(new Intent(), activity.getPackageName(), activity, userId) 4428 .size() > 0; 4429 } finally { 4430 logDurationStat(Stats.IS_ACTIVITY_ENABLED, start); 4431 } 4432 } 4433 4434 /** 4435 * Get the {@link LauncherApps#ACTION_CONFIRM_PIN_SHORTCUT} or 4436 * {@link LauncherApps#ACTION_CONFIRM_PIN_APPWIDGET} activity in a given package depending on 4437 * the requestType. 4438 */ 4439 @Nullable 4440 ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName, 4441 int launcherUserId, int requestType) { 4442 Objects.requireNonNull(launcherPackageName); 4443 String action = requestType == LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ? 4444 LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT : 4445 LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET; 4446 4447 final Intent confirmIntent = new Intent(action).setPackage(launcherPackageName); 4448 final List<ResolveInfo> candidates = queryActivities( 4449 confirmIntent, launcherUserId, /* exportedOnly =*/ false); 4450 for (ResolveInfo ri : candidates) { 4451 return ri.activityInfo.getComponentName(); 4452 } 4453 return null; 4454 } 4455 4456 boolean injectIsSafeModeEnabled() { 4457 final long token = injectClearCallingIdentity(); 4458 try { 4459 return IWindowManager.Stub 4460 .asInterface(ServiceManager.getService(Context.WINDOW_SERVICE)) 4461 .isSafeModeEnabled(); 4462 } catch (RemoteException e) { 4463 return false; // Shouldn't happen though. 4464 } finally { 4465 injectRestoreCallingIdentity(token); 4466 } 4467 } 4468 4469 /** 4470 * If {@code userId} is of a managed profile, return the parent user ID. Otherwise return 4471 * itself. 4472 */ 4473 int getParentOrSelfUserId(int userId) { 4474 return mUserManagerInternal.getProfileParentId(userId); 4475 } 4476 4477 void injectSendIntentSender(IntentSender intentSender, Intent extras) { 4478 if (intentSender == null) { 4479 return; 4480 } 4481 try { 4482 ActivityOptions options = ActivityOptions.makeBasic() 4483 .setPendingIntentBackgroundActivityStartMode( 4484 MODE_BACKGROUND_ACTIVITY_START_DENIED); 4485 intentSender.sendIntent(mContext, /* code= */ 0, extras, 4486 /* onFinished=*/ null, /* handler= */ null, null, options.toBundle()); 4487 } catch (SendIntentException e) { 4488 Slog.w(TAG, "sendIntent failed().", e); 4489 } 4490 } 4491 4492 // === Backup & restore === 4493 4494 boolean shouldBackupApp(String packageName, int userId) { 4495 return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP); 4496 } 4497 4498 static boolean shouldBackupApp(PackageInfo pi) { 4499 return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0; 4500 } 4501 4502 @Override 4503 public byte[] getBackupPayload(@UserIdInt int userId) { 4504 enforceSystem(); 4505 if (DEBUG) { 4506 Slog.d(TAG, "Backing up user " + userId); 4507 } 4508 synchronized (mServiceLock) { 4509 if (!isUserUnlockedL(userId)) { 4510 wtf("Can't backup: user " + userId + " is locked or not running"); 4511 return null; 4512 } 4513 4514 final ShortcutUser user = getUserShortcutsLocked(userId); 4515 if (user == null) { 4516 wtf("Can't backup: user not found: id=" + userId); 4517 return null; 4518 } 4519 4520 // Update the signatures for all packages. 4521 user.forAllPackageItems(spi -> spi.refreshPackageSignatureAndSave()); 4522 4523 // Rescan all apps; this will also update the version codes and "allow-backup". 4524 user.forAllPackages(pkg -> pkg.rescanPackageIfNeeded( 4525 /*isNewApp=*/ false, /*forceRescan=*/ true)); 4526 4527 // Set the version code for the launchers. 4528 user.forAllLaunchers(launcher -> launcher.ensurePackageInfo()); 4529 4530 // Save to the filesystem. 4531 scheduleSaveUser(userId); 4532 saveDirtyInfo(); 4533 4534 // Note, in case of backup, we don't have to wait on bitmap saving, because we don't 4535 // back up bitmaps anyway. 4536 4537 // Then create the backup payload. 4538 final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024); 4539 try { 4540 saveUserInternalLocked(userId, os, /* forBackup */ true); 4541 } catch (XmlPullParserException | IOException e) { 4542 // Shouldn't happen. 4543 Slog.w(TAG, "Backup failed.", e); 4544 return null; 4545 } 4546 byte[] payload = os.toByteArray(); 4547 mShortcutDumpFiles.save("backup-1-payload.txt", payload); 4548 return payload; 4549 } 4550 } 4551 4552 @Override 4553 public void applyRestore(byte[] payload, @UserIdInt int userId) { 4554 enforceSystem(); 4555 if (DEBUG || DEBUG_REBOOT) { 4556 Slog.d(TAG, "Restoring user " + userId); 4557 } 4558 synchronized (mServiceLock) { 4559 if (!isUserUnlockedL(userId)) { 4560 wtf("Can't restore: user " + userId + " is locked or not running"); 4561 return; 4562 } 4563 // Note we print the file timestamps in dumpsys too, but also printing the timestamp 4564 // in the files anyway. 4565 mShortcutDumpFiles.save("restore-0-start.txt", pw -> { 4566 pw.print("Start time: "); 4567 dumpCurrentTime(pw); 4568 pw.println(); 4569 }); 4570 mShortcutDumpFiles.save("restore-1-payload.xml", payload); 4571 // Actually do restore. 4572 final ShortcutUser restored; 4573 final ByteArrayInputStream is = new ByteArrayInputStream(payload); 4574 try { 4575 restored = loadUserInternal(userId, is, /* fromBackup */ true); 4576 } catch (XmlPullParserException | IOException | InvalidFileFormatException e) { 4577 Slog.w(TAG, "Restoration failed.", e); 4578 return; 4579 } 4580 mShortcutDumpFiles.save("restore-2.txt", this::dumpInner); 4581 getUserShortcutsLocked(userId).mergeRestoredFile(restored); 4582 mShortcutDumpFiles.save("restore-3.txt", this::dumpInner); 4583 // Rescan all packages to re-publish manifest shortcuts and do other checks. 4584 rescanUpdatedPackagesLocked(userId, 4585 0 // lastScanTime = 0; rescan all packages. 4586 ); 4587 mShortcutDumpFiles.save("restore-4.txt", this::dumpInner); 4588 mShortcutDumpFiles.save("restore-5-finish.txt", pw -> { 4589 pw.print("Finish time: "); 4590 dumpCurrentTime(pw); 4591 pw.println(); 4592 }); 4593 } 4594 saveUser(userId); 4595 } 4596 4597 // === Dump === 4598 4599 @Override 4600 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 4601 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; 4602 dumpNoCheck(fd, pw, args); 4603 } 4604 4605 @VisibleForTesting 4606 void dumpNoCheck(FileDescriptor fd, PrintWriter pw, String[] args) { 4607 final DumpFilter filter = parseDumpArgs(args); 4608 4609 if (filter.shouldDumpCheckIn()) { 4610 // Other flags are not supported for checkin. 4611 dumpCheckin(pw, filter.shouldCheckInClear()); 4612 } else { 4613 if (filter.shouldDumpMain()) { 4614 dumpInner(pw, filter); 4615 pw.println(); 4616 } 4617 if (filter.shouldDumpUid()) { 4618 dumpUid(pw); 4619 pw.println(); 4620 } 4621 if (filter.shouldDumpFiles()) { 4622 dumpDumpFiles(pw); 4623 pw.println(); 4624 } 4625 } 4626 } 4627 4628 private static DumpFilter parseDumpArgs(String[] args) { 4629 final DumpFilter filter = new DumpFilter(); 4630 if (args == null) { 4631 return filter; 4632 } 4633 4634 int argIndex = 0; 4635 while (argIndex < args.length) { 4636 final String arg = args[argIndex++]; 4637 4638 if ("-c".equals(arg)) { 4639 filter.setDumpCheckIn(true); 4640 continue; 4641 } 4642 if ("--checkin".equals(arg)) { 4643 filter.setDumpCheckIn(true); 4644 filter.setCheckInClear(true); 4645 continue; 4646 } 4647 if ("-a".equals(arg) || "--all".equals(arg)) { 4648 filter.setDumpUid(true); 4649 filter.setDumpFiles(true); 4650 continue; 4651 } 4652 if ("-u".equals(arg) || "--uid".equals(arg)) { 4653 filter.setDumpUid(true); 4654 continue; 4655 } 4656 if ("-f".equals(arg) || "--files".equals(arg)) { 4657 filter.setDumpFiles(true); 4658 continue; 4659 } 4660 if ("-n".equals(arg) || "--no-main".equals(arg)) { 4661 filter.setDumpMain(false); 4662 continue; 4663 } 4664 if ("--user".equals(arg)) { 4665 if (argIndex >= args.length) { 4666 throw new IllegalArgumentException("Missing user ID for --user"); 4667 } 4668 try { 4669 filter.addUser(Integer.parseInt(args[argIndex++])); 4670 } catch (NumberFormatException e) { 4671 throw new IllegalArgumentException("Invalid user ID", e); 4672 } 4673 continue; 4674 } 4675 if ("-p".equals(arg) || "--package".equals(arg)) { 4676 if (argIndex >= args.length) { 4677 throw new IllegalArgumentException("Missing package name for --package"); 4678 } 4679 filter.addPackageRegex(args[argIndex++]); 4680 filter.setDumpDetails(false); 4681 continue; 4682 } 4683 if (arg.startsWith("-")) { 4684 throw new IllegalArgumentException("Unknown option " + arg); 4685 } 4686 break; 4687 } 4688 while (argIndex < args.length) { 4689 filter.addPackage(args[argIndex++]); 4690 } 4691 return filter; 4692 } 4693 4694 static class DumpFilter { 4695 private boolean mDumpCheckIn = false; 4696 private boolean mCheckInClear = false; 4697 4698 private boolean mDumpMain = true; 4699 private boolean mDumpUid = false; 4700 private boolean mDumpFiles = false; 4701 4702 private boolean mDumpDetails = true; 4703 private final List<Pattern> mPackagePatterns = new ArrayList<>(); 4704 private final List<Integer> mUsers = new ArrayList<>(); 4705 4706 void addPackageRegex(String regex) { 4707 mPackagePatterns.add(Pattern.compile(regex)); 4708 } 4709 4710 public void addPackage(String packageName) { 4711 addPackageRegex(Pattern.quote(packageName)); 4712 } 4713 4714 void addUser(int userId) { 4715 mUsers.add(userId); 4716 } 4717 4718 boolean isPackageMatch(String packageName) { 4719 if (mPackagePatterns.size() == 0) { 4720 return true; 4721 } 4722 for (int i = 0; i < mPackagePatterns.size(); i++) { 4723 if (mPackagePatterns.get(i).matcher(packageName).find()) { 4724 return true; 4725 } 4726 } 4727 return false; 4728 } 4729 4730 boolean isUserMatch(int userId) { 4731 if (mUsers.size() == 0) { 4732 return true; 4733 } 4734 for (int i = 0; i < mUsers.size(); i++) { 4735 if (mUsers.get(i) == userId) { 4736 return true; 4737 } 4738 } 4739 return false; 4740 } 4741 4742 public boolean shouldDumpCheckIn() { 4743 return mDumpCheckIn; 4744 } 4745 4746 public void setDumpCheckIn(boolean dumpCheckIn) { 4747 mDumpCheckIn = dumpCheckIn; 4748 } 4749 4750 public boolean shouldCheckInClear() { 4751 return mCheckInClear; 4752 } 4753 4754 public void setCheckInClear(boolean checkInClear) { 4755 mCheckInClear = checkInClear; 4756 } 4757 4758 public boolean shouldDumpMain() { 4759 return mDumpMain; 4760 } 4761 4762 public void setDumpMain(boolean dumpMain) { 4763 mDumpMain = dumpMain; 4764 } 4765 4766 public boolean shouldDumpUid() { 4767 return mDumpUid; 4768 } 4769 4770 public void setDumpUid(boolean dumpUid) { 4771 mDumpUid = dumpUid; 4772 } 4773 4774 public boolean shouldDumpFiles() { 4775 return mDumpFiles; 4776 } 4777 4778 public void setDumpFiles(boolean dumpFiles) { 4779 mDumpFiles = dumpFiles; 4780 } 4781 4782 public boolean shouldDumpDetails() { 4783 return mDumpDetails; 4784 } 4785 4786 public void setDumpDetails(boolean dumpDetails) { 4787 mDumpDetails = dumpDetails; 4788 } 4789 } 4790 4791 private void dumpInner(PrintWriter pw) { 4792 dumpInner(pw, new DumpFilter()); 4793 } 4794 4795 private void dumpInner(PrintWriter pw, DumpFilter filter) { 4796 synchronized (mServiceLock) { 4797 if (filter.shouldDumpDetails()) { 4798 final long now = injectCurrentTimeMillis(); 4799 pw.print("Now: ["); 4800 pw.print(now); 4801 pw.print("] "); 4802 pw.print(formatTime(now)); 4803 4804 pw.print(" Raw last reset: ["); 4805 pw.print(mRawLastResetTime.get()); 4806 pw.print("] "); 4807 pw.print(formatTime(mRawLastResetTime.get())); 4808 4809 final long last = getLastResetTimeLocked(); 4810 pw.print(" Last reset: ["); 4811 pw.print(last); 4812 pw.print("] "); 4813 pw.print(formatTime(last)); 4814 4815 final long next = getNextResetTimeLocked(); 4816 pw.print(" Next reset: ["); 4817 pw.print(next); 4818 pw.print("] "); 4819 pw.print(formatTime(next)); 4820 pw.println(); 4821 pw.println(); 4822 4823 pw.print(" Config:"); 4824 pw.print(" Max icon dim: "); 4825 pw.println(mMaxIconDimension); 4826 pw.print(" Icon format: "); 4827 pw.println(mIconPersistFormat); 4828 pw.print(" Icon quality: "); 4829 pw.println(mIconPersistQuality); 4830 pw.print(" saveDelayMillis: "); 4831 pw.println(mSaveDelayMillis); 4832 pw.print(" resetInterval: "); 4833 pw.println(mResetInterval); 4834 pw.print(" maxUpdatesPerInterval: "); 4835 pw.println(mMaxUpdatesPerInterval); 4836 pw.print(" maxShortcutsPerActivity: "); 4837 pw.println(mMaxShortcuts); 4838 pw.println(); 4839 4840 mStatLogger.dump(pw, " "); 4841 4842 synchronized (mWtfLock) { 4843 pw.println(); 4844 pw.print(" #Failures: "); 4845 pw.println(mWtfCount); 4846 4847 if (mLastWtfStacktrace != null) { 4848 pw.print(" Last failure stack trace: "); 4849 pw.println(Log.getStackTraceString(mLastWtfStacktrace)); 4850 } 4851 } 4852 4853 pw.println(); 4854 } 4855 4856 for (int i = 0; i < mUsers.size(); i++) { 4857 final ShortcutUser user = mUsers.valueAt(i); 4858 if (filter.isUserMatch(user.getUserId())) { 4859 user.dump(pw, " ", filter); 4860 pw.println(); 4861 } 4862 } 4863 4864 for (int i = 0; i < mShortcutNonPersistentUsers.size(); i++) { 4865 final ShortcutNonPersistentUser user = mShortcutNonPersistentUsers.valueAt(i); 4866 if (filter.isUserMatch(user.getUserId())) { 4867 user.dump(pw, " ", filter); 4868 pw.println(); 4869 } 4870 } 4871 } 4872 } 4873 4874 private void dumpUid(PrintWriter pw) { 4875 synchronized (mServiceLock) { 4876 pw.println("** SHORTCUT MANAGER UID STATES (dumpsys shortcut -n -u)"); 4877 4878 for (int i = 0; i < mUidState.size(); i++) { 4879 final int uid = mUidState.keyAt(i); 4880 final int state = mUidState.valueAt(i); 4881 pw.print(" UID="); 4882 pw.print(uid); 4883 pw.print(" state="); 4884 pw.print(state); 4885 if (isProcessStateForeground(state)) { 4886 pw.print(" [FG]"); 4887 } 4888 pw.print(" last FG="); 4889 pw.print(mUidLastForegroundElapsedTime.get(uid)); 4890 pw.println(); 4891 } 4892 } 4893 } 4894 4895 static String formatTime(long time) { 4896 return TimeMigrationUtils.formatMillisWithFixedFormat(time); 4897 } 4898 4899 private void dumpCurrentTime(PrintWriter pw) { 4900 pw.print(formatTime(injectCurrentTimeMillis())); 4901 } 4902 4903 /** 4904 * Dumpsys for checkin. 4905 * 4906 * @param clear if true, clear the history information. Some other system services have this 4907 * behavior but shortcut service doesn't for now. 4908 */ 4909 private void dumpCheckin(PrintWriter pw, boolean clear) { 4910 synchronized (mServiceLock) { 4911 try { 4912 final JSONArray users = new JSONArray(); 4913 4914 for (int i = 0; i < mUsers.size(); i++) { 4915 users.put(mUsers.valueAt(i).dumpCheckin(clear)); 4916 } 4917 4918 final JSONObject result = new JSONObject(); 4919 4920 result.put(KEY_SHORTCUT, users); 4921 result.put(KEY_LOW_RAM, injectIsLowRamDevice()); 4922 result.put(KEY_ICON_SIZE, mMaxIconDimension); 4923 4924 pw.println(result.toString(1)); 4925 } catch (JSONException e) { 4926 Slog.e(TAG, "Unable to write in json", e); 4927 } 4928 } 4929 } 4930 4931 private void dumpDumpFiles(PrintWriter pw) { 4932 synchronized (mServiceLock) { 4933 pw.println("** SHORTCUT MANAGER FILES (dumpsys shortcut -n -f)"); 4934 mShortcutDumpFiles.dumpAll(pw); 4935 } 4936 } 4937 4938 // === Shell support === 4939 4940 @Override 4941 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 4942 String[] args, ShellCallback callback, ResultReceiver resultReceiver) { 4943 4944 enforceShell(); 4945 4946 final long token = injectClearCallingIdentity(); 4947 try { 4948 final int status = (new MyShellCommand()).exec(this, in, out, err, args, callback, 4949 resultReceiver); 4950 resultReceiver.send(status, null); 4951 } finally { 4952 injectRestoreCallingIdentity(token); 4953 } 4954 } 4955 4956 static class CommandException extends Exception { 4957 public CommandException(String message) { 4958 super(message); 4959 } 4960 } 4961 4962 /** 4963 * Handle "adb shell cmd". 4964 */ 4965 private class MyShellCommand extends ShellCommand { 4966 4967 private int mUserId = UserHandle.USER_SYSTEM; 4968 4969 private int mShortcutMatchFlags = ShortcutManager.FLAG_MATCH_CACHED 4970 | ShortcutManager.FLAG_MATCH_DYNAMIC | ShortcutManager.FLAG_MATCH_MANIFEST 4971 | ShortcutManager.FLAG_MATCH_PINNED; 4972 4973 private void parseOptionsLocked(boolean takeUser) 4974 throws CommandException { 4975 String opt; 4976 while ((opt = getNextOption()) != null) { 4977 switch (opt) { 4978 case "--user": 4979 if (takeUser) { 4980 mUserId = UserHandle.parseUserArg(getNextArgRequired()); 4981 if (!isUserUnlockedL(mUserId)) { 4982 throw new CommandException( 4983 "User " + mUserId + " is not running or locked"); 4984 } 4985 break; 4986 } 4987 // fallthrough 4988 case "--flags": 4989 mShortcutMatchFlags = Integer.parseInt(getNextArgRequired()); 4990 break; 4991 default: 4992 throw new CommandException("Unknown option: " + opt); 4993 } 4994 } 4995 } 4996 4997 @Override 4998 public int onCommand(String cmd) { 4999 if (cmd == null) { 5000 return handleDefaultCommands(cmd); 5001 } 5002 final PrintWriter pw = getOutPrintWriter(); 5003 try { 5004 switch (cmd) { 5005 case "reset-throttling": 5006 handleResetThrottling(); 5007 break; 5008 case "reset-all-throttling": 5009 handleResetAllThrottling(); 5010 break; 5011 case "override-config": 5012 handleOverrideConfig(); 5013 break; 5014 case "reset-config": 5015 handleResetConfig(); 5016 break; 5017 case "get-default-launcher": 5018 handleGetDefaultLauncher(); 5019 break; 5020 case "unload-user": 5021 handleUnloadUser(); 5022 break; 5023 case "clear-shortcuts": 5024 handleClearShortcuts(); 5025 break; 5026 case "get-shortcuts": 5027 handleGetShortcuts(); 5028 break; 5029 case "verify-states": // hidden command to verify various internal states. 5030 handleVerifyStates(); 5031 break; 5032 case "has-shortcut-access": 5033 handleHasShortcutAccess(); 5034 break; 5035 default: 5036 return handleDefaultCommands(cmd); 5037 } 5038 } catch (CommandException e) { 5039 pw.println("Error: " + e.getMessage()); 5040 return 1; 5041 } 5042 pw.println("Success"); 5043 return 0; 5044 } 5045 5046 @Override 5047 public void onHelp() { 5048 final PrintWriter pw = getOutPrintWriter(); 5049 pw.println("Usage: cmd shortcut COMMAND [options ...]"); 5050 pw.println(); 5051 pw.println("cmd shortcut reset-throttling [--user USER_ID]"); 5052 pw.println(" Reset throttling for all packages and users"); 5053 pw.println(); 5054 pw.println("cmd shortcut reset-all-throttling"); 5055 pw.println(" Reset the throttling state for all users"); 5056 pw.println(); 5057 pw.println("cmd shortcut override-config CONFIG"); 5058 pw.println(" Override the configuration for testing (will last until reboot)"); 5059 pw.println(); 5060 pw.println("cmd shortcut reset-config"); 5061 pw.println(" Reset the configuration set with \"update-config\""); 5062 pw.println(); 5063 pw.println("[Deprecated] cmd shortcut get-default-launcher [--user USER_ID]"); 5064 pw.println(" Show the default launcher"); 5065 pw.println(" Note: This command is deprecated. Callers should query the default" 5066 + " launcher from RoleManager instead."); 5067 pw.println(); 5068 pw.println("cmd shortcut unload-user [--user USER_ID]"); 5069 pw.println(" Unload a user from the memory"); 5070 pw.println(" (This should not affect any observable behavior)"); 5071 pw.println(); 5072 pw.println("cmd shortcut clear-shortcuts [--user USER_ID] PACKAGE"); 5073 pw.println(" Remove all shortcuts from a package, including pinned shortcuts"); 5074 pw.println(); 5075 pw.println("cmd shortcut get-shortcuts [--user USER_ID] [--flags FLAGS] PACKAGE"); 5076 pw.println(" Show the shortcuts for a package that match the given flags"); 5077 pw.println(); 5078 pw.println("cmd shortcut has-shortcut-access [--user USER_ID] PACKAGE"); 5079 pw.println(" Prints \"true\" if the package can access shortcuts," 5080 + " \"false\" otherwise"); 5081 pw.println(); 5082 } 5083 5084 private void handleResetThrottling() throws CommandException { 5085 synchronized (mServiceLock) { 5086 parseOptionsLocked(/* takeUser =*/ true); 5087 5088 Slog.i(TAG, "cmd: handleResetThrottling: user=" + mUserId); 5089 5090 resetThrottlingInner(mUserId); 5091 } 5092 } 5093 5094 private void handleResetAllThrottling() { 5095 Slog.i(TAG, "cmd: handleResetAllThrottling"); 5096 5097 resetAllThrottlingInner(); 5098 } 5099 5100 private void handleOverrideConfig() throws CommandException { 5101 final String config = getNextArgRequired(); 5102 5103 Slog.i(TAG, "cmd: handleOverrideConfig: " + config); 5104 5105 synchronized (mServiceLock) { 5106 if (!updateConfigurationLocked(config)) { 5107 throw new CommandException("override-config failed. See logcat for details."); 5108 } 5109 } 5110 } 5111 5112 private void handleResetConfig() { 5113 Slog.i(TAG, "cmd: handleResetConfig"); 5114 5115 synchronized (mServiceLock) { 5116 loadConfigurationLocked(); 5117 } 5118 } 5119 5120 // This method is used by various cts modules to get the current default launcher. Tests 5121 // should query this information directly from RoleManager instead. Keeping the old behavior 5122 // by returning the result from package manager. 5123 private void handleGetDefaultLauncher() throws CommandException { 5124 synchronized (mServiceLock) { 5125 parseOptionsLocked(/* takeUser =*/ true); 5126 5127 final String defaultLauncher = getDefaultLauncher(mUserId); 5128 if (defaultLauncher == null) { 5129 throw new CommandException( 5130 "Failed to get the default launcher for user " + mUserId); 5131 } 5132 5133 // Get the class name of the component from PM to keep the old behaviour. 5134 final List<ResolveInfo> allHomeCandidates = new ArrayList<>(); 5135 mPackageManagerInternal.getHomeActivitiesAsUser(allHomeCandidates, 5136 getParentOrSelfUserId(mUserId)); 5137 for (ResolveInfo ri : allHomeCandidates) { 5138 final ComponentInfo ci = ri.getComponentInfo(); 5139 if (ci.packageName.equals(defaultLauncher)) { 5140 getOutPrintWriter().println("Launcher: " + ci.getComponentName()); 5141 break; 5142 } 5143 } 5144 } 5145 } 5146 5147 private void handleUnloadUser() throws CommandException { 5148 synchronized (mServiceLock) { 5149 parseOptionsLocked(/* takeUser =*/ true); 5150 5151 Slog.i(TAG, "cmd: handleUnloadUser: user=" + mUserId); 5152 5153 ShortcutService.this.handleStopUser(mUserId); 5154 } 5155 } 5156 5157 private void handleClearShortcuts() throws CommandException { 5158 synchronized (mServiceLock) { 5159 parseOptionsLocked(/* takeUser =*/ true); 5160 final String packageName = getNextArgRequired(); 5161 5162 Slog.i(TAG, "cmd: handleClearShortcuts: user" + mUserId + ", " + packageName); 5163 5164 ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId, 5165 /* appStillExists = */ true); 5166 } 5167 } 5168 5169 private void handleGetShortcuts() throws CommandException { 5170 synchronized (mServiceLock) { 5171 parseOptionsLocked(/* takeUser =*/ true); 5172 final String packageName = getNextArgRequired(); 5173 5174 Slog.i(TAG, "cmd: handleGetShortcuts: user=" + mUserId + ", flags=" 5175 + mShortcutMatchFlags + ", package=" + packageName); 5176 5177 final ShortcutUser user = ShortcutService.this.getUserShortcutsLocked(mUserId); 5178 final ShortcutPackage p = user.getPackageShortcutsIfExists(packageName); 5179 if (p == null) { 5180 return; 5181 } 5182 5183 p.dumpShortcuts(getOutPrintWriter(), mShortcutMatchFlags); 5184 } 5185 } 5186 5187 private void handleVerifyStates() throws CommandException { 5188 try { 5189 verifyStatesForce(); // This will throw when there's an issue. 5190 } catch (Throwable th) { 5191 throw new CommandException(th.getMessage() + "\n" + Log.getStackTraceString(th)); 5192 } 5193 } 5194 5195 private void handleHasShortcutAccess() throws CommandException { 5196 synchronized (mServiceLock) { 5197 parseOptionsLocked(/* takeUser =*/ true); 5198 final String packageName = getNextArgRequired(); 5199 5200 boolean shortcutAccess = hasShortcutHostPermissionInner(packageName, mUserId); 5201 getOutPrintWriter().println(Boolean.toString(shortcutAccess)); 5202 } 5203 } 5204 } 5205 5206 // === Unit test support === 5207 5208 // Injection point. 5209 @VisibleForTesting 5210 long injectCurrentTimeMillis() { 5211 return System.currentTimeMillis(); 5212 } 5213 5214 @VisibleForTesting 5215 long injectElapsedRealtime() { 5216 return SystemClock.elapsedRealtime(); 5217 } 5218 5219 @VisibleForTesting 5220 long injectUptimeMillis() { 5221 return SystemClock.uptimeMillis(); 5222 } 5223 5224 // Injection point. 5225 @VisibleForTesting 5226 int injectBinderCallingUid() { 5227 return getCallingUid(); 5228 } 5229 5230 @VisibleForTesting 5231 int injectBinderCallingPid() { 5232 return getCallingPid(); 5233 } 5234 5235 private int getCallingUserId() { 5236 return UserHandle.getUserId(injectBinderCallingUid()); 5237 } 5238 5239 // Injection point. 5240 long injectClearCallingIdentity() { 5241 return Binder.clearCallingIdentity(); 5242 } 5243 5244 // Injection point. 5245 void injectRestoreCallingIdentity(long token) { 5246 Binder.restoreCallingIdentity(token); 5247 } 5248 5249 // Injection point. 5250 String injectBuildFingerprint() { 5251 return Build.FINGERPRINT; 5252 } 5253 5254 final void wtf(String message) { 5255 wtf(message, /* exception= */ null); 5256 } 5257 5258 // Injection point. 5259 void wtf(String message, Throwable e) { 5260 if (e == null) { 5261 e = new RuntimeException("Stacktrace"); 5262 } 5263 synchronized (mWtfLock) { 5264 mWtfCount++; 5265 mLastWtfStacktrace = new Exception("Last failure was logged here:"); 5266 } 5267 Slog.wtf(TAG, message, e); 5268 } 5269 5270 @VisibleForTesting 5271 File injectSystemDataPath() { 5272 return Environment.getDataSystemDirectory(); 5273 } 5274 5275 @VisibleForTesting 5276 File injectUserDataPath(@UserIdInt int userId) { 5277 return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER); 5278 } 5279 5280 public File getDumpPath() { 5281 return new File(injectUserDataPath(UserHandle.USER_SYSTEM), DIRECTORY_DUMP); 5282 } 5283 5284 @VisibleForTesting 5285 boolean injectIsLowRamDevice() { 5286 return ActivityManager.isLowRamDeviceStatic(); 5287 } 5288 5289 @VisibleForTesting 5290 void injectRegisterUidObserver(IUidObserver observer, int which) { 5291 try { 5292 ActivityManager.getService().registerUidObserver(observer, which, 5293 ActivityManager.PROCESS_STATE_UNKNOWN, null); 5294 } catch (RemoteException shouldntHappen) { 5295 } 5296 } 5297 5298 @VisibleForTesting 5299 void injectRegisterRoleHoldersListener(OnRoleHoldersChangedListener listener) { 5300 mRoleManager.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(), listener, 5301 UserHandle.ALL); 5302 } 5303 5304 @VisibleForTesting 5305 String injectGetHomeRoleHolderAsUser(int userId) { 5306 List<String> roleHolders = mRoleManager.getRoleHoldersAsUser( 5307 RoleManager.ROLE_HOME, UserHandle.of(userId)); 5308 return roleHolders.isEmpty() ? null : roleHolders.get(0); 5309 } 5310 5311 File getUserBitmapFilePath(@UserIdInt int userId) { 5312 return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS); 5313 } 5314 5315 @VisibleForTesting 5316 SparseArray<ShortcutUser> getShortcutsForTest() { 5317 return mUsers; 5318 } 5319 5320 @VisibleForTesting 5321 int getMaxShortcutsForTest() { 5322 return mMaxShortcuts; 5323 } 5324 5325 @VisibleForTesting 5326 int getMaxUpdatesPerIntervalForTest() { 5327 return mMaxUpdatesPerInterval; 5328 } 5329 5330 @VisibleForTesting 5331 long getResetIntervalForTest() { 5332 return mResetInterval; 5333 } 5334 5335 @VisibleForTesting 5336 int getMaxIconDimensionForTest() { 5337 return mMaxIconDimension; 5338 } 5339 5340 @VisibleForTesting 5341 CompressFormat getIconPersistFormatForTest() { 5342 return mIconPersistFormat; 5343 } 5344 5345 @VisibleForTesting 5346 int getIconPersistQualityForTest() { 5347 return mIconPersistQuality; 5348 } 5349 5350 @VisibleForTesting 5351 ShortcutPackage getPackageShortcutForTest(String packageName, int userId) { 5352 synchronized (mServiceLock) { 5353 final ShortcutUser user = mUsers.get(userId); 5354 if (user == null) return null; 5355 5356 return user.getAllPackagesForTest().get(packageName); 5357 } 5358 } 5359 5360 @VisibleForTesting 5361 ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) { 5362 synchronized (mServiceLock) { 5363 final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId); 5364 if (pkg == null) return null; 5365 5366 return pkg.findShortcutById(shortcutId); 5367 } 5368 } 5369 5370 @VisibleForTesting 5371 void updatePackageShortcutForTest(String packageName, String shortcutId, int userId, 5372 Consumer<ShortcutInfo> cb) { 5373 synchronized (mServiceLock) { 5374 final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId); 5375 if (pkg == null) return; 5376 cb.accept(pkg.findShortcutById(shortcutId)); 5377 } 5378 } 5379 5380 @VisibleForTesting 5381 ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) { 5382 synchronized (mServiceLock) { 5383 final ShortcutUser user = mUsers.get(userId); 5384 if (user == null) return null; 5385 5386 return user.getAllLaunchersForTest().get(UserPackage.of(userId, packageName)); 5387 } 5388 } 5389 5390 @VisibleForTesting 5391 ShortcutRequestPinProcessor getShortcutRequestPinProcessorForTest() { 5392 return mShortcutRequestPinProcessor; 5393 } 5394 5395 /** 5396 * Control whether {@link #verifyStates} should be performed. We always perform it during unit 5397 * tests. 5398 */ 5399 @VisibleForTesting 5400 boolean injectShouldPerformVerification() { 5401 return DEBUG; 5402 } 5403 5404 /** 5405 * Check various internal states and throws if there's any inconsistency. 5406 * This is normally only enabled during unit tests. 5407 */ 5408 final void verifyStates() { 5409 if (injectShouldPerformVerification()) { 5410 verifyStatesInner(); 5411 } 5412 } 5413 5414 private final void verifyStatesForce() { 5415 verifyStatesInner(); 5416 } 5417 5418 private void verifyStatesInner() { 5419 synchronized (mServiceLock) { 5420 forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates)); 5421 } 5422 } 5423 5424 @VisibleForTesting 5425 void waitForBitmapSavesForTest() { 5426 synchronized (mServiceLock) { 5427 forEachLoadedUserLocked(u -> 5428 u.forAllPackageItems(ShortcutPackageItem::waitForBitmapSaves)); 5429 } 5430 } 5431 5432 /** 5433 * This helper method does the following 3 tasks: 5434 * 5435 * 1- Combines the |changed| and |updated| shortcut lists, while removing duplicates. 5436 * 2- If a shortcut is deleted and added at once in the same operation, removes it from the 5437 * |removed| list. 5438 * 3- Reloads the final list to get the latest flags. 5439 */ 5440 private List<ShortcutInfo> prepareChangedShortcuts(ArraySet<String> changedIds, 5441 ArraySet<String> newIds, List<ShortcutInfo> deletedList, final ShortcutPackage ps) { 5442 if (ps == null) { 5443 // This can happen when package restore is not finished yet. 5444 return null; 5445 } 5446 if (CollectionUtils.isEmpty(changedIds) && CollectionUtils.isEmpty(newIds)) { 5447 return null; 5448 } 5449 5450 ArraySet<String> resultIds = new ArraySet<>(); 5451 if (!CollectionUtils.isEmpty(changedIds)) { 5452 resultIds.addAll(changedIds); 5453 } 5454 if (!CollectionUtils.isEmpty(newIds)) { 5455 resultIds.addAll(newIds); 5456 } 5457 5458 if (!CollectionUtils.isEmpty(deletedList)) { 5459 deletedList.removeIf((ShortcutInfo si) -> resultIds.contains(si.getId())); 5460 } 5461 5462 List<ShortcutInfo> result = new ArrayList<>(); 5463 ps.findAll(result, (ShortcutInfo si) -> resultIds.contains(si.getId()), 5464 ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); 5465 return result; 5466 } 5467 5468 private List<ShortcutInfo> prepareChangedShortcuts(List<ShortcutInfo> changedList, 5469 List<ShortcutInfo> newList, List<ShortcutInfo> deletedList, final ShortcutPackage ps) { 5470 ArraySet<String> changedIds = new ArraySet<>(); 5471 addShortcutIdsToSet(changedIds, changedList); 5472 5473 ArraySet<String> newIds = new ArraySet<>(); 5474 addShortcutIdsToSet(newIds, newList); 5475 5476 return prepareChangedShortcuts(changedIds, newIds, deletedList, ps); 5477 } 5478 5479 private void addShortcutIdsToSet(ArraySet<String> ids, List<ShortcutInfo> shortcuts) { 5480 if (CollectionUtils.isEmpty(shortcuts)) { 5481 return; 5482 } 5483 final int size = shortcuts.size(); 5484 for (int i = 0; i < size; i++) { 5485 ids.add(shortcuts.get(i).getId()); 5486 } 5487 } 5488 } 5489