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