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