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