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