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