ShortcutService.java revision c51b2876ec5c0af449469a0f76bb38c51cfcff04
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.NonNull; 19import android.annotation.Nullable; 20import android.annotation.UserIdInt; 21import android.app.ActivityManager; 22import android.app.ActivityManagerNative; 23import android.app.AppGlobals; 24import android.app.IUidObserver; 25import android.content.ComponentName; 26import android.content.Context; 27import android.content.Intent; 28import android.content.pm.ApplicationInfo; 29import android.content.pm.IPackageManager; 30import android.content.pm.IShortcutService; 31import android.content.pm.LauncherApps; 32import android.content.pm.LauncherApps.ShortcutQuery; 33import android.content.pm.PackageInfo; 34import android.content.pm.PackageManager; 35import android.content.pm.PackageManagerInternal; 36import android.content.pm.ParceledListSlice; 37import android.content.pm.ResolveInfo; 38import android.content.pm.ShortcutInfo; 39import android.content.pm.ShortcutServiceInternal; 40import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; 41import android.graphics.Bitmap; 42import android.graphics.Bitmap.CompressFormat; 43import android.graphics.Canvas; 44import android.graphics.RectF; 45import android.graphics.drawable.Icon; 46import android.os.Binder; 47import android.os.Environment; 48import android.os.FileUtils; 49import android.os.Handler; 50import android.os.Looper; 51import android.os.ParcelFileDescriptor; 52import android.os.PersistableBundle; 53import android.os.Process; 54import android.os.RemoteException; 55import android.os.ResultReceiver; 56import android.os.SELinux; 57import android.os.ShellCommand; 58import android.os.SystemClock; 59import android.os.UserHandle; 60import android.os.UserManager; 61import android.text.TextUtils; 62import android.text.format.Time; 63import android.util.ArraySet; 64import android.util.AtomicFile; 65import android.util.KeyValueListParser; 66import android.util.Slog; 67import android.util.SparseArray; 68import android.util.SparseIntArray; 69import android.util.SparseLongArray; 70import android.util.TypedValue; 71import android.util.Xml; 72 73import com.android.internal.annotations.GuardedBy; 74import com.android.internal.annotations.VisibleForTesting; 75import com.android.internal.content.PackageMonitor; 76import com.android.internal.os.BackgroundThread; 77import com.android.internal.util.FastXmlSerializer; 78import com.android.internal.util.Preconditions; 79import com.android.server.LocalServices; 80import com.android.server.SystemService; 81import com.android.server.pm.ShortcutUser.PackageWithUser; 82 83import libcore.io.IoUtils; 84 85import org.xmlpull.v1.XmlPullParser; 86import org.xmlpull.v1.XmlPullParserException; 87import org.xmlpull.v1.XmlSerializer; 88 89import java.io.BufferedInputStream; 90import java.io.BufferedOutputStream; 91import java.io.ByteArrayInputStream; 92import java.io.ByteArrayOutputStream; 93import java.io.File; 94import java.io.FileDescriptor; 95import java.io.FileInputStream; 96import java.io.FileNotFoundException; 97import java.io.FileOutputStream; 98import java.io.IOException; 99import java.io.InputStream; 100import java.io.OutputStream; 101import java.io.PrintWriter; 102import java.net.URISyntaxException; 103import java.nio.charset.StandardCharsets; 104import java.util.ArrayList; 105import java.util.List; 106import java.util.concurrent.atomic.AtomicBoolean; 107import java.util.concurrent.atomic.AtomicLong; 108import java.util.function.Consumer; 109import java.util.function.Predicate; 110 111/** 112 * TODO: 113 * 114 * - Default launcher check does take a few ms. Worth caching. 115 * 116 * - Clear data -> remove all dynamic? but not the pinned? 117 * 118 * - Scan and remove orphan bitmaps (just in case). 119 * 120 * - Detect when already registered instances are passed to APIs again, which might break 121 * internal bitmap handling. 122 * 123 * - Add more call stats. 124 */ 125public class ShortcutService extends IShortcutService.Stub { 126 static final String TAG = "ShortcutService"; 127 128 static final boolean DEBUG = false; // STOPSHIP if true 129 static final boolean DEBUG_LOAD = false; // STOPSHIP if true 130 static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true 131 132 @VisibleForTesting 133 static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day 134 135 @VisibleForTesting 136 static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10; 137 138 @VisibleForTesting 139 static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5; 140 141 @VisibleForTesting 142 static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96; 143 144 @VisibleForTesting 145 static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48; 146 147 @VisibleForTesting 148 static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name(); 149 150 @VisibleForTesting 151 static final int DEFAULT_ICON_PERSIST_QUALITY = 100; 152 153 @VisibleForTesting 154 static final int DEFAULT_SAVE_DELAY_MS = 3000; 155 156 @VisibleForTesting 157 static final String FILENAME_BASE_STATE = "shortcut_service.xml"; 158 159 @VisibleForTesting 160 static final String DIRECTORY_PER_USER = "shortcut_service"; 161 162 @VisibleForTesting 163 static final String FILENAME_USER_PACKAGES = "shortcuts.xml"; 164 165 static final String DIRECTORY_BITMAPS = "bitmaps"; 166 167 private static final String TAG_ROOT = "root"; 168 private static final String TAG_LAST_RESET_TIME = "last_reset_time"; 169 private static final String TAG_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale_seq_no"; 170 171 private static final String ATTR_VALUE = "value"; 172 173 @VisibleForTesting 174 interface ConfigConstants { 175 /** 176 * Key name for the save delay, in milliseconds. (int) 177 */ 178 String KEY_SAVE_DELAY_MILLIS = "save_delay_ms"; 179 180 /** 181 * Key name for the throttling reset interval, in seconds. (long) 182 */ 183 String KEY_RESET_INTERVAL_SEC = "reset_interval_sec"; 184 185 /** 186 * Key name for the max number of modifying API calls per app for every interval. (int) 187 */ 188 String KEY_MAX_UPDATES_PER_INTERVAL = "max_updates_per_interval"; 189 190 /** 191 * Key name for the max icon dimensions in DP, for non-low-memory devices. 192 */ 193 String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp"; 194 195 /** 196 * Key name for the max icon dimensions in DP, for low-memory devices. 197 */ 198 String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram"; 199 200 /** 201 * Key name for the max dynamic shortcuts per app. (int) 202 */ 203 String KEY_MAX_SHORTCUTS = "max_shortcuts"; 204 205 /** 206 * Key name for icon compression quality, 0-100. 207 */ 208 String KEY_ICON_QUALITY = "icon_quality"; 209 210 /** 211 * Key name for icon compression format: "PNG", "JPEG" or "WEBP" 212 */ 213 String KEY_ICON_FORMAT = "icon_format"; 214 } 215 216 final Context mContext; 217 218 private final Object mLock = new Object(); 219 220 private final Handler mHandler; 221 222 @GuardedBy("mLock") 223 private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1); 224 225 @GuardedBy("mLock") 226 private long mRawLastResetTime; 227 228 /** 229 * User ID -> UserShortcuts 230 */ 231 @GuardedBy("mLock") 232 private final SparseArray<ShortcutUser> mUsers = new SparseArray<>(); 233 234 /** 235 * Max number of dynamic shortcuts that each application can have at a time. 236 */ 237 private int mMaxDynamicShortcuts; 238 239 /** 240 * Max number of updating API calls that each application can make during the interval. 241 */ 242 int mMaxUpdatesPerInterval; 243 244 /** 245 * Actual throttling-reset interval. By default it's a day. 246 */ 247 private long mResetInterval; 248 249 /** 250 * Icon max width/height in pixels. 251 */ 252 private int mMaxIconDimension; 253 254 private CompressFormat mIconPersistFormat; 255 private int mIconPersistQuality; 256 257 private int mSaveDelayMillis; 258 259 private final IPackageManager mIPackageManager; 260 private final PackageManagerInternal mPackageManagerInternal; 261 private final UserManager mUserManager; 262 263 @GuardedBy("mLock") 264 final SparseIntArray mUidState = new SparseIntArray(); 265 266 @GuardedBy("mLock") 267 final SparseLongArray mUidLastForegroundElapsedTime = new SparseLongArray(); 268 269 @GuardedBy("mLock") 270 private List<Integer> mDirtyUserIds = new ArrayList<>(); 271 272 /** 273 * A counter that increments every time the system locale changes. We keep track of it to reset 274 * throttling counters on the first call from each package after the last locale change. 275 * 276 * We need this mechanism because we can't do much in the locale change callback, which is 277 * {@link ShortcutServiceInternal#onSystemLocaleChangedNoLock()}. 278 */ 279 private final AtomicLong mLocaleChangeSequenceNumber = new AtomicLong(); 280 281 private final AtomicBoolean mBootCompleted = new AtomicBoolean(); 282 283 private static final int PACKAGE_MATCH_FLAGS = 284 PackageManager.MATCH_DIRECT_BOOT_AWARE 285 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 286 | PackageManager.MATCH_UNINSTALLED_PACKAGES; 287 288 // Stats 289 @VisibleForTesting 290 interface Stats { 291 int GET_DEFAULT_HOME = 0; 292 int GET_PACKAGE_INFO = 1; 293 int GET_PACKAGE_INFO_WITH_SIG = 2; 294 int GET_APPLICATION_INFO = 3; 295 int LAUNCHER_PERMISSION_CHECK = 4; 296 int CLEANUP_DANGLING_BITMAPS = 5; 297 298 int COUNT = CLEANUP_DANGLING_BITMAPS + 1; 299 } 300 301 final Object mStatLock = new Object(); 302 303 @GuardedBy("mStatLock") 304 private final int[] mCountStats = new int[Stats.COUNT]; 305 306 @GuardedBy("mStatLock") 307 private final long[] mDurationStats = new long[Stats.COUNT]; 308 309 private static final int PROCESS_STATE_FOREGROUND_THRESHOLD = 310 ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; 311 312 public ShortcutService(Context context) { 313 this(context, BackgroundThread.get().getLooper()); 314 } 315 316 @VisibleForTesting 317 ShortcutService(Context context, Looper looper) { 318 mContext = Preconditions.checkNotNull(context); 319 LocalServices.addService(ShortcutServiceInternal.class, new LocalService()); 320 mHandler = new Handler(looper); 321 mIPackageManager = AppGlobals.getPackageManager(); 322 mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); 323 mUserManager = context.getSystemService(UserManager.class); 324 325 mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false); 326 327 injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE 328 | ActivityManager.UID_OBSERVER_GONE); 329 } 330 331 void logDurationStat(int statId, long start) { 332 synchronized (mStatLock) { 333 mCountStats[statId]++; 334 mDurationStats[statId] += (injectElapsedRealtime() - start); 335 } 336 } 337 338 public long getLocaleChangeSequenceNumber() { 339 return mLocaleChangeSequenceNumber.get(); 340 } 341 342 final private IUidObserver mUidObserver = new IUidObserver.Stub() { 343 @Override public void onUidStateChanged(int uid, int procState) throws RemoteException { 344 handleOnUidStateChanged(uid, procState); 345 } 346 347 @Override public void onUidGone(int uid) throws RemoteException { 348 handleOnUidStateChanged(uid, ActivityManager.MAX_PROCESS_STATE); 349 } 350 351 @Override public void onUidActive(int uid) throws RemoteException { 352 } 353 354 @Override public void onUidIdle(int uid) throws RemoteException { 355 } 356 }; 357 358 void handleOnUidStateChanged(int uid, int procState) { 359 if (DEBUG_PROCSTATE) { 360 Slog.d(TAG, "onUidStateChanged: uid=" + uid + " state=" + procState); 361 } 362 synchronized (mLock) { 363 mUidState.put(uid, procState); 364 365 // We need to keep track of last time an app comes to foreground. 366 // See ShortcutPackage.getApiCallCount() for how it's used. 367 // It doesn't have to be persisted, but it needs to be the elapsed time. 368 if (isProcessStateForeground(procState)) { 369 mUidLastForegroundElapsedTime.put(uid, injectElapsedRealtime()); 370 } 371 } 372 } 373 374 private boolean isProcessStateForeground(int processState) { 375 return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD; 376 } 377 378 boolean isUidForegroundLocked(int uid) { 379 if (uid == Process.SYSTEM_UID) { 380 // IUidObserver doesn't report the state of SYSTEM, but it always has bound services, 381 // so it's foreground anyway. 382 return true; 383 } 384 return isProcessStateForeground(mUidState.get(uid, ActivityManager.MAX_PROCESS_STATE)); 385 } 386 387 long getUidLastForegroundElapsedTimeLocked(int uid) { 388 return mUidLastForegroundElapsedTime.get(uid); 389 } 390 391 /** 392 * System service lifecycle. 393 */ 394 public static final class Lifecycle extends SystemService { 395 final ShortcutService mService; 396 397 public Lifecycle(Context context) { 398 super(context); 399 mService = new ShortcutService(context); 400 } 401 402 @Override 403 public void onStart() { 404 publishBinderService(Context.SHORTCUT_SERVICE, mService); 405 } 406 407 @Override 408 public void onBootPhase(int phase) { 409 mService.onBootPhase(phase); 410 } 411 412 @Override 413 public void onCleanupUser(int userHandle) { 414 mService.handleCleanupUser(userHandle); 415 } 416 417 @Override 418 public void onUnlockUser(int userId) { 419 mService.handleUnlockUser(userId); 420 } 421 } 422 423 /** lifecycle event */ 424 void onBootPhase(int phase) { 425 if (DEBUG) { 426 Slog.d(TAG, "onBootPhase: " + phase); 427 } 428 switch (phase) { 429 case SystemService.PHASE_LOCK_SETTINGS_READY: 430 initialize(); 431 break; 432 case SystemService.PHASE_BOOT_COMPLETED: 433 mBootCompleted.set(true); 434 break; 435 } 436 } 437 438 /** lifecycle event */ 439 void handleUnlockUser(int userId) { 440 synchronized (mLock) { 441 // Preload 442 getUserShortcutsLocked(userId); 443 444 checkPackageChanges(userId); 445 } 446 } 447 448 /** lifecycle event */ 449 void handleCleanupUser(int userId) { 450 synchronized (mLock) { 451 unloadUserLocked(userId); 452 } 453 } 454 455 private void unloadUserLocked(int userId) { 456 if (DEBUG) { 457 Slog.d(TAG, "unloadUserLocked: user=" + userId); 458 } 459 // Save all dirty information. 460 saveDirtyInfo(); 461 462 // Unload 463 mUsers.delete(userId); 464 } 465 466 /** Return the base state file name */ 467 private AtomicFile getBaseStateFile() { 468 final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE); 469 path.mkdirs(); 470 return new AtomicFile(path); 471 } 472 473 /** 474 * Init the instance. (load the state file, etc) 475 */ 476 private void initialize() { 477 synchronized (mLock) { 478 loadConfigurationLocked(); 479 loadBaseStateLocked(); 480 } 481 } 482 483 /** 484 * Load the configuration from Settings. 485 */ 486 private void loadConfigurationLocked() { 487 updateConfigurationLocked(injectShortcutManagerConstants()); 488 } 489 490 /** 491 * Load the configuration from Settings. 492 */ 493 @VisibleForTesting 494 boolean updateConfigurationLocked(String config) { 495 boolean result = true; 496 497 final KeyValueListParser parser = new KeyValueListParser(','); 498 try { 499 parser.setString(config); 500 } catch (IllegalArgumentException e) { 501 // Failed to parse the settings string, log this and move on 502 // with defaults. 503 Slog.e(TAG, "Bad shortcut manager settings", e); 504 result = false; 505 } 506 507 mSaveDelayMillis = Math.max(0, (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS, 508 DEFAULT_SAVE_DELAY_MS)); 509 510 mResetInterval = Math.max(1, parser.getLong( 511 ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC) 512 * 1000L); 513 514 mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong( 515 ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL)); 516 517 mMaxDynamicShortcuts = Math.max(0, (int) parser.getLong( 518 ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP)); 519 520 final int iconDimensionDp = Math.max(1, injectIsLowRamDevice() 521 ? (int) parser.getLong( 522 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM, 523 DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP) 524 : (int) parser.getLong( 525 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP, 526 DEFAULT_MAX_ICON_DIMENSION_DP)); 527 528 mMaxIconDimension = injectDipToPixel(iconDimensionDp); 529 530 mIconPersistFormat = CompressFormat.valueOf( 531 parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT)); 532 533 mIconPersistQuality = (int) parser.getLong( 534 ConfigConstants.KEY_ICON_QUALITY, 535 DEFAULT_ICON_PERSIST_QUALITY); 536 537 return result; 538 } 539 540 @VisibleForTesting 541 String injectShortcutManagerConstants() { 542 return android.provider.Settings.Global.getString( 543 mContext.getContentResolver(), 544 android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS); 545 } 546 547 @VisibleForTesting 548 int injectDipToPixel(int dip) { 549 return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, 550 mContext.getResources().getDisplayMetrics()); 551 } 552 553 // === Persisting === 554 555 @Nullable 556 static String parseStringAttribute(XmlPullParser parser, String attribute) { 557 return parser.getAttributeValue(null, attribute); 558 } 559 560 static boolean parseBooleanAttribute(XmlPullParser parser, String attribute) { 561 return parseLongAttribute(parser, attribute) == 1; 562 } 563 564 static int parseIntAttribute(XmlPullParser parser, String attribute) { 565 return (int) parseLongAttribute(parser, attribute); 566 } 567 568 static int parseIntAttribute(XmlPullParser parser, String attribute, int def) { 569 return (int) parseLongAttribute(parser, attribute, def); 570 } 571 572 static long parseLongAttribute(XmlPullParser parser, String attribute) { 573 return parseLongAttribute(parser, attribute, 0); 574 } 575 576 static long parseLongAttribute(XmlPullParser parser, String attribute, long def) { 577 final String value = parseStringAttribute(parser, attribute); 578 if (TextUtils.isEmpty(value)) { 579 return def; 580 } 581 try { 582 return Long.parseLong(value); 583 } catch (NumberFormatException e) { 584 Slog.e(TAG, "Error parsing long " + value); 585 return def; 586 } 587 } 588 589 @Nullable 590 static ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) { 591 final String value = parseStringAttribute(parser, attribute); 592 if (TextUtils.isEmpty(value)) { 593 return null; 594 } 595 return ComponentName.unflattenFromString(value); 596 } 597 598 @Nullable 599 static Intent parseIntentAttribute(XmlPullParser parser, String attribute) { 600 final String value = parseStringAttribute(parser, attribute); 601 if (TextUtils.isEmpty(value)) { 602 return null; 603 } 604 try { 605 return Intent.parseUri(value, /* flags =*/ 0); 606 } catch (URISyntaxException e) { 607 Slog.e(TAG, "Error parsing intent", e); 608 return null; 609 } 610 } 611 612 static void writeTagValue(XmlSerializer out, String tag, String value) throws IOException { 613 if (TextUtils.isEmpty(value)) return; 614 615 out.startTag(null, tag); 616 out.attribute(null, ATTR_VALUE, value); 617 out.endTag(null, tag); 618 } 619 620 static void writeTagValue(XmlSerializer out, String tag, long value) throws IOException { 621 writeTagValue(out, tag, Long.toString(value)); 622 } 623 624 static void writeTagValue(XmlSerializer out, String tag, ComponentName name) throws IOException { 625 if (name == null) return; 626 writeTagValue(out, tag, name.flattenToString()); 627 } 628 629 static void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle) 630 throws IOException, XmlPullParserException { 631 if (bundle == null) return; 632 633 out.startTag(null, tag); 634 bundle.saveToXml(out); 635 out.endTag(null, tag); 636 } 637 638 static void writeAttr(XmlSerializer out, String name, String value) throws IOException { 639 if (TextUtils.isEmpty(value)) return; 640 641 out.attribute(null, name, value); 642 } 643 644 static void writeAttr(XmlSerializer out, String name, long value) throws IOException { 645 writeAttr(out, name, String.valueOf(value)); 646 } 647 648 static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException { 649 if (value) { 650 writeAttr(out, name, "1"); 651 } 652 } 653 654 static void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException { 655 if (comp == null) return; 656 writeAttr(out, name, comp.flattenToString()); 657 } 658 659 static void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException { 660 if (intent == null) return; 661 662 writeAttr(out, name, intent.toUri(/* flags =*/ 0)); 663 } 664 665 @VisibleForTesting 666 void saveBaseStateLocked() { 667 final AtomicFile file = getBaseStateFile(); 668 if (DEBUG) { 669 Slog.d(TAG, "Saving to " + file.getBaseFile()); 670 } 671 672 FileOutputStream outs = null; 673 try { 674 outs = file.startWrite(); 675 676 // Write to XML 677 XmlSerializer out = new FastXmlSerializer(); 678 out.setOutput(outs, StandardCharsets.UTF_8.name()); 679 out.startDocument(null, true); 680 out.startTag(null, TAG_ROOT); 681 682 // Body. 683 writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime); 684 writeTagValue(out, TAG_LOCALE_CHANGE_SEQUENCE_NUMBER, 685 mLocaleChangeSequenceNumber.get()); 686 687 // Epilogue. 688 out.endTag(null, TAG_ROOT); 689 out.endDocument(); 690 691 // Close. 692 file.finishWrite(outs); 693 } catch (IOException e) { 694 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); 695 file.failWrite(outs); 696 } 697 } 698 699 private void loadBaseStateLocked() { 700 mRawLastResetTime = 0; 701 702 final AtomicFile file = getBaseStateFile(); 703 if (DEBUG) { 704 Slog.d(TAG, "Loading from " + file.getBaseFile()); 705 } 706 try (FileInputStream in = file.openRead()) { 707 XmlPullParser parser = Xml.newPullParser(); 708 parser.setInput(in, StandardCharsets.UTF_8.name()); 709 710 int type; 711 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 712 if (type != XmlPullParser.START_TAG) { 713 continue; 714 } 715 final int depth = parser.getDepth(); 716 // Check the root tag 717 final String tag = parser.getName(); 718 if (depth == 1) { 719 if (!TAG_ROOT.equals(tag)) { 720 Slog.e(TAG, "Invalid root tag: " + tag); 721 return; 722 } 723 continue; 724 } 725 // Assume depth == 2 726 switch (tag) { 727 case TAG_LAST_RESET_TIME: 728 mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE); 729 break; 730 case TAG_LOCALE_CHANGE_SEQUENCE_NUMBER: 731 mLocaleChangeSequenceNumber.set(parseLongAttribute(parser, ATTR_VALUE)); 732 break; 733 default: 734 Slog.e(TAG, "Invalid tag: " + tag); 735 break; 736 } 737 } 738 } catch (FileNotFoundException e) { 739 // Use the default 740 } catch (IOException|XmlPullParserException e) { 741 Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); 742 743 mRawLastResetTime = 0; 744 } 745 // Adjust the last reset time. 746 getLastResetTimeLocked(); 747 } 748 749 private void saveUserLocked(@UserIdInt int userId) { 750 final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); 751 if (DEBUG) { 752 Slog.d(TAG, "Saving to " + path); 753 } 754 path.mkdirs(); 755 final AtomicFile file = new AtomicFile(path); 756 FileOutputStream os = null; 757 try { 758 os = file.startWrite(); 759 760 saveUserInternalLocked(userId, os, /* forBackup= */ false); 761 762 file.finishWrite(os); 763 } catch (XmlPullParserException|IOException e) { 764 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); 765 file.failWrite(os); 766 } 767 } 768 769 private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os, 770 boolean forBackup) throws IOException, XmlPullParserException { 771 772 final BufferedOutputStream bos = new BufferedOutputStream(os); 773 774 // Write to XML 775 XmlSerializer out = new FastXmlSerializer(); 776 out.setOutput(bos, StandardCharsets.UTF_8.name()); 777 out.startDocument(null, true); 778 779 getUserShortcutsLocked(userId).saveToXml(out, forBackup); 780 781 out.endDocument(); 782 783 bos.flush(); 784 os.flush(); 785 } 786 787 static IOException throwForInvalidTag(int depth, String tag) throws IOException { 788 throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth)); 789 } 790 791 static void warnForInvalidTag(int depth, String tag) throws IOException { 792 Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth)); 793 } 794 795 @Nullable 796 private ShortcutUser loadUserLocked(@UserIdInt int userId) { 797 final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); 798 if (DEBUG) { 799 Slog.d(TAG, "Loading from " + path); 800 } 801 final AtomicFile file = new AtomicFile(path); 802 803 final FileInputStream in; 804 try { 805 in = file.openRead(); 806 } catch (FileNotFoundException e) { 807 if (DEBUG) { 808 Slog.d(TAG, "Not found " + path); 809 } 810 return null; 811 } 812 try { 813 final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false); 814 cleanupDanglingBitmapDirectoriesLocked(userId, ret); 815 return ret; 816 } catch (IOException|XmlPullParserException e) { 817 Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); 818 return null; 819 } finally { 820 IoUtils.closeQuietly(in); 821 } 822 } 823 824 private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, 825 boolean fromBackup) throws XmlPullParserException, IOException { 826 827 final BufferedInputStream bis = new BufferedInputStream(is); 828 829 ShortcutUser ret = null; 830 XmlPullParser parser = Xml.newPullParser(); 831 parser.setInput(bis, StandardCharsets.UTF_8.name()); 832 833 int type; 834 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 835 if (type != XmlPullParser.START_TAG) { 836 continue; 837 } 838 final int depth = parser.getDepth(); 839 840 final String tag = parser.getName(); 841 if (DEBUG_LOAD) { 842 Slog.d(TAG, String.format("depth=%d type=%d name=%s", 843 depth, type, tag)); 844 } 845 if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) { 846 ret = ShortcutUser.loadFromXml(this, parser, userId, fromBackup); 847 continue; 848 } 849 throwForInvalidTag(depth, tag); 850 } 851 return ret; 852 } 853 854 private void scheduleSaveBaseState() { 855 scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state. 856 } 857 858 void scheduleSaveUser(@UserIdInt int userId) { 859 scheduleSaveInner(userId); 860 } 861 862 // In order to re-schedule, we need to reuse the same instance, so keep it in final. 863 private final Runnable mSaveDirtyInfoRunner = this::saveDirtyInfo; 864 865 private void scheduleSaveInner(@UserIdInt int userId) { 866 if (DEBUG) { 867 Slog.d(TAG, "Scheduling to save for " + userId); 868 } 869 synchronized (mLock) { 870 if (!mDirtyUserIds.contains(userId)) { 871 mDirtyUserIds.add(userId); 872 } 873 } 874 // If already scheduled, remove that and re-schedule in N seconds. 875 mHandler.removeCallbacks(mSaveDirtyInfoRunner); 876 mHandler.postDelayed(mSaveDirtyInfoRunner, mSaveDelayMillis); 877 } 878 879 @VisibleForTesting 880 void saveDirtyInfo() { 881 if (DEBUG) { 882 Slog.d(TAG, "saveDirtyInfo"); 883 } 884 synchronized (mLock) { 885 for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) { 886 final int userId = mDirtyUserIds.get(i); 887 if (userId == UserHandle.USER_NULL) { // USER_NULL for base state. 888 saveBaseStateLocked(); 889 } else { 890 saveUserLocked(userId); 891 } 892 } 893 mDirtyUserIds.clear(); 894 } 895 } 896 897 /** Return the last reset time. */ 898 long getLastResetTimeLocked() { 899 updateTimesLocked(); 900 return mRawLastResetTime; 901 } 902 903 /** Return the next reset time. */ 904 long getNextResetTimeLocked() { 905 updateTimesLocked(); 906 return mRawLastResetTime + mResetInterval; 907 } 908 909 static boolean isClockValid(long time) { 910 return time >= 1420070400; // Thu, 01 Jan 2015 00:00:00 GMT 911 } 912 913 /** 914 * Update the last reset time. 915 */ 916 private void updateTimesLocked() { 917 918 final long now = injectCurrentTimeMillis(); 919 920 final long prevLastResetTime = mRawLastResetTime; 921 922 if (mRawLastResetTime == 0) { // first launch. 923 // TODO Randomize?? 924 mRawLastResetTime = now; 925 } else if (now < mRawLastResetTime) { 926 // Clock rewound. 927 if (isClockValid(now)) { 928 Slog.w(TAG, "Clock rewound"); 929 // TODO Randomize?? 930 mRawLastResetTime = now; 931 } 932 } else { 933 if ((mRawLastResetTime + mResetInterval) <= now) { 934 final long offset = mRawLastResetTime % mResetInterval; 935 mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset; 936 } 937 } 938 if (prevLastResetTime != mRawLastResetTime) { 939 scheduleSaveBaseState(); 940 } 941 } 942 943 @GuardedBy("mLock") 944 @NonNull 945 private boolean isUserLoadedLocked(@UserIdInt int userId) { 946 return mUsers.get(userId) != null; 947 } 948 949 /** Return the per-user state. */ 950 @GuardedBy("mLock") 951 @NonNull 952 ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) { 953 ShortcutUser userPackages = mUsers.get(userId); 954 if (userPackages == null) { 955 userPackages = loadUserLocked(userId); 956 if (userPackages == null) { 957 userPackages = new ShortcutUser(this, userId); 958 } 959 mUsers.put(userId, userPackages); 960 } 961 return userPackages; 962 } 963 964 void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) { 965 for (int i = mUsers.size() - 1; i >= 0; i--) { 966 c.accept(mUsers.valueAt(i)); 967 } 968 } 969 970 /** Return the per-user per-package state. */ 971 @GuardedBy("mLock") 972 @NonNull 973 ShortcutPackage getPackageShortcutsLocked( 974 @NonNull String packageName, @UserIdInt int userId) { 975 return getUserShortcutsLocked(userId).getPackageShortcuts(packageName); 976 } 977 978 @GuardedBy("mLock") 979 @NonNull 980 ShortcutLauncher getLauncherShortcutsLocked( 981 @NonNull String packageName, @UserIdInt int ownerUserId, 982 @UserIdInt int launcherUserId) { 983 return getUserShortcutsLocked(ownerUserId) 984 .getLauncherShortcuts(packageName, launcherUserId); 985 } 986 987 // === Caller validation === 988 989 void removeIcon(@UserIdInt int userId, ShortcutInfo shortcut) { 990 if (shortcut.getBitmapPath() != null) { 991 if (DEBUG) { 992 Slog.d(TAG, "Removing " + shortcut.getBitmapPath()); 993 } 994 new File(shortcut.getBitmapPath()).delete(); 995 996 shortcut.setBitmapPath(null); 997 shortcut.setIconResourceId(0); 998 shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES); 999 } 1000 } 1001 1002 public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) { 1003 final File packagePath = new File(getUserBitmapFilePath(userId), packageName); 1004 if (!packagePath.isDirectory()) { 1005 return; 1006 } 1007 if (!(FileUtils.deleteContents(packagePath) && packagePath.delete())) { 1008 Slog.w(TAG, "Unable to remove directory " + packagePath); 1009 } 1010 } 1011 1012 private void cleanupDanglingBitmapDirectoriesLocked( 1013 @UserIdInt int userId, @NonNull ShortcutUser user) { 1014 if (DEBUG) { 1015 Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId); 1016 } 1017 final long start = injectElapsedRealtime(); 1018 1019 final File bitmapDir = getUserBitmapFilePath(userId); 1020 final File[] children = bitmapDir.listFiles(); 1021 if (children == null) { 1022 return; 1023 } 1024 for (File child : children) { 1025 if (!child.isDirectory()) { 1026 continue; 1027 } 1028 final String packageName = child.getName(); 1029 if (DEBUG) { 1030 Slog.d(TAG, "cleanupDanglingBitmaps: Found directory=" + packageName); 1031 } 1032 if (!user.hasPackage(packageName)) { 1033 if (DEBUG) { 1034 Slog.d(TAG, "Removing dangling bitmap directory: " + packageName); 1035 } 1036 cleanupBitmapsForPackage(userId, packageName); 1037 } else { 1038 cleanupDanglingBitmapFilesLocked(userId, user, packageName, child); 1039 } 1040 } 1041 logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start); 1042 } 1043 1044 private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user, 1045 @NonNull String packageName, @NonNull File path) { 1046 final ArraySet<String> usedFiles = 1047 user.getPackageShortcuts(packageName).getUsedBitmapFiles(); 1048 1049 for (File child : path.listFiles()) { 1050 if (!child.isFile()) { 1051 continue; 1052 } 1053 final String name = child.getName(); 1054 if (!usedFiles.contains(name)) { 1055 if (DEBUG) { 1056 Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath()); 1057 } 1058 child.delete(); 1059 } 1060 } 1061 } 1062 1063 @VisibleForTesting 1064 static class FileOutputStreamWithPath extends FileOutputStream { 1065 private final File mFile; 1066 1067 public FileOutputStreamWithPath(File file) throws FileNotFoundException { 1068 super(file); 1069 mFile = file; 1070 } 1071 1072 public File getFile() { 1073 return mFile; 1074 } 1075 } 1076 1077 /** 1078 * Build the cached bitmap filename for a shortcut icon. 1079 * 1080 * The filename will be based on the ID, except certain characters will be escaped. 1081 */ 1082 @VisibleForTesting 1083 FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut) 1084 throws IOException { 1085 final File packagePath = new File(getUserBitmapFilePath(userId), 1086 shortcut.getPackageName()); 1087 if (!packagePath.isDirectory()) { 1088 packagePath.mkdirs(); 1089 if (!packagePath.isDirectory()) { 1090 throw new IOException("Unable to create directory " + packagePath); 1091 } 1092 SELinux.restorecon(packagePath); 1093 } 1094 1095 final String baseName = String.valueOf(injectCurrentTimeMillis()); 1096 for (int suffix = 0;; suffix++) { 1097 final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png"; 1098 final File file = new File(packagePath, filename); 1099 if (!file.exists()) { 1100 if (DEBUG) { 1101 Slog.d(TAG, "Saving icon to " + file.getAbsolutePath()); 1102 } 1103 return new FileOutputStreamWithPath(file); 1104 } 1105 } 1106 } 1107 1108 void saveIconAndFixUpShortcut(@UserIdInt int userId, ShortcutInfo shortcut) { 1109 if (shortcut.hasIconFile() || shortcut.hasIconResource()) { 1110 return; 1111 } 1112 1113 final long token = injectClearCallingIdentity(); 1114 try { 1115 // Clear icon info on the shortcut. 1116 shortcut.setIconResourceId(0); 1117 shortcut.setBitmapPath(null); 1118 1119 final Icon icon = shortcut.getIcon(); 1120 if (icon == null) { 1121 return; // has no icon 1122 } 1123 1124 Bitmap bitmap; 1125 Bitmap bitmapToRecycle = null; 1126 try { 1127 switch (icon.getType()) { 1128 case Icon.TYPE_RESOURCE: { 1129 injectValidateIconResPackage(shortcut, icon); 1130 1131 shortcut.setIconResourceId(icon.getResId()); 1132 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES); 1133 return; 1134 } 1135 case Icon.TYPE_BITMAP: { 1136 bitmap = icon.getBitmap(); // Don't recycle in this case. 1137 break; 1138 } 1139 default: 1140 // This shouldn't happen because we've already validated the icon, but 1141 // just in case. 1142 throw ShortcutInfo.getInvalidIconException(); 1143 } 1144 if (bitmap == null) { 1145 Slog.e(TAG, "Null bitmap detected"); 1146 return; 1147 } 1148 // Shrink and write to the file. 1149 File path = null; 1150 try { 1151 final FileOutputStreamWithPath out = openIconFileForWrite(userId, shortcut); 1152 try { 1153 path = out.getFile(); 1154 1155 Bitmap shrunk = shrinkBitmap(bitmap, mMaxIconDimension); 1156 try { 1157 shrunk.compress(mIconPersistFormat, mIconPersistQuality, out); 1158 } finally { 1159 if (bitmap != shrunk) { 1160 shrunk.recycle(); 1161 } 1162 } 1163 1164 shortcut.setBitmapPath(out.getFile().getAbsolutePath()); 1165 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_FILE); 1166 } finally { 1167 IoUtils.closeQuietly(out); 1168 } 1169 } catch (IOException|RuntimeException e) { 1170 // STOPSHIP Change wtf to e 1171 Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e); 1172 if (path != null && path.exists()) { 1173 path.delete(); 1174 } 1175 } 1176 } finally { 1177 if (bitmapToRecycle != null) { 1178 bitmapToRecycle.recycle(); 1179 } 1180 // Once saved, we won't use the original icon information, so null it out. 1181 shortcut.clearIcon(); 1182 } 1183 } finally { 1184 injectRestoreCallingIdentity(token); 1185 } 1186 } 1187 1188 // Unfortunately we can't do this check in unit tests because we fake creator package names, 1189 // so override in unit tests. 1190 // TODO CTS this case. 1191 void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) { 1192 if (!shortcut.getPackageName().equals(icon.getResPackage())) { 1193 throw new IllegalArgumentException( 1194 "Icon resource must reside in shortcut owner package"); 1195 } 1196 } 1197 1198 @VisibleForTesting 1199 static Bitmap shrinkBitmap(Bitmap in, int maxSize) { 1200 // Original width/height. 1201 final int ow = in.getWidth(); 1202 final int oh = in.getHeight(); 1203 if ((ow <= maxSize) && (oh <= maxSize)) { 1204 if (DEBUG) { 1205 Slog.d(TAG, String.format("Icon size %dx%d, no need to shrink", ow, oh)); 1206 } 1207 return in; 1208 } 1209 final int longerDimension = Math.max(ow, oh); 1210 1211 // New width and height. 1212 final int nw = ow * maxSize / longerDimension; 1213 final int nh = oh * maxSize / longerDimension; 1214 if (DEBUG) { 1215 Slog.d(TAG, String.format("Icon size %dx%d, shrinking to %dx%d", 1216 ow, oh, nw, nh)); 1217 } 1218 1219 final Bitmap scaledBitmap = Bitmap.createBitmap(nw, nh, Bitmap.Config.ARGB_8888); 1220 final Canvas c = new Canvas(scaledBitmap); 1221 1222 final RectF dst = new RectF(0, 0, nw, nh); 1223 1224 c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null); 1225 1226 return scaledBitmap; 1227 } 1228 1229 // === Caller validation === 1230 1231 private boolean isCallerSystem() { 1232 final int callingUid = injectBinderCallingUid(); 1233 return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID); 1234 } 1235 1236 private boolean isCallerShell() { 1237 final int callingUid = injectBinderCallingUid(); 1238 return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID; 1239 } 1240 1241 private void enforceSystemOrShell() { 1242 Preconditions.checkState(isCallerSystem() || isCallerShell(), 1243 "Caller must be system or shell"); 1244 } 1245 1246 private void enforceShell() { 1247 Preconditions.checkState(isCallerShell(), "Caller must be shell"); 1248 } 1249 1250 private void enforceSystem() { 1251 Preconditions.checkState(isCallerSystem(), "Caller must be system"); 1252 } 1253 1254 private void enforceResetThrottlingPermission() { 1255 if (isCallerSystem()) { 1256 return; 1257 } 1258 injectEnforceCallingPermission( 1259 android.Manifest.permission.RESET_SHORTCUT_MANAGER_THROTTLING, null); 1260 } 1261 1262 /** 1263 * Somehow overriding ServiceContext.enforceCallingPermission() in the unit tests would confuse 1264 * mockito. So instead we extracted it here and override it in the tests. 1265 */ 1266 @VisibleForTesting 1267 void injectEnforceCallingPermission( 1268 @NonNull String permission, @Nullable String message) { 1269 mContext.enforceCallingPermission(permission, message); 1270 } 1271 1272 private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) { 1273 Preconditions.checkStringNotEmpty(packageName, "packageName"); 1274 1275 if (isCallerSystem()) { 1276 return; // no check 1277 } 1278 1279 final int callingUid = injectBinderCallingUid(); 1280 1281 // Otherwise, make sure the arguments are valid. 1282 if (UserHandle.getUserId(callingUid) != userId) { 1283 throw new SecurityException("Invalid user-ID"); 1284 } 1285 if (injectGetPackageUid(packageName, userId) == injectBinderCallingUid()) { 1286 return; // Caller is valid. 1287 } 1288 throw new SecurityException("Calling package name mismatch"); 1289 } 1290 1291 void postToHandler(Runnable r) { 1292 mHandler.post(r); 1293 } 1294 1295 /** 1296 * Throw if {@code numShortcuts} is bigger than {@link #mMaxDynamicShortcuts}. 1297 */ 1298 void enforceMaxDynamicShortcuts(int numShortcuts) { 1299 if (numShortcuts > mMaxDynamicShortcuts) { 1300 throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded"); 1301 } 1302 } 1303 1304 /** 1305 * - Sends a notification to LauncherApps 1306 * - Write to file 1307 */ 1308 void packageShortcutsChanged(@NonNull String packageName, @UserIdInt int userId) { 1309 if (DEBUG) { 1310 Slog.d(TAG, String.format( 1311 "Shortcut changes: package=%s, user=%d", packageName, userId)); 1312 } 1313 notifyListeners(packageName, userId); 1314 scheduleSaveUser(userId); 1315 } 1316 1317 private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) { 1318 final long token = injectClearCallingIdentity(); 1319 try { 1320 if (!mUserManager.isUserRunning(userId)) { 1321 return; 1322 } 1323 } finally { 1324 injectRestoreCallingIdentity(token); 1325 } 1326 postToHandler(() -> { 1327 final ArrayList<ShortcutChangeListener> copy; 1328 synchronized (mLock) { 1329 copy = new ArrayList<>(mListeners); 1330 } 1331 // Note onShortcutChanged() needs to be called with the system service permissions. 1332 for (int i = copy.size() - 1; i >= 0; i--) { 1333 copy.get(i).onShortcutChanged(packageName, userId); 1334 } 1335 }); 1336 } 1337 1338 /** 1339 * Clean up / validate an incoming shortcut. 1340 * - Make sure all mandatory fields are set. 1341 * - Make sure the intent's extras are persistable, and them to set 1342 * {@link ShortcutInfo#mIntentPersistableExtras}. Also clear its extras. 1343 * - Clear flags. 1344 * 1345 * TODO Detailed unit tests 1346 */ 1347 private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) { 1348 Preconditions.checkNotNull(shortcut, "Null shortcut detected"); 1349 if (shortcut.getActivityComponent() != null) { 1350 Preconditions.checkState( 1351 shortcut.getPackageName().equals( 1352 shortcut.getActivityComponent().getPackageName()), 1353 "Activity package name mismatch"); 1354 } 1355 1356 if (!forUpdate) { 1357 shortcut.enforceMandatoryFields(); 1358 } 1359 if (shortcut.getIcon() != null) { 1360 ShortcutInfo.validateIcon(shortcut.getIcon()); 1361 } 1362 1363 validateForXml(shortcut.getId()); 1364 validateForXml(shortcut.getTitle()); 1365 validatePersistableBundleForXml(shortcut.getIntentPersistableExtras()); 1366 validatePersistableBundleForXml(shortcut.getExtras()); 1367 1368 shortcut.replaceFlags(0); 1369 } 1370 1371 // KXmlSerializer is strict and doesn't allow certain characters, so we disallow those 1372 // characters. 1373 1374 private static void validatePersistableBundleForXml(PersistableBundle b) { 1375 if (b == null || b.size() == 0) { 1376 return; 1377 } 1378 for (String key : b.keySet()) { 1379 validateForXml(key); 1380 final Object value = b.get(key); 1381 if (value == null) { 1382 continue; 1383 } else if (value instanceof String) { 1384 validateForXml((String) value); 1385 } else if (value instanceof String[]) { 1386 for (String v : (String[]) value) { 1387 validateForXml(v); 1388 } 1389 } else if (value instanceof PersistableBundle) { 1390 validatePersistableBundleForXml((PersistableBundle) value); 1391 } 1392 } 1393 } 1394 1395 private static void validateForXml(String s) { 1396 if (TextUtils.isEmpty(s)) { 1397 return; 1398 } 1399 for (int i = s.length() - 1; i >= 0; i--) { 1400 if (!isAllowedInXml(s.charAt(i))) { 1401 throw new IllegalArgumentException("Unsupported character detected in: " + s); 1402 } 1403 } 1404 } 1405 1406 private static boolean isAllowedInXml(char c) { 1407 return (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd); 1408 } 1409 1410 // === APIs === 1411 1412 @Override 1413 public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, 1414 @UserIdInt int userId) { 1415 verifyCaller(packageName, userId); 1416 1417 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); 1418 final int size = newShortcuts.size(); 1419 1420 synchronized (mLock) { 1421 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); 1422 1423 // Throttling. 1424 if (!ps.tryApiCall()) { 1425 return false; 1426 } 1427 enforceMaxDynamicShortcuts(size); 1428 1429 // Validate the shortcuts. 1430 for (int i = 0; i < size; i++) { 1431 fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false); 1432 } 1433 1434 // First, remove all un-pinned; dynamic shortcuts 1435 ps.deleteAllDynamicShortcuts(); 1436 1437 // Then, add/update all. We need to make sure to take over "pinned" flag. 1438 for (int i = 0; i < size; i++) { 1439 final ShortcutInfo newShortcut = newShortcuts.get(i); 1440 ps.addDynamicShortcut(newShortcut); 1441 } 1442 } 1443 packageShortcutsChanged(packageName, userId); 1444 return true; 1445 } 1446 1447 @Override 1448 public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, 1449 @UserIdInt int userId) { 1450 verifyCaller(packageName, userId); 1451 1452 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); 1453 final int size = newShortcuts.size(); 1454 1455 synchronized (mLock) { 1456 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); 1457 1458 // Throttling. 1459 if (!ps.tryApiCall()) { 1460 return false; 1461 } 1462 1463 for (int i = 0; i < size; i++) { 1464 final ShortcutInfo source = newShortcuts.get(i); 1465 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true); 1466 1467 final ShortcutInfo target = ps.findShortcutById(source.getId()); 1468 if (target != null) { 1469 final boolean replacingIcon = (source.getIcon() != null); 1470 if (replacingIcon) { 1471 removeIcon(userId, target); 1472 } 1473 1474 target.copyNonNullFieldsFrom(source); 1475 1476 if (replacingIcon) { 1477 saveIconAndFixUpShortcut(userId, target); 1478 } 1479 } 1480 } 1481 } 1482 packageShortcutsChanged(packageName, userId); 1483 1484 return true; 1485 } 1486 1487 @Override 1488 public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, 1489 @UserIdInt int userId) { 1490 verifyCaller(packageName, userId); 1491 1492 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); 1493 final int size = newShortcuts.size(); 1494 1495 synchronized (mLock) { 1496 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); 1497 1498 // Throttling. 1499 if (!ps.tryApiCall()) { 1500 return false; 1501 } 1502 for (int i = 0; i < size; i++) { 1503 final ShortcutInfo newShortcut = newShortcuts.get(i); 1504 1505 // Validate the shortcut. 1506 fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false); 1507 1508 // Add it. 1509 ps.addDynamicShortcut(newShortcut); 1510 } 1511 } 1512 packageShortcutsChanged(packageName, userId); 1513 1514 return true; 1515 } 1516 1517 @Override 1518 public void removeDynamicShortcuts(String packageName, List shortcutIds, 1519 @UserIdInt int userId) { 1520 verifyCaller(packageName, userId); 1521 Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); 1522 1523 synchronized (mLock) { 1524 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 1525 getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId( 1526 Preconditions.checkStringNotEmpty((String) shortcutIds.get(i))); 1527 } 1528 } 1529 packageShortcutsChanged(packageName, userId); 1530 } 1531 1532 @Override 1533 public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) { 1534 verifyCaller(packageName, userId); 1535 1536 synchronized (mLock) { 1537 getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(); 1538 } 1539 packageShortcutsChanged(packageName, userId); 1540 } 1541 1542 @Override 1543 public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName, 1544 @UserIdInt int userId) { 1545 verifyCaller(packageName, userId); 1546 synchronized (mLock) { 1547 return getShortcutsWithQueryLocked( 1548 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, 1549 ShortcutInfo::isDynamic); 1550 } 1551 } 1552 1553 @Override 1554 public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName, 1555 @UserIdInt int userId) { 1556 verifyCaller(packageName, userId); 1557 synchronized (mLock) { 1558 return getShortcutsWithQueryLocked( 1559 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, 1560 ShortcutInfo::isPinned); 1561 } 1562 } 1563 1564 private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName, 1565 @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) { 1566 1567 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 1568 1569 getPackageShortcutsLocked(packageName, userId).findAll(ret, query, cloneFlags); 1570 1571 return new ParceledListSlice<>(ret); 1572 } 1573 1574 @Override 1575 public int getMaxDynamicShortcutCount(String packageName, @UserIdInt int userId) 1576 throws RemoteException { 1577 verifyCaller(packageName, userId); 1578 1579 return mMaxDynamicShortcuts; 1580 } 1581 1582 @Override 1583 public int getRemainingCallCount(String packageName, @UserIdInt int userId) { 1584 verifyCaller(packageName, userId); 1585 1586 synchronized (mLock) { 1587 return mMaxUpdatesPerInterval 1588 - getPackageShortcutsLocked(packageName, userId).getApiCallCount(); 1589 } 1590 } 1591 1592 @Override 1593 public long getRateLimitResetTime(String packageName, @UserIdInt int userId) { 1594 verifyCaller(packageName, userId); 1595 1596 synchronized (mLock) { 1597 return getNextResetTimeLocked(); 1598 } 1599 } 1600 1601 @Override 1602 public int getIconMaxDimensions(String packageName, int userId) throws RemoteException { 1603 verifyCaller(packageName, userId); 1604 1605 synchronized (mLock) { 1606 return mMaxIconDimension; 1607 } 1608 } 1609 1610 /** 1611 * Reset all throttling, for developer options and command line. Only system/shell can call it. 1612 */ 1613 @Override 1614 public void resetThrottling() { 1615 enforceSystemOrShell(); 1616 1617 resetThrottlingInner(getCallingUserId()); 1618 } 1619 1620 void resetThrottlingInner(@UserIdInt int userId) { 1621 synchronized (mLock) { 1622 getUserShortcutsLocked(userId).resetThrottling(); 1623 } 1624 scheduleSaveUser(userId); 1625 Slog.i(TAG, "ShortcutManager: throttling counter reset for user " + userId); 1626 } 1627 1628 void resetAllThrottlingInner() { 1629 synchronized (mLock) { 1630 mRawLastResetTime = injectCurrentTimeMillis(); 1631 } 1632 scheduleSaveBaseState(); 1633 Slog.i(TAG, "ShortcutManager: throttling counter reset for all users"); 1634 } 1635 1636 void resetPackageThrottling(String packageName, int userId) { 1637 synchronized (mLock) { 1638 getPackageShortcutsLocked(packageName, userId) 1639 .resetRateLimitingForCommandLineNoSaving(); 1640 saveUserLocked(userId); 1641 } 1642 } 1643 1644 @Override 1645 public void onApplicationActive(String packageName, int userId) { 1646 if (DEBUG) { 1647 Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId); 1648 } 1649 enforceResetThrottlingPermission(); 1650 resetPackageThrottling(packageName, userId); 1651 } 1652 1653 // We override this method in unit tests to do a simpler check. 1654 boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) { 1655 return hasShortcutHostPermissionInner(callingPackage, userId); 1656 } 1657 1658 // This method is extracted so we can directly call this method from unit tests, 1659 // even when hasShortcutPermission() is overridden. 1660 @VisibleForTesting 1661 boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) { 1662 synchronized (mLock) { 1663 final long start = injectElapsedRealtime(); 1664 1665 final ShortcutUser user = getUserShortcutsLocked(userId); 1666 1667 final List<ResolveInfo> allHomeCandidates = new ArrayList<>(); 1668 1669 // Default launcher from package manager. 1670 final long startGetHomeActivitiesAsUser = injectElapsedRealtime(); 1671 final ComponentName defaultLauncher = injectPackageManagerInternal() 1672 .getHomeActivitiesAsUser(allHomeCandidates, userId); 1673 logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser); 1674 1675 ComponentName detected; 1676 if (defaultLauncher != null) { 1677 detected = defaultLauncher; 1678 if (DEBUG) { 1679 Slog.v(TAG, "Default launcher from PM: " + detected); 1680 } 1681 } else { 1682 detected = user.getDefaultLauncherComponent(); 1683 1684 // TODO: Make sure it's still enabled. 1685 if (DEBUG) { 1686 Slog.v(TAG, "Cached launcher: " + detected); 1687 } 1688 } 1689 1690 if (detected == null) { 1691 // If we reach here, that means it's the first check since the user was created, 1692 // and there's already multiple launchers and there's no default set. 1693 // Find the system one with the highest priority. 1694 // (We need to check the priority too because of FallbackHome in Settings.) 1695 // If there's no system launcher yet, then no one can access shortcuts, until 1696 // the user explicitly 1697 final int size = allHomeCandidates.size(); 1698 1699 int lastPriority = Integer.MIN_VALUE; 1700 for (int i = 0; i < size; i++) { 1701 final ResolveInfo ri = allHomeCandidates.get(i); 1702 if (!ri.activityInfo.applicationInfo.isSystemApp()) { 1703 continue; 1704 } 1705 if (DEBUG) { 1706 Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d", 1707 ri.activityInfo.getComponentName(), ri.priority)); 1708 } 1709 if (ri.priority < lastPriority) { 1710 continue; 1711 } 1712 detected = ri.activityInfo.getComponentName(); 1713 lastPriority = ri.priority; 1714 } 1715 } 1716 logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start); 1717 1718 if (detected != null) { 1719 if (DEBUG) { 1720 Slog.v(TAG, "Detected launcher: " + detected); 1721 } 1722 user.setDefaultLauncherComponent(detected); 1723 return detected.getPackageName().equals(callingPackage); 1724 } else { 1725 // Default launcher not found. 1726 return false; 1727 } 1728 } 1729 } 1730 1731 // === House keeping === 1732 1733 private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId) { 1734 synchronized (mLock) { 1735 forEachLoadedUserLocked(user -> 1736 cleanUpPackageLocked(packageName, user.getUserId(), packageUserId)); 1737 } 1738 } 1739 1740 /** 1741 * Remove all the information associated with a package. This will really remove all the 1742 * information, including the restore information (i.e. it'll remove packages even if they're 1743 * shadow). 1744 * 1745 * This is called when an app is uninstalled, or an app gets "clear data"ed. 1746 */ 1747 @VisibleForTesting 1748 void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) { 1749 final boolean wasUserLoaded = isUserLoadedLocked(owningUserId); 1750 1751 final ShortcutUser user = getUserShortcutsLocked(owningUserId); 1752 boolean doNotify = false; 1753 1754 // First, remove the package from the package list (if the package is a publisher). 1755 if (packageUserId == owningUserId) { 1756 if (user.removePackage(packageName) != null) { 1757 doNotify = true; 1758 } 1759 } 1760 1761 // Also remove from the launcher list (if the package is a launcher). 1762 user.removeLauncher(packageUserId, packageName); 1763 1764 // Then remove pinned shortcuts from all launchers. 1765 user.forAllLaunchers(l -> l.cleanUpPackage(packageName, packageUserId)); 1766 1767 // Now there may be orphan shortcuts because we removed pinned shortcuts at the previous 1768 // step. Remove them too. 1769 user.forAllPackages(p -> p.refreshPinnedFlags()); 1770 1771 scheduleSaveUser(owningUserId); 1772 1773 if (doNotify) { 1774 notifyListeners(packageName, owningUserId); 1775 } 1776 1777 if (!wasUserLoaded) { 1778 // Note this will execute the scheduled save. 1779 unloadUserLocked(owningUserId); 1780 } 1781 } 1782 1783 /** 1784 * Entry point from {@link LauncherApps}. 1785 */ 1786 private class LocalService extends ShortcutServiceInternal { 1787 1788 @Override 1789 public List<ShortcutInfo> getShortcuts(int launcherUserId, 1790 @NonNull String callingPackage, long changedSince, 1791 @Nullable String packageName, @Nullable List<String> shortcutIds, 1792 @Nullable ComponentName componentName, 1793 int queryFlags, int userId) { 1794 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 1795 final int cloneFlag = 1796 ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) == 0) 1797 ? ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER 1798 : ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO; 1799 if (packageName == null) { 1800 shortcutIds = null; // LauncherAppsService already threw for it though. 1801 } 1802 1803 synchronized (mLock) { 1804 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 1805 .attemptToRestoreIfNeededAndSave(); 1806 1807 if (packageName != null) { 1808 getShortcutsInnerLocked(launcherUserId, 1809 callingPackage, packageName, shortcutIds, changedSince, 1810 componentName, queryFlags, userId, ret, cloneFlag); 1811 } else { 1812 final List<String> shortcutIdsF = shortcutIds; 1813 getUserShortcutsLocked(userId).forAllPackages(p -> { 1814 getShortcutsInnerLocked(launcherUserId, 1815 callingPackage, p.getPackageName(), shortcutIdsF, changedSince, 1816 componentName, queryFlags, userId, ret, cloneFlag); 1817 }); 1818 } 1819 } 1820 return ret; 1821 } 1822 1823 private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage, 1824 @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince, 1825 @Nullable ComponentName componentName, int queryFlags, 1826 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) { 1827 final ArraySet<String> ids = shortcutIds == null ? null 1828 : new ArraySet<>(shortcutIds); 1829 1830 final ShortcutPackage p = getUserShortcutsLocked(userId) 1831 .getPackageShortcutsIfExists(packageName); 1832 if (p == null) { 1833 return; // No need to instantiate ShortcutPackage. 1834 } 1835 1836 p.findAll(ret, 1837 (ShortcutInfo si) -> { 1838 if (si.getLastChangedTimestamp() < changedSince) { 1839 return false; 1840 } 1841 if (ids != null && !ids.contains(si.getId())) { 1842 return false; 1843 } 1844 if (componentName != null) { 1845 if (si.getActivityComponent() != null 1846 && !si.getActivityComponent().equals(componentName)) { 1847 return false; 1848 } 1849 } 1850 final boolean matchDynamic = 1851 ((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0) 1852 && si.isDynamic(); 1853 final boolean matchPinned = 1854 ((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0) 1855 && si.isPinned(); 1856 return matchDynamic || matchPinned; 1857 }, cloneFlag, callingPackage, launcherUserId); 1858 } 1859 1860 @Override 1861 public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, 1862 @NonNull String packageName, @NonNull String shortcutId, int userId) { 1863 Preconditions.checkStringNotEmpty(packageName, "packageName"); 1864 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 1865 1866 synchronized (mLock) { 1867 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 1868 .attemptToRestoreIfNeededAndSave(); 1869 1870 final ShortcutInfo si = getShortcutInfoLocked( 1871 launcherUserId, callingPackage, packageName, shortcutId, userId); 1872 return si != null && si.isPinned(); 1873 } 1874 } 1875 1876 private ShortcutInfo getShortcutInfoLocked( 1877 int launcherUserId, @NonNull String callingPackage, 1878 @NonNull String packageName, @NonNull String shortcutId, int userId) { 1879 Preconditions.checkStringNotEmpty(packageName, "packageName"); 1880 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 1881 1882 final ShortcutPackage p = getUserShortcutsLocked(userId) 1883 .getPackageShortcutsIfExists(packageName); 1884 if (p == null) { 1885 return null; 1886 } 1887 1888 final ArrayList<ShortcutInfo> list = new ArrayList<>(1); 1889 p.findAll(list, 1890 (ShortcutInfo si) -> shortcutId.equals(si.getId()), 1891 /* clone flags=*/ 0, callingPackage, launcherUserId); 1892 return list.size() == 0 ? null : list.get(0); 1893 } 1894 1895 @Override 1896 public void pinShortcuts(int launcherUserId, 1897 @NonNull String callingPackage, @NonNull String packageName, 1898 @NonNull List<String> shortcutIds, int userId) { 1899 // Calling permission must be checked by LauncherAppsImpl. 1900 Preconditions.checkStringNotEmpty(packageName, "packageName"); 1901 Preconditions.checkNotNull(shortcutIds, "shortcutIds"); 1902 1903 synchronized (mLock) { 1904 final ShortcutLauncher launcher = 1905 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId); 1906 launcher.attemptToRestoreIfNeededAndSave(); 1907 1908 launcher.pinShortcuts(userId, packageName, shortcutIds); 1909 } 1910 packageShortcutsChanged(packageName, userId); 1911 } 1912 1913 @Override 1914 public Intent createShortcutIntent(int launcherUserId, 1915 @NonNull String callingPackage, 1916 @NonNull String packageName, @NonNull String shortcutId, int userId) { 1917 // Calling permission must be checked by LauncherAppsImpl. 1918 Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty"); 1919 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty"); 1920 1921 synchronized (mLock) { 1922 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 1923 .attemptToRestoreIfNeededAndSave(); 1924 1925 // Make sure the shortcut is actually visible to the launcher. 1926 final ShortcutInfo si = getShortcutInfoLocked( 1927 launcherUserId, callingPackage, packageName, shortcutId, userId); 1928 // "si == null" should suffice here, but check the flags too just to make sure. 1929 if (si == null || !(si.isDynamic() || si.isPinned())) { 1930 return null; 1931 } 1932 return si.getIntent(); 1933 } 1934 } 1935 1936 @Override 1937 public void addListener(@NonNull ShortcutChangeListener listener) { 1938 synchronized (mLock) { 1939 mListeners.add(Preconditions.checkNotNull(listener)); 1940 } 1941 } 1942 1943 @Override 1944 public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage, 1945 @NonNull String packageName, @NonNull String shortcutId, int userId) { 1946 Preconditions.checkNotNull(callingPackage, "callingPackage"); 1947 Preconditions.checkNotNull(packageName, "packageName"); 1948 Preconditions.checkNotNull(shortcutId, "shortcutId"); 1949 1950 synchronized (mLock) { 1951 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 1952 .attemptToRestoreIfNeededAndSave(); 1953 1954 final ShortcutPackage p = getUserShortcutsLocked(userId) 1955 .getPackageShortcutsIfExists(packageName); 1956 if (p == null) { 1957 return 0; 1958 } 1959 1960 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 1961 return (shortcutInfo != null && shortcutInfo.hasIconResource()) 1962 ? shortcutInfo.getIconResourceId() : 0; 1963 } 1964 } 1965 1966 @Override 1967 public ParcelFileDescriptor getShortcutIconFd(int launcherUserId, 1968 @NonNull String callingPackage, @NonNull String packageName, 1969 @NonNull String shortcutId, int userId) { 1970 Preconditions.checkNotNull(callingPackage, "callingPackage"); 1971 Preconditions.checkNotNull(packageName, "packageName"); 1972 Preconditions.checkNotNull(shortcutId, "shortcutId"); 1973 1974 synchronized (mLock) { 1975 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 1976 .attemptToRestoreIfNeededAndSave(); 1977 1978 final ShortcutPackage p = getUserShortcutsLocked(userId) 1979 .getPackageShortcutsIfExists(packageName); 1980 if (p == null) { 1981 return null; 1982 } 1983 1984 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 1985 if (shortcutInfo == null || !shortcutInfo.hasIconFile()) { 1986 return null; 1987 } 1988 try { 1989 if (shortcutInfo.getBitmapPath() == null) { 1990 Slog.w(TAG, "null bitmap detected in getShortcutIconFd()"); 1991 return null; 1992 } 1993 return ParcelFileDescriptor.open( 1994 new File(shortcutInfo.getBitmapPath()), 1995 ParcelFileDescriptor.MODE_READ_ONLY); 1996 } catch (FileNotFoundException e) { 1997 Slog.e(TAG, "Icon file not found: " + shortcutInfo.getBitmapPath()); 1998 return null; 1999 } 2000 } 2001 } 2002 2003 @Override 2004 public boolean hasShortcutHostPermission(int launcherUserId, 2005 @NonNull String callingPackage) { 2006 return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId); 2007 } 2008 2009 /** 2010 * Called by AM when the system locale changes *within the AM lock. ABSOLUTELY do not take 2011 * any locks in this method. 2012 */ 2013 @Override 2014 public void onSystemLocaleChangedNoLock() { 2015 // DO NOT HOLD ANY LOCKS HERE. 2016 2017 // We want to reset throttling for all packages for all users. But we can't just do so 2018 // here because: 2019 // - We can't load/save users that are locked. 2020 // - Even for loaded users, resetting the counters would require us to hold mLock. 2021 // 2022 // So we use a "pull" model instead. In here, we just increment the "locale change 2023 // sequence number". Each ShortcutUser has the "last known locale change sequence". 2024 // 2025 // This allows ShortcutUser's to detect the system locale change, so they can reset 2026 // counters. 2027 2028 // Ignore all callback during system boot. 2029 if (mBootCompleted.get()) { 2030 mLocaleChangeSequenceNumber.incrementAndGet(); 2031 if (DEBUG) { 2032 Slog.d(TAG, "onSystemLocaleChangedNoLock: " + mLocaleChangeSequenceNumber.get()); 2033 } 2034 postToHandler(() -> scheduleSaveBaseState()); 2035 } 2036 } 2037 } 2038 2039 /** 2040 * Package event callbacks. 2041 */ 2042 @VisibleForTesting 2043 final PackageMonitor mPackageMonitor = new PackageMonitor() { 2044 @Override 2045 public void onPackageAdded(String packageName, int uid) { 2046 handlePackageAdded(packageName, getChangingUserId()); 2047 } 2048 2049 @Override 2050 public void onPackageUpdateFinished(String packageName, int uid) { 2051 handlePackageUpdateFinished(packageName, getChangingUserId()); 2052 } 2053 2054 @Override 2055 public void onPackageRemoved(String packageName, int uid) { 2056 handlePackageRemoved(packageName, getChangingUserId()); 2057 } 2058 2059 @Override 2060 public void onPackageDataCleared(String packageName, int uid) { 2061 handlePackageDataCleared(packageName, getChangingUserId()); 2062 } 2063 }; 2064 2065 /** 2066 * Called when a user is unlocked. 2067 * - Check all known packages still exist, and otherwise perform cleanup. 2068 * - If a package still exists, check the version code. If it's been updated, may need to 2069 * update timestamps of its shortcuts. 2070 */ 2071 @VisibleForTesting 2072 void checkPackageChanges(@UserIdInt int ownerUserId) { 2073 if (DEBUG) { 2074 Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId); 2075 } 2076 final ArrayList<PackageWithUser> gonePackages = new ArrayList<>(); 2077 2078 synchronized (mLock) { 2079 final ShortcutUser user = getUserShortcutsLocked(ownerUserId); 2080 2081 user.forAllPackageItems(spi -> { 2082 if (spi.getPackageInfo().isShadow()) { 2083 return; // Don't delete shadow information. 2084 } 2085 final int versionCode = getApplicationVersionCode( 2086 spi.getPackageName(), spi.getPackageUserId()); 2087 if (versionCode >= 0) { 2088 // Package still installed, see if it's updated. 2089 getUserShortcutsLocked(ownerUserId).handlePackageUpdated( 2090 spi.getPackageName(), versionCode); 2091 } else { 2092 gonePackages.add(PackageWithUser.of(spi)); 2093 } 2094 }); 2095 if (gonePackages.size() > 0) { 2096 for (int i = gonePackages.size() - 1; i >= 0; i--) { 2097 final PackageWithUser pu = gonePackages.get(i); 2098 cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId); 2099 } 2100 } 2101 } 2102 } 2103 2104 private void handlePackageAdded(String packageName, @UserIdInt int userId) { 2105 if (DEBUG) { 2106 Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId)); 2107 } 2108 synchronized (mLock) { 2109 forEachLoadedUserLocked(user -> 2110 user.attemptToRestoreIfNeededAndSave(this, packageName, userId)); 2111 } 2112 } 2113 2114 private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) { 2115 if (DEBUG) { 2116 Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d", 2117 packageName, userId)); 2118 } 2119 synchronized (mLock) { 2120 forEachLoadedUserLocked(user -> 2121 user.attemptToRestoreIfNeededAndSave(this, packageName, userId)); 2122 2123 final int versionCode = getApplicationVersionCode(packageName, userId); 2124 if (versionCode < 0) { 2125 return; // shouldn't happen 2126 } 2127 getUserShortcutsLocked(userId).handlePackageUpdated(packageName, versionCode); 2128 } 2129 } 2130 2131 private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) { 2132 if (DEBUG) { 2133 Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, 2134 packageUserId)); 2135 } 2136 cleanUpPackageForAllLoadedUsers(packageName, packageUserId); 2137 } 2138 2139 private void handlePackageDataCleared(String packageName, int packageUserId) { 2140 if (DEBUG) { 2141 Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName, 2142 packageUserId)); 2143 } 2144 cleanUpPackageForAllLoadedUsers(packageName, packageUserId); 2145 } 2146 2147 // === PackageManager interaction === 2148 2149 PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) { 2150 return injectPackageInfo(packageName, userId, true); 2151 } 2152 2153 int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) { 2154 final long token = injectClearCallingIdentity(); 2155 try { 2156 return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS 2157 , userId); 2158 } catch (RemoteException e) { 2159 // Shouldn't happen. 2160 Slog.wtf(TAG, "RemoteException", e); 2161 return -1; 2162 } finally { 2163 injectRestoreCallingIdentity(token); 2164 } 2165 } 2166 2167 @VisibleForTesting 2168 PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId, 2169 boolean getSignatures) { 2170 final long start = injectElapsedRealtime(); 2171 final long token = injectClearCallingIdentity(); 2172 try { 2173 return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS 2174 | (getSignatures ? PackageManager.GET_SIGNATURES : 0) 2175 , userId); 2176 } catch (RemoteException e) { 2177 // Shouldn't happen. 2178 Slog.wtf(TAG, "RemoteException", e); 2179 return null; 2180 } finally { 2181 injectRestoreCallingIdentity(token); 2182 2183 logDurationStat( 2184 (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO), 2185 start); 2186 } 2187 } 2188 2189 @VisibleForTesting 2190 ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) { 2191 final long start = injectElapsedRealtime(); 2192 final long token = injectClearCallingIdentity(); 2193 try { 2194 return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId); 2195 } catch (RemoteException e) { 2196 // Shouldn't happen. 2197 Slog.wtf(TAG, "RemoteException", e); 2198 return null; 2199 } finally { 2200 injectRestoreCallingIdentity(token); 2201 2202 logDurationStat(Stats.GET_APPLICATION_INFO, start); 2203 } 2204 } 2205 2206 private boolean isApplicationFlagSet(String packageName, int userId, int flags) { 2207 final ApplicationInfo ai = injectApplicationInfo(packageName, userId); 2208 return (ai != null) && ((ai.flags & flags) == flags); 2209 } 2210 2211 boolean isPackageInstalled(String packageName, int userId) { 2212 return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED); 2213 } 2214 2215 /** 2216 * @return the version code of the package, or -1 if the app is not installed. 2217 */ 2218 int getApplicationVersionCode(String packageName, int userId) { 2219 final ApplicationInfo ai = injectApplicationInfo(packageName, userId); 2220 if ((ai == null) || ((ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) { 2221 return -1; 2222 } 2223 return ai.versionCode; 2224 } 2225 2226 // === Backup & restore === 2227 2228 boolean shouldBackupApp(String packageName, int userId) { 2229 return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP); 2230 } 2231 2232 boolean shouldBackupApp(PackageInfo pi) { 2233 return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0; 2234 } 2235 2236 @Override 2237 public byte[] getBackupPayload(@UserIdInt int userId) { 2238 enforceSystem(); 2239 if (DEBUG) { 2240 Slog.d(TAG, "Backing up user " + userId); 2241 } 2242 synchronized (mLock) { 2243 final ShortcutUser user = getUserShortcutsLocked(userId); 2244 if (user == null) { 2245 Slog.w(TAG, "Can't backup: user not found: id=" + userId); 2246 return null; 2247 } 2248 2249 user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave()); 2250 2251 // Then save. 2252 final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024); 2253 try { 2254 saveUserInternalLocked(userId, os, /* forBackup */ true); 2255 } catch (XmlPullParserException|IOException e) { 2256 // Shouldn't happen. 2257 Slog.w(TAG, "Backup failed.", e); 2258 return null; 2259 } 2260 return os.toByteArray(); 2261 } 2262 } 2263 2264 @Override 2265 public void applyRestore(byte[] payload, @UserIdInt int userId) { 2266 enforceSystem(); 2267 if (DEBUG) { 2268 Slog.d(TAG, "Restoring user " + userId); 2269 } 2270 final ShortcutUser user; 2271 final ByteArrayInputStream is = new ByteArrayInputStream(payload); 2272 try { 2273 user = loadUserInternal(userId, is, /* fromBackup */ true); 2274 } catch (XmlPullParserException|IOException e) { 2275 Slog.w(TAG, "Restoration failed.", e); 2276 return; 2277 } 2278 synchronized (mLock) { 2279 mUsers.put(userId, user); 2280 2281 // Then purge all the save images. 2282 final File bitmapPath = getUserBitmapFilePath(userId); 2283 final boolean success = FileUtils.deleteContents(bitmapPath); 2284 if (!success) { 2285 Slog.w(TAG, "Failed to delete " + bitmapPath); 2286 } 2287 2288 saveUserLocked(userId); 2289 } 2290 } 2291 2292 // === Dump === 2293 2294 @Override 2295 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2296 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 2297 != PackageManager.PERMISSION_GRANTED) { 2298 pw.println("Permission Denial: can't dump UserManager from from pid=" 2299 + Binder.getCallingPid() 2300 + ", uid=" + Binder.getCallingUid() 2301 + " without permission " 2302 + android.Manifest.permission.DUMP); 2303 return; 2304 } 2305 dumpInner(pw, args); 2306 } 2307 2308 @VisibleForTesting 2309 void dumpInner(PrintWriter pw, String[] args) { 2310 synchronized (mLock) { 2311 final long now = injectCurrentTimeMillis(); 2312 pw.print("Now: ["); 2313 pw.print(now); 2314 pw.print("] "); 2315 pw.print(formatTime(now)); 2316 2317 pw.print(" Raw last reset: ["); 2318 pw.print(mRawLastResetTime); 2319 pw.print("] "); 2320 pw.print(formatTime(mRawLastResetTime)); 2321 2322 final long last = getLastResetTimeLocked(); 2323 pw.print(" Last reset: ["); 2324 pw.print(last); 2325 pw.print("] "); 2326 pw.print(formatTime(last)); 2327 2328 final long next = getNextResetTimeLocked(); 2329 pw.print(" Next reset: ["); 2330 pw.print(next); 2331 pw.print("] "); 2332 pw.print(formatTime(next)); 2333 2334 pw.print(" Locale change seq#: "); 2335 pw.print(mLocaleChangeSequenceNumber.get()); 2336 pw.println(); 2337 2338 pw.print(" Config:"); 2339 pw.print(" Max icon dim: "); 2340 pw.println(mMaxIconDimension); 2341 pw.print(" Icon format: "); 2342 pw.println(mIconPersistFormat); 2343 pw.print(" Icon quality: "); 2344 pw.println(mIconPersistQuality); 2345 pw.print(" saveDelayMillis: "); 2346 pw.println(mSaveDelayMillis); 2347 pw.print(" resetInterval: "); 2348 pw.println(mResetInterval); 2349 pw.print(" maxUpdatesPerInterval: "); 2350 pw.println(mMaxUpdatesPerInterval); 2351 pw.print(" maxDynamicShortcuts: "); 2352 pw.println(mMaxDynamicShortcuts); 2353 pw.println(); 2354 2355 pw.println(" Stats:"); 2356 synchronized (mStatLock) { 2357 final String p = " "; 2358 dumpStatLS(pw, p, Stats.GET_DEFAULT_HOME, "getHomeActivities()"); 2359 dumpStatLS(pw, p, Stats.LAUNCHER_PERMISSION_CHECK, "Launcher permission check"); 2360 2361 dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()"); 2362 dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)"); 2363 dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo"); 2364 2365 dumpStatLS(pw, p, Stats.CLEANUP_DANGLING_BITMAPS, "cleanupDanglingBitmaps"); 2366 } 2367 2368 for (int i = 0; i < mUsers.size(); i++) { 2369 pw.println(); 2370 mUsers.valueAt(i).dump(pw, " "); 2371 } 2372 2373 pw.println(); 2374 pw.println(" UID state:"); 2375 2376 for (int i = 0; i < mUidState.size(); i++) { 2377 final int uid = mUidState.keyAt(i); 2378 final int state = mUidState.valueAt(i); 2379 pw.print(" UID="); 2380 pw.print(uid); 2381 pw.print(" state="); 2382 pw.print(state); 2383 if (isProcessStateForeground(state)) { 2384 pw.print(" [FG]"); 2385 } 2386 pw.print(" last FG="); 2387 pw.print(mUidLastForegroundElapsedTime.get(uid)); 2388 pw.println(); 2389 } 2390 } 2391 } 2392 2393 static String formatTime(long time) { 2394 Time tobj = new Time(); 2395 tobj.set(time); 2396 return tobj.format("%Y-%m-%d %H:%M:%S"); 2397 } 2398 2399 private void dumpStatLS(PrintWriter pw, String prefix, int statId, String label) { 2400 pw.print(prefix); 2401 final int count = mCountStats[statId]; 2402 final long dur = mDurationStats[statId]; 2403 pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms", 2404 label, count, dur, 2405 (count == 0 ? 0 : ((double) dur) / count))); 2406 } 2407 2408 // === Shell support === 2409 2410 @Override 2411 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 2412 String[] args, ResultReceiver resultReceiver) throws RemoteException { 2413 2414 enforceShell(); 2415 2416 (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver); 2417 } 2418 2419 static class CommandException extends Exception { 2420 public CommandException(String message) { 2421 super(message); 2422 } 2423 } 2424 2425 /** 2426 * Handle "adb shell cmd". 2427 */ 2428 private class MyShellCommand extends ShellCommand { 2429 2430 private int mUserId = UserHandle.USER_SYSTEM; 2431 2432 private void parseOptions(boolean takeUser) 2433 throws CommandException { 2434 String opt; 2435 while ((opt = getNextOption()) != null) { 2436 switch (opt) { 2437 case "--user": 2438 if (takeUser) { 2439 mUserId = UserHandle.parseUserArg(getNextArgRequired()); 2440 break; 2441 } 2442 // fallthrough 2443 default: 2444 throw new CommandException("Unknown option: " + opt); 2445 } 2446 } 2447 } 2448 2449 @Override 2450 public int onCommand(String cmd) { 2451 if (cmd == null) { 2452 return handleDefaultCommands(cmd); 2453 } 2454 final PrintWriter pw = getOutPrintWriter(); 2455 try { 2456 switch (cmd) { 2457 case "reset-package-throttling": 2458 handleResetPackageThrottling(); 2459 break; 2460 case "reset-throttling": 2461 handleResetThrottling(); 2462 break; 2463 case "reset-all-throttling": 2464 handleResetAllThrottling(); 2465 break; 2466 case "override-config": 2467 handleOverrideConfig(); 2468 break; 2469 case "reset-config": 2470 handleResetConfig(); 2471 break; 2472 case "clear-default-launcher": 2473 handleClearDefaultLauncher(); 2474 break; 2475 case "get-default-launcher": 2476 handleGetDefaultLauncher(); 2477 break; 2478 case "refresh-default-launcher": 2479 handleRefreshDefaultLauncher(); 2480 break; 2481 case "unload-user": 2482 handleUnloadUser(); 2483 break; 2484 case "clear-shortcuts": 2485 handleClearShortcuts(); 2486 break; 2487 default: 2488 return handleDefaultCommands(cmd); 2489 } 2490 } catch (CommandException e) { 2491 pw.println("Error: " + e.getMessage()); 2492 return 1; 2493 } 2494 pw.println("Success"); 2495 return 0; 2496 } 2497 2498 @Override 2499 public void onHelp() { 2500 final PrintWriter pw = getOutPrintWriter(); 2501 pw.println("Usage: cmd shortcut COMMAND [options ...]"); 2502 pw.println(); 2503 pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE"); 2504 pw.println(" Reset throttling for a package"); 2505 pw.println(); 2506 pw.println("cmd shortcut reset-throttling [--user USER_ID]"); 2507 pw.println(" Reset throttling for all packages and users"); 2508 pw.println(); 2509 pw.println("cmd shortcut reset-all-throttling"); 2510 pw.println(" Reset the throttling state for all users"); 2511 pw.println(); 2512 pw.println("cmd shortcut override-config CONFIG"); 2513 pw.println(" Override the configuration for testing (will last until reboot)"); 2514 pw.println(); 2515 pw.println("cmd shortcut reset-config"); 2516 pw.println(" Reset the configuration set with \"update-config\""); 2517 pw.println(); 2518 pw.println("cmd shortcut clear-default-launcher [--user USER_ID]"); 2519 pw.println(" Clear the cached default launcher"); 2520 pw.println(); 2521 pw.println("cmd shortcut get-default-launcher [--user USER_ID]"); 2522 pw.println(" Show the cached default launcher"); 2523 pw.println(); 2524 pw.println("cmd shortcut refresh-default-launcher [--user USER_ID]"); 2525 pw.println(" Refresh the cached default launcher"); 2526 pw.println(); 2527 pw.println("cmd shortcut unload-user [--user USER_ID]"); 2528 pw.println(" Unload a user from the memory"); 2529 pw.println(" (This should not affect any observable behavior)"); 2530 pw.println(); 2531 pw.println("cmd shortcut clear-shortcuts [--user USER_ID] PACKAGE"); 2532 pw.println(" Remove all shortcuts from a package, including pinned shortcuts"); 2533 pw.println(); 2534 } 2535 2536 private void handleResetThrottling() throws CommandException { 2537 parseOptions(/* takeUser =*/ true); 2538 2539 Slog.i(TAG, "cmd: handleResetThrottling"); 2540 2541 resetThrottlingInner(mUserId); 2542 } 2543 2544 private void handleResetAllThrottling() { 2545 Slog.i(TAG, "cmd: handleResetAllThrottling"); 2546 2547 resetAllThrottlingInner(); 2548 } 2549 2550 private void handleResetPackageThrottling() throws CommandException { 2551 parseOptions(/* takeUser =*/ true); 2552 2553 final String packageName = getNextArgRequired(); 2554 2555 Slog.i(TAG, "cmd: handleResetPackageThrottling: " + packageName); 2556 2557 resetPackageThrottling(packageName, mUserId); 2558 } 2559 2560 private void handleOverrideConfig() throws CommandException { 2561 final String config = getNextArgRequired(); 2562 2563 Slog.i(TAG, "cmd: handleOverrideConfig: " + config); 2564 2565 synchronized (mLock) { 2566 if (!updateConfigurationLocked(config)) { 2567 throw new CommandException("override-config failed. See logcat for details."); 2568 } 2569 } 2570 } 2571 2572 private void handleResetConfig() { 2573 Slog.i(TAG, "cmd: handleResetConfig"); 2574 2575 synchronized (mLock) { 2576 loadConfigurationLocked(); 2577 } 2578 } 2579 2580 private void clearLauncher() { 2581 synchronized (mLock) { 2582 getUserShortcutsLocked(mUserId).setDefaultLauncherComponent(null); 2583 } 2584 } 2585 2586 private void showLauncher() { 2587 synchronized (mLock) { 2588 // This ensures to set the cached launcher. Package name doesn't matter. 2589 hasShortcutHostPermissionInner("-", mUserId); 2590 2591 getOutPrintWriter().println("Launcher: " 2592 + getUserShortcutsLocked(mUserId).getDefaultLauncherComponent()); 2593 } 2594 } 2595 2596 private void handleClearDefaultLauncher() throws CommandException { 2597 parseOptions(/* takeUser =*/ true); 2598 2599 clearLauncher(); 2600 } 2601 2602 private void handleGetDefaultLauncher() throws CommandException { 2603 parseOptions(/* takeUser =*/ true); 2604 2605 showLauncher(); 2606 } 2607 2608 private void handleRefreshDefaultLauncher() throws CommandException { 2609 parseOptions(/* takeUser =*/ true); 2610 2611 clearLauncher(); 2612 showLauncher(); 2613 } 2614 2615 private void handleUnloadUser() throws CommandException { 2616 parseOptions(/* takeUser =*/ true); 2617 2618 Slog.i(TAG, "cmd: handleUnloadUser: " + mUserId); 2619 2620 ShortcutService.this.handleCleanupUser(mUserId); 2621 } 2622 2623 private void handleClearShortcuts() throws CommandException { 2624 parseOptions(/* takeUser =*/ true); 2625 final String packageName = getNextArgRequired(); 2626 2627 Slog.i(TAG, "cmd: handleClearShortcuts: " + mUserId + ", " + packageName); 2628 2629 ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId); 2630 } 2631 } 2632 2633 // === Unit test support === 2634 2635 // Injection point. 2636 @VisibleForTesting 2637 long injectCurrentTimeMillis() { 2638 return System.currentTimeMillis(); 2639 } 2640 2641 @VisibleForTesting 2642 long injectElapsedRealtime() { 2643 return SystemClock.elapsedRealtime(); 2644 } 2645 2646 // Injection point. 2647 @VisibleForTesting 2648 int injectBinderCallingUid() { 2649 return getCallingUid(); 2650 } 2651 2652 private int getCallingUserId() { 2653 return UserHandle.getUserId(injectBinderCallingUid()); 2654 } 2655 2656 // Injection point. 2657 @VisibleForTesting 2658 long injectClearCallingIdentity() { 2659 return Binder.clearCallingIdentity(); 2660 } 2661 2662 // Injection point. 2663 @VisibleForTesting 2664 void injectRestoreCallingIdentity(long token) { 2665 Binder.restoreCallingIdentity(token); 2666 } 2667 2668 final void wtf(String message) { 2669 wtf( message, /* exception= */ null); 2670 } 2671 2672 // Injection point. 2673 void wtf(String message, Exception e) { 2674 Slog.wtf(TAG, message, e); 2675 } 2676 2677 @VisibleForTesting 2678 File injectSystemDataPath() { 2679 return Environment.getDataSystemDirectory(); 2680 } 2681 2682 @VisibleForTesting 2683 File injectUserDataPath(@UserIdInt int userId) { 2684 return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER); 2685 } 2686 2687 @VisibleForTesting 2688 boolean injectIsLowRamDevice() { 2689 return ActivityManager.isLowRamDeviceStatic(); 2690 } 2691 2692 @VisibleForTesting 2693 void injectRegisterUidObserver(IUidObserver observer, int which) { 2694 try { 2695 ActivityManagerNative.getDefault().registerUidObserver(observer, which); 2696 } catch (RemoteException shouldntHappen) { 2697 } 2698 } 2699 2700 @VisibleForTesting 2701 PackageManagerInternal injectPackageManagerInternal() { 2702 return mPackageManagerInternal; 2703 } 2704 2705 File getUserBitmapFilePath(@UserIdInt int userId) { 2706 return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS); 2707 } 2708 2709 @VisibleForTesting 2710 SparseArray<ShortcutUser> getShortcutsForTest() { 2711 return mUsers; 2712 } 2713 2714 @VisibleForTesting 2715 int getMaxDynamicShortcutsForTest() { 2716 return mMaxDynamicShortcuts; 2717 } 2718 2719 @VisibleForTesting 2720 int getMaxUpdatesPerIntervalForTest() { 2721 return mMaxUpdatesPerInterval; 2722 } 2723 2724 @VisibleForTesting 2725 long getResetIntervalForTest() { 2726 return mResetInterval; 2727 } 2728 2729 @VisibleForTesting 2730 int getMaxIconDimensionForTest() { 2731 return mMaxIconDimension; 2732 } 2733 2734 @VisibleForTesting 2735 CompressFormat getIconPersistFormatForTest() { 2736 return mIconPersistFormat; 2737 } 2738 2739 @VisibleForTesting 2740 int getIconPersistQualityForTest() { 2741 return mIconPersistQuality; 2742 } 2743 2744 @VisibleForTesting 2745 ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) { 2746 synchronized (mLock) { 2747 final ShortcutUser user = mUsers.get(userId); 2748 if (user == null) return null; 2749 2750 final ShortcutPackage pkg = user.getAllPackagesForTest().get(packageName); 2751 if (pkg == null) return null; 2752 2753 return pkg.findShortcutById(shortcutId); 2754 } 2755 } 2756} 2757