ShortcutService.java revision 905e8855e7df111f835fecde32598479058fe4df
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.AppGlobals; 23import android.content.ComponentName; 24import android.content.ContentProvider; 25import android.content.Context; 26import android.content.Intent; 27import android.content.pm.ApplicationInfo; 28import android.content.pm.IPackageManager; 29import android.content.pm.IShortcutService; 30import android.content.pm.LauncherApps; 31import android.content.pm.LauncherApps.ShortcutQuery; 32import android.content.pm.PackageInfo; 33import android.content.pm.PackageManager; 34import android.content.pm.PackageManager.NameNotFoundException; 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.BitmapFactory; 44import android.graphics.Canvas; 45import android.graphics.RectF; 46import android.graphics.drawable.Icon; 47import android.net.Uri; 48import android.os.Binder; 49import android.os.Environment; 50import android.os.Handler; 51import android.os.Looper; 52import android.os.ParcelFileDescriptor; 53import android.os.PersistableBundle; 54import android.os.Process; 55import android.os.RemoteException; 56import android.os.ResultReceiver; 57import android.os.SELinux; 58import android.os.ShellCommand; 59import android.os.UserHandle; 60import android.os.UserManager; 61import android.text.TextUtils; 62import android.text.format.Time; 63import android.util.ArrayMap; 64import android.util.ArraySet; 65import android.util.AtomicFile; 66import android.util.KeyValueListParser; 67import android.util.Slog; 68import android.util.SparseArray; 69import android.util.TypedValue; 70import android.util.Xml; 71 72import com.android.internal.annotations.GuardedBy; 73import com.android.internal.annotations.VisibleForTesting; 74import com.android.internal.content.PackageMonitor; 75import com.android.internal.os.BackgroundThread; 76import com.android.internal.util.ArrayUtils; 77import com.android.internal.util.FastXmlSerializer; 78import com.android.internal.util.Preconditions; 79import com.android.server.LocalServices; 80import com.android.server.SystemService; 81 82import libcore.io.IoUtils; 83 84import org.xmlpull.v1.XmlPullParser; 85import org.xmlpull.v1.XmlPullParserException; 86import org.xmlpull.v1.XmlSerializer; 87 88import java.io.File; 89import java.io.FileDescriptor; 90import java.io.FileInputStream; 91import java.io.FileNotFoundException; 92import java.io.FileOutputStream; 93import java.io.IOException; 94import java.io.InputStream; 95import java.io.PrintWriter; 96import java.net.URISyntaxException; 97import java.nio.charset.StandardCharsets; 98import java.util.ArrayList; 99import java.util.List; 100import java.util.function.Predicate; 101 102/** 103 * TODO: 104 * 105 * - Default launcher check does take a few ms. Worth caching. 106 * 107 * - Allow non-default launcher to start pinned shortcuts. (but not dynamic.) 108 * 109 * - Extract the user/package/launcher classes to their own files. Maybe rename so they all have 110 * the same "Shortcut" prefix. 111 * 112 * - Listen to PACKAGE_*, remove orphan info, update timestamp for icon res 113 * -> Need to scan all packages when a user starts too. 114 * -> Clear data -> remove all dynamic? but not the pinned? 115 * 116 * - Scan and remove orphan bitmaps (just in case). 117 * 118 * - Backup & restore 119 * 120 * - Detect when already registered instances are passed to APIs again, which might break 121 * internal bitmap handling. 122 */ 123public class ShortcutService extends IShortcutService.Stub { 124 static final String TAG = "ShortcutService"; 125 126 static final boolean DEBUG = false; // STOPSHIP if true 127 static final boolean DEBUG_LOAD = false; // STOPSHIP if true 128 static final boolean ENABLE_DEBUG_COMMAND = true; // STOPSHIP if true 129 130 @VisibleForTesting 131 static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day 132 133 @VisibleForTesting 134 static final int DEFAULT_MAX_DAILY_UPDATES = 10; 135 136 @VisibleForTesting 137 static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5; 138 139 @VisibleForTesting 140 static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96; 141 142 @VisibleForTesting 143 static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48; 144 145 @VisibleForTesting 146 static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name(); 147 148 @VisibleForTesting 149 static final int DEFAULT_ICON_PERSIST_QUALITY = 100; 150 151 @VisibleForTesting 152 static final int DEFAULT_SAVE_DELAY_MS = 3000; 153 154 @VisibleForTesting 155 static final String FILENAME_BASE_STATE = "shortcut_service.xml"; 156 157 @VisibleForTesting 158 static final String DIRECTORY_PER_USER = "shortcut_service"; 159 160 @VisibleForTesting 161 static final String FILENAME_USER_PACKAGES = "shortcuts.xml"; 162 163 static final String DIRECTORY_BITMAPS = "bitmaps"; 164 165 private static final String TAG_ROOT = "root"; 166 private static final String TAG_LAST_RESET_TIME = "last_reset_time"; 167 168 private static final String ATTR_VALUE = "value"; 169 170 @VisibleForTesting 171 interface ConfigConstants { 172 /** 173 * Key name for the save delay, in milliseconds. (int) 174 */ 175 String KEY_SAVE_DELAY_MILLIS = "save_delay_ms"; 176 177 /** 178 * Key name for the throttling reset interval, in seconds. (long) 179 */ 180 String KEY_RESET_INTERVAL_SEC = "reset_interval_sec"; 181 182 /** 183 * Key name for the max number of modifying API calls per app for every interval. (int) 184 */ 185 String KEY_MAX_DAILY_UPDATES = "max_daily_updates"; 186 187 /** 188 * Key name for the max icon dimensions in DP, for non-low-memory devices. 189 */ 190 String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp"; 191 192 /** 193 * Key name for the max icon dimensions in DP, for low-memory devices. 194 */ 195 String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram"; 196 197 /** 198 * Key name for the max dynamic shortcuts per app. (int) 199 */ 200 String KEY_MAX_SHORTCUTS = "max_shortcuts"; 201 202 /** 203 * Key name for icon compression quality, 0-100. 204 */ 205 String KEY_ICON_QUALITY = "icon_quality"; 206 207 /** 208 * Key name for icon compression format: "PNG", "JPEG" or "WEBP" 209 */ 210 String KEY_ICON_FORMAT = "icon_format"; 211 } 212 213 final Context mContext; 214 215 private final Object mLock = new Object(); 216 217 private final Handler mHandler; 218 219 @GuardedBy("mLock") 220 private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1); 221 222 @GuardedBy("mLock") 223 private long mRawLastResetTime; 224 225 /** 226 * User ID -> UserShortcuts 227 */ 228 @GuardedBy("mLock") 229 private final SparseArray<ShortcutUser> mUsers = new SparseArray<>(); 230 231 /** 232 * Max number of dynamic shortcuts that each application can have at a time. 233 */ 234 private int mMaxDynamicShortcuts; 235 236 /** 237 * Max number of updating API calls that each application can make a day. 238 */ 239 int mMaxDailyUpdates; 240 241 /** 242 * Actual throttling-reset interval. By default it's a day. 243 */ 244 private long mResetInterval; 245 246 /** 247 * Icon max width/height in pixels. 248 */ 249 private int mMaxIconDimension; 250 251 private CompressFormat mIconPersistFormat; 252 private int mIconPersistQuality; 253 254 private int mSaveDelayMillis; 255 256 private final IPackageManager mIPackageManager; 257 private final PackageManagerInternal mPackageManagerInternal; 258 private final UserManager mUserManager; 259 260 @GuardedBy("mLock") 261 private List<Integer> mDirtyUserIds = new ArrayList<>(); 262 263 private static final int PACKAGE_MATCH_FLAGS = 264 PackageManager.MATCH_DIRECT_BOOT_AWARE 265 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 266 | PackageManager.MATCH_UNINSTALLED_PACKAGES; 267 268 public ShortcutService(Context context) { 269 this(context, BackgroundThread.get().getLooper()); 270 } 271 272 @VisibleForTesting 273 ShortcutService(Context context, Looper looper) { 274 mContext = Preconditions.checkNotNull(context); 275 LocalServices.addService(ShortcutServiceInternal.class, new LocalService()); 276 mHandler = new Handler(looper); 277 mIPackageManager = AppGlobals.getPackageManager(); 278 mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); 279 mUserManager = context.getSystemService(UserManager.class); 280 281 mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false); 282 } 283 284 /** 285 * System service lifecycle. 286 */ 287 public static final class Lifecycle extends SystemService { 288 final ShortcutService mService; 289 290 public Lifecycle(Context context) { 291 super(context); 292 mService = new ShortcutService(context); 293 } 294 295 @Override 296 public void onStart() { 297 publishBinderService(Context.SHORTCUT_SERVICE, mService); 298 } 299 300 @Override 301 public void onBootPhase(int phase) { 302 mService.onBootPhase(phase); 303 } 304 305 @Override 306 public void onCleanupUser(int userHandle) { 307 mService.handleCleanupUser(userHandle); 308 } 309 310 @Override 311 public void onUnlockUser(int userId) { 312 mService.handleUnlockUser(userId); 313 } 314 } 315 316 /** lifecycle event */ 317 void onBootPhase(int phase) { 318 if (DEBUG) { 319 Slog.d(TAG, "onBootPhase: " + phase); 320 } 321 switch (phase) { 322 case SystemService.PHASE_LOCK_SETTINGS_READY: 323 initialize(); 324 break; 325 } 326 } 327 328 /** lifecycle event */ 329 void handleUnlockUser(int userId) { 330 synchronized (mLock) { 331 // Preload 332 getUserShortcutsLocked(userId); 333 334 cleanupGonePackages(userId); 335 } 336 } 337 338 /** lifecycle event */ 339 void handleCleanupUser(int userId) { 340 synchronized (mLock) { 341 unloadUserLocked(userId); 342 } 343 } 344 345 private void unloadUserLocked(int userId) { 346 if (DEBUG) { 347 Slog.d(TAG, "unloadUserLocked: user=" + userId); 348 } 349 // Save all dirty information. 350 saveDirtyInfo(); 351 352 // Unload 353 mUsers.delete(userId); 354 } 355 356 /** Return the base state file name */ 357 private AtomicFile getBaseStateFile() { 358 final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE); 359 path.mkdirs(); 360 return new AtomicFile(path); 361 } 362 363 /** 364 * Init the instance. (load the state file, etc) 365 */ 366 private void initialize() { 367 synchronized (mLock) { 368 loadConfigurationLocked(); 369 loadBaseStateLocked(); 370 } 371 } 372 373 /** 374 * Load the configuration from Settings. 375 */ 376 private void loadConfigurationLocked() { 377 updateConfigurationLocked(injectShortcutManagerConstants()); 378 } 379 380 /** 381 * Load the configuration from Settings. 382 */ 383 @VisibleForTesting 384 boolean updateConfigurationLocked(String config) { 385 boolean result = true; 386 387 final KeyValueListParser parser = new KeyValueListParser(','); 388 try { 389 parser.setString(config); 390 } catch (IllegalArgumentException e) { 391 // Failed to parse the settings string, log this and move on 392 // with defaults. 393 Slog.e(TAG, "Bad shortcut manager settings", e); 394 result = false; 395 } 396 397 mSaveDelayMillis = (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS, 398 DEFAULT_SAVE_DELAY_MS); 399 400 mResetInterval = parser.getLong( 401 ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC) 402 * 1000L; 403 404 mMaxDailyUpdates = (int) parser.getLong( 405 ConfigConstants.KEY_MAX_DAILY_UPDATES, DEFAULT_MAX_DAILY_UPDATES); 406 407 mMaxDynamicShortcuts = (int) parser.getLong( 408 ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP); 409 410 final int iconDimensionDp = injectIsLowRamDevice() 411 ? (int) parser.getLong( 412 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM, 413 DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP) 414 : (int) parser.getLong( 415 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP, 416 DEFAULT_MAX_ICON_DIMENSION_DP); 417 418 mMaxIconDimension = injectDipToPixel(iconDimensionDp); 419 420 mIconPersistFormat = CompressFormat.valueOf( 421 parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT)); 422 423 mIconPersistQuality = (int) parser.getLong( 424 ConfigConstants.KEY_ICON_QUALITY, 425 DEFAULT_ICON_PERSIST_QUALITY); 426 427 return result; 428 } 429 430 @VisibleForTesting 431 String injectShortcutManagerConstants() { 432 return android.provider.Settings.Global.getString( 433 mContext.getContentResolver(), 434 android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS); 435 } 436 437 @VisibleForTesting 438 int injectDipToPixel(int dip) { 439 return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, 440 mContext.getResources().getDisplayMetrics()); 441 } 442 443 // === Persisting === 444 445 @Nullable 446 static String parseStringAttribute(XmlPullParser parser, String attribute) { 447 return parser.getAttributeValue(null, attribute); 448 } 449 450 static boolean parseBooleanAttribute(XmlPullParser parser, String attribute) { 451 return parseLongAttribute(parser, attribute) == 1; 452 } 453 454 static int parseIntAttribute(XmlPullParser parser, String attribute) { 455 return (int) parseLongAttribute(parser, attribute); 456 } 457 458 static long parseLongAttribute(XmlPullParser parser, String attribute) { 459 final String value = parseStringAttribute(parser, attribute); 460 if (TextUtils.isEmpty(value)) { 461 return 0; 462 } 463 try { 464 return Long.parseLong(value); 465 } catch (NumberFormatException e) { 466 Slog.e(TAG, "Error parsing long " + value); 467 return 0; 468 } 469 } 470 471 @Nullable 472 static ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) { 473 final String value = parseStringAttribute(parser, attribute); 474 if (TextUtils.isEmpty(value)) { 475 return null; 476 } 477 return ComponentName.unflattenFromString(value); 478 } 479 480 @Nullable 481 static Intent parseIntentAttribute(XmlPullParser parser, String attribute) { 482 final String value = parseStringAttribute(parser, attribute); 483 if (TextUtils.isEmpty(value)) { 484 return null; 485 } 486 try { 487 return Intent.parseUri(value, /* flags =*/ 0); 488 } catch (URISyntaxException e) { 489 Slog.e(TAG, "Error parsing intent", e); 490 return null; 491 } 492 } 493 494 static void writeTagValue(XmlSerializer out, String tag, String value) throws IOException { 495 if (TextUtils.isEmpty(value)) return; 496 497 out.startTag(null, tag); 498 out.attribute(null, ATTR_VALUE, value); 499 out.endTag(null, tag); 500 } 501 502 static void writeTagValue(XmlSerializer out, String tag, long value) throws IOException { 503 writeTagValue(out, tag, Long.toString(value)); 504 } 505 506 static void writeTagValue(XmlSerializer out, String tag, ComponentName name) throws IOException { 507 if (name == null) return; 508 writeTagValue(out, tag, name.flattenToString()); 509 } 510 511 static void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle) 512 throws IOException, XmlPullParserException { 513 if (bundle == null) return; 514 515 out.startTag(null, tag); 516 bundle.saveToXml(out); 517 out.endTag(null, tag); 518 } 519 520 static void writeAttr(XmlSerializer out, String name, String value) throws IOException { 521 if (TextUtils.isEmpty(value)) return; 522 523 out.attribute(null, name, value); 524 } 525 526 static void writeAttr(XmlSerializer out, String name, long value) throws IOException { 527 writeAttr(out, name, String.valueOf(value)); 528 } 529 530 static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException { 531 if (value) { 532 writeAttr(out, name, "1"); 533 } 534 } 535 536 static void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException { 537 if (comp == null) return; 538 writeAttr(out, name, comp.flattenToString()); 539 } 540 541 static void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException { 542 if (intent == null) return; 543 544 writeAttr(out, name, intent.toUri(/* flags =*/ 0)); 545 } 546 547 @VisibleForTesting 548 void saveBaseStateLocked() { 549 final AtomicFile file = getBaseStateFile(); 550 if (DEBUG) { 551 Slog.d(TAG, "Saving to " + file.getBaseFile()); 552 } 553 554 FileOutputStream outs = null; 555 try { 556 outs = file.startWrite(); 557 558 // Write to XML 559 XmlSerializer out = new FastXmlSerializer(); 560 out.setOutput(outs, StandardCharsets.UTF_8.name()); 561 out.startDocument(null, true); 562 out.startTag(null, TAG_ROOT); 563 564 // Body. 565 writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime); 566 567 // Epilogue. 568 out.endTag(null, TAG_ROOT); 569 out.endDocument(); 570 571 // Close. 572 file.finishWrite(outs); 573 } catch (IOException e) { 574 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); 575 file.failWrite(outs); 576 } 577 } 578 579 private void loadBaseStateLocked() { 580 mRawLastResetTime = 0; 581 582 final AtomicFile file = getBaseStateFile(); 583 if (DEBUG) { 584 Slog.d(TAG, "Loading from " + file.getBaseFile()); 585 } 586 try (FileInputStream in = file.openRead()) { 587 XmlPullParser parser = Xml.newPullParser(); 588 parser.setInput(in, StandardCharsets.UTF_8.name()); 589 590 int type; 591 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 592 if (type != XmlPullParser.START_TAG) { 593 continue; 594 } 595 final int depth = parser.getDepth(); 596 // Check the root tag 597 final String tag = parser.getName(); 598 if (depth == 1) { 599 if (!TAG_ROOT.equals(tag)) { 600 Slog.e(TAG, "Invalid root tag: " + tag); 601 return; 602 } 603 continue; 604 } 605 // Assume depth == 2 606 switch (tag) { 607 case TAG_LAST_RESET_TIME: 608 mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE); 609 break; 610 default: 611 Slog.e(TAG, "Invalid tag: " + tag); 612 break; 613 } 614 } 615 } catch (FileNotFoundException e) { 616 // Use the default 617 } catch (IOException|XmlPullParserException e) { 618 Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); 619 620 mRawLastResetTime = 0; 621 } 622 // Adjust the last reset time. 623 getLastResetTimeLocked(); 624 } 625 626 private void saveUserLocked(@UserIdInt int userId) { 627 final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); 628 if (DEBUG) { 629 Slog.d(TAG, "Saving to " + path); 630 } 631 path.mkdirs(); 632 final AtomicFile file = new AtomicFile(path); 633 FileOutputStream outs = null; 634 try { 635 outs = file.startWrite(); 636 637 // Write to XML 638 XmlSerializer out = new FastXmlSerializer(); 639 out.setOutput(outs, StandardCharsets.UTF_8.name()); 640 out.startDocument(null, true); 641 642 getUserShortcutsLocked(userId).saveToXml(this, out, /* forBackup= */ false); 643 644 out.endDocument(); 645 646 // Close. 647 file.finishWrite(outs); 648 } catch (IOException|XmlPullParserException e) { 649 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); 650 file.failWrite(outs); 651 } 652 } 653 654 static IOException throwForInvalidTag(int depth, String tag) throws IOException { 655 throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth)); 656 } 657 658 @Nullable 659 private ShortcutUser loadUserLocked(@UserIdInt int userId) { 660 final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); 661 if (DEBUG) { 662 Slog.d(TAG, "Loading from " + path); 663 } 664 final AtomicFile file = new AtomicFile(path); 665 666 final FileInputStream in; 667 try { 668 in = file.openRead(); 669 } catch (FileNotFoundException e) { 670 if (DEBUG) { 671 Slog.d(TAG, "Not found " + path); 672 } 673 return null; 674 } 675 ShortcutUser ret = null; 676 try { 677 XmlPullParser parser = Xml.newPullParser(); 678 parser.setInput(in, StandardCharsets.UTF_8.name()); 679 680 int type; 681 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 682 if (type != XmlPullParser.START_TAG) { 683 continue; 684 } 685 final int depth = parser.getDepth(); 686 687 final String tag = parser.getName(); 688 if (DEBUG_LOAD) { 689 Slog.d(TAG, String.format("depth=%d type=%d name=%s", 690 depth, type, tag)); 691 } 692 if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) { 693 ret = ShortcutUser.loadFromXml(parser, userId); 694 continue; 695 } 696 throwForInvalidTag(depth, tag); 697 } 698 return ret; 699 } catch (IOException|XmlPullParserException e) { 700 Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); 701 return null; 702 } finally { 703 IoUtils.closeQuietly(in); 704 } 705 } 706 707 private void scheduleSaveBaseState() { 708 scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state. 709 } 710 711 void scheduleSaveUser(@UserIdInt int userId) { 712 scheduleSaveInner(userId); 713 } 714 715 // In order to re-schedule, we need to reuse the same instance, so keep it in final. 716 private final Runnable mSaveDirtyInfoRunner = this::saveDirtyInfo; 717 718 private void scheduleSaveInner(@UserIdInt int userId) { 719 if (DEBUG) { 720 Slog.d(TAG, "Scheduling to save for " + userId); 721 } 722 synchronized (mLock) { 723 if (!mDirtyUserIds.contains(userId)) { 724 mDirtyUserIds.add(userId); 725 } 726 } 727 // If already scheduled, remove that and re-schedule in N seconds. 728 mHandler.removeCallbacks(mSaveDirtyInfoRunner); 729 mHandler.postDelayed(mSaveDirtyInfoRunner, mSaveDelayMillis); 730 } 731 732 @VisibleForTesting 733 void saveDirtyInfo() { 734 if (DEBUG) { 735 Slog.d(TAG, "saveDirtyInfo"); 736 } 737 synchronized (mLock) { 738 for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) { 739 final int userId = mDirtyUserIds.get(i); 740 if (userId == UserHandle.USER_NULL) { // USER_NULL for base state. 741 saveBaseStateLocked(); 742 } else { 743 saveUserLocked(userId); 744 } 745 } 746 mDirtyUserIds.clear(); 747 } 748 } 749 750 /** Return the last reset time. */ 751 long getLastResetTimeLocked() { 752 updateTimesLocked(); 753 return mRawLastResetTime; 754 } 755 756 /** Return the next reset time. */ 757 long getNextResetTimeLocked() { 758 updateTimesLocked(); 759 return mRawLastResetTime + mResetInterval; 760 } 761 762 static boolean isClockValid(long time) { 763 return time >= 1420070400; // Thu, 01 Jan 2015 00:00:00 GMT 764 } 765 766 /** 767 * Update the last reset time. 768 */ 769 private void updateTimesLocked() { 770 771 final long now = injectCurrentTimeMillis(); 772 773 final long prevLastResetTime = mRawLastResetTime; 774 775 if (mRawLastResetTime == 0) { // first launch. 776 // TODO Randomize?? 777 mRawLastResetTime = now; 778 } else if (now < mRawLastResetTime) { 779 // Clock rewound. 780 if (isClockValid(now)) { 781 Slog.w(TAG, "Clock rewound"); 782 // TODO Randomize?? 783 mRawLastResetTime = now; 784 } 785 } else { 786 if ((mRawLastResetTime + mResetInterval) <= now) { 787 final long offset = mRawLastResetTime % mResetInterval; 788 mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset; 789 } 790 } 791 if (prevLastResetTime != mRawLastResetTime) { 792 scheduleSaveBaseState(); 793 } 794 } 795 796 @GuardedBy("mLock") 797 @NonNull 798 boolean isUserLoadedLocked(@UserIdInt int userId) { 799 return mUsers.get(userId) != null; 800 } 801 802 /** Return the per-user state. */ 803 @GuardedBy("mLock") 804 @NonNull 805 ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) { 806 ShortcutUser userPackages = mUsers.get(userId); 807 if (userPackages == null) { 808 userPackages = loadUserLocked(userId); 809 if (userPackages == null) { 810 userPackages = new ShortcutUser(userId); 811 } 812 mUsers.put(userId, userPackages); 813 } 814 return userPackages; 815 } 816 817 /** Return the per-user per-package state. */ 818 @GuardedBy("mLock") 819 @NonNull 820 ShortcutPackage getPackageShortcutsLocked( 821 @NonNull String packageName, @UserIdInt int userId) { 822 return getUserShortcutsLocked(userId).getPackageShortcuts(packageName); 823 } 824 825 @GuardedBy("mLock") 826 @NonNull 827 ShortcutLauncher getLauncherShortcuts( 828 @NonNull String packageName, @UserIdInt int userId) { 829 return getUserShortcutsLocked(userId).getLauncherShortcuts(packageName); 830 } 831 832 // === Caller validation === 833 834 void removeIcon(@UserIdInt int userId, ShortcutInfo shortcut) { 835 if (shortcut.getBitmapPath() != null) { 836 if (DEBUG) { 837 Slog.d(TAG, "Removing " + shortcut.getBitmapPath()); 838 } 839 new File(shortcut.getBitmapPath()).delete(); 840 841 shortcut.setBitmapPath(null); 842 shortcut.setIconResourceId(0); 843 shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES); 844 } 845 } 846 847 @VisibleForTesting 848 static class FileOutputStreamWithPath extends FileOutputStream { 849 private final File mFile; 850 851 public FileOutputStreamWithPath(File file) throws FileNotFoundException { 852 super(file); 853 mFile = file; 854 } 855 856 public File getFile() { 857 return mFile; 858 } 859 } 860 861 /** 862 * Build the cached bitmap filename for a shortcut icon. 863 * 864 * The filename will be based on the ID, except certain characters will be escaped. 865 */ 866 @VisibleForTesting 867 FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut) 868 throws IOException { 869 final File packagePath = new File(getUserBitmapFilePath(userId), 870 shortcut.getPackageName()); 871 if (!packagePath.isDirectory()) { 872 packagePath.mkdirs(); 873 if (!packagePath.isDirectory()) { 874 throw new IOException("Unable to create directory " + packagePath); 875 } 876 SELinux.restorecon(packagePath); 877 } 878 879 final String baseName = String.valueOf(injectCurrentTimeMillis()); 880 for (int suffix = 0;; suffix++) { 881 final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png"; 882 final File file = new File(packagePath, filename); 883 if (!file.exists()) { 884 if (DEBUG) { 885 Slog.d(TAG, "Saving icon to " + file.getAbsolutePath()); 886 } 887 return new FileOutputStreamWithPath(file); 888 } 889 } 890 } 891 892 void saveIconAndFixUpShortcut(@UserIdInt int userId, ShortcutInfo shortcut) { 893 if (shortcut.hasIconFile() || shortcut.hasIconResource()) { 894 return; 895 } 896 897 final long token = injectClearCallingIdentity(); 898 try { 899 // Clear icon info on the shortcut. 900 shortcut.setIconResourceId(0); 901 shortcut.setBitmapPath(null); 902 903 final Icon icon = shortcut.getIcon(); 904 if (icon == null) { 905 return; // has no icon 906 } 907 908 Bitmap bitmap = null; 909 try { 910 switch (icon.getType()) { 911 case Icon.TYPE_RESOURCE: { 912 injectValidateIconResPackage(shortcut, icon); 913 914 shortcut.setIconResourceId(icon.getResId()); 915 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES); 916 return; 917 } 918 case Icon.TYPE_BITMAP: { 919 bitmap = icon.getBitmap(); 920 break; 921 } 922 case Icon.TYPE_URI: { 923 final Uri uri = ContentProvider.maybeAddUserId(icon.getUri(), userId); 924 925 try (InputStream is = mContext.getContentResolver().openInputStream(uri)) { 926 927 bitmap = BitmapFactory.decodeStream(is); 928 929 } catch (IOException e) { 930 Slog.e(TAG, "Unable to load icon from " + uri); 931 return; 932 } 933 break; 934 } 935 default: 936 // This shouldn't happen because we've already validated the icon, but 937 // just in case. 938 throw ShortcutInfo.getInvalidIconException(); 939 } 940 if (bitmap == null) { 941 Slog.e(TAG, "Null bitmap detected"); 942 return; 943 } 944 // Shrink and write to the file. 945 File path = null; 946 try { 947 final FileOutputStreamWithPath out = openIconFileForWrite(userId, shortcut); 948 try { 949 path = out.getFile(); 950 951 shrinkBitmap(bitmap, mMaxIconDimension) 952 .compress(mIconPersistFormat, mIconPersistQuality, out); 953 954 shortcut.setBitmapPath(out.getFile().getAbsolutePath()); 955 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_FILE); 956 } finally { 957 IoUtils.closeQuietly(out); 958 } 959 } catch (IOException|RuntimeException e) { 960 // STOPSHIP Change wtf to e 961 Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e); 962 if (path != null && path.exists()) { 963 path.delete(); 964 } 965 } 966 } finally { 967 if (bitmap != null) { 968 bitmap.recycle(); 969 } 970 // Once saved, we won't use the original icon information, so null it out. 971 shortcut.clearIcon(); 972 } 973 } finally { 974 injectRestoreCallingIdentity(token); 975 } 976 } 977 978 // Unfortunately we can't do this check in unit tests because we fake creator package names, 979 // so override in unit tests. 980 // TODO CTS this case. 981 void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) { 982 if (!shortcut.getPackageName().equals(icon.getResPackage())) { 983 throw new IllegalArgumentException( 984 "Icon resource must reside in shortcut owner package"); 985 } 986 } 987 988 @VisibleForTesting 989 static Bitmap shrinkBitmap(Bitmap in, int maxSize) { 990 // Original width/height. 991 final int ow = in.getWidth(); 992 final int oh = in.getHeight(); 993 if ((ow <= maxSize) && (oh <= maxSize)) { 994 if (DEBUG) { 995 Slog.d(TAG, String.format("Icon size %dx%d, no need to shrink", ow, oh)); 996 } 997 return in; 998 } 999 final int longerDimension = Math.max(ow, oh); 1000 1001 // New width and height. 1002 final int nw = ow * maxSize / longerDimension; 1003 final int nh = oh * maxSize / longerDimension; 1004 if (DEBUG) { 1005 Slog.d(TAG, String.format("Icon size %dx%d, shrinking to %dx%d", 1006 ow, oh, nw, nh)); 1007 } 1008 1009 final Bitmap scaledBitmap = Bitmap.createBitmap(nw, nh, Bitmap.Config.ARGB_8888); 1010 final Canvas c = new Canvas(scaledBitmap); 1011 1012 final RectF dst = new RectF(0, 0, nw, nh); 1013 1014 c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null); 1015 1016 in.recycle(); 1017 1018 return scaledBitmap; 1019 } 1020 1021 // === Caller validation === 1022 1023 private boolean isCallerSystem() { 1024 final int callingUid = injectBinderCallingUid(); 1025 return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID); 1026 } 1027 1028 private boolean isCallerShell() { 1029 final int callingUid = injectBinderCallingUid(); 1030 return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID; 1031 } 1032 1033 private void enforceSystemOrShell() { 1034 Preconditions.checkState(isCallerSystem() || isCallerShell(), 1035 "Caller must be system or shell"); 1036 } 1037 1038 private void enforceShell() { 1039 Preconditions.checkState(isCallerShell(), "Caller must be shell"); 1040 } 1041 1042 private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) { 1043 Preconditions.checkStringNotEmpty(packageName, "packageName"); 1044 1045 if (isCallerSystem()) { 1046 return; // no check 1047 } 1048 1049 final int callingUid = injectBinderCallingUid(); 1050 1051 // Otherwise, make sure the arguments are valid. 1052 if (UserHandle.getUserId(callingUid) != userId) { 1053 throw new SecurityException("Invalid user-ID"); 1054 } 1055 if (injectGetPackageUid(packageName, userId) == injectBinderCallingUid()) { 1056 return; // Caller is valid. 1057 } 1058 throw new SecurityException("Caller UID= doesn't own " + packageName); 1059 } 1060 1061 void postToHandler(Runnable r) { 1062 mHandler.post(r); 1063 } 1064 1065 /** 1066 * Throw if {@code numShortcuts} is bigger than {@link #mMaxDynamicShortcuts}. 1067 */ 1068 void enforceMaxDynamicShortcuts(int numShortcuts) { 1069 if (numShortcuts > mMaxDynamicShortcuts) { 1070 throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded"); 1071 } 1072 } 1073 1074 /** 1075 * - Sends a notification to LauncherApps 1076 * - Write to file 1077 */ 1078 private void userPackageChanged(@NonNull String packageName, @UserIdInt int userId) { 1079 notifyListeners(packageName, userId); 1080 scheduleSaveUser(userId); 1081 } 1082 1083 private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) { 1084 if (!mUserManager.isUserRunning(userId)) { 1085 return; 1086 } 1087 postToHandler(() -> { 1088 final ArrayList<ShortcutChangeListener> copy; 1089 synchronized (mLock) { 1090 copy = new ArrayList<>(mListeners); 1091 } 1092 // Note onShortcutChanged() needs to be called with the system service permissions. 1093 for (int i = copy.size() - 1; i >= 0; i--) { 1094 copy.get(i).onShortcutChanged(packageName, userId); 1095 } 1096 }); 1097 } 1098 1099 /** 1100 * Clean up / validate an incoming shortcut. 1101 * - Make sure all mandatory fields are set. 1102 * - Make sure the intent's extras are persistable, and them to set 1103 * {@link ShortcutInfo#mIntentPersistableExtras}. Also clear its extras. 1104 * - Clear flags. 1105 * 1106 * TODO Detailed unit tests 1107 */ 1108 private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) { 1109 Preconditions.checkNotNull(shortcut, "Null shortcut detected"); 1110 if (shortcut.getActivityComponent() != null) { 1111 Preconditions.checkState( 1112 shortcut.getPackageName().equals( 1113 shortcut.getActivityComponent().getPackageName()), 1114 "Activity package name mismatch"); 1115 } 1116 1117 if (!forUpdate) { 1118 shortcut.enforceMandatoryFields(); 1119 } 1120 if (shortcut.getIcon() != null) { 1121 ShortcutInfo.validateIcon(shortcut.getIcon()); 1122 } 1123 1124 validateForXml(shortcut.getId()); 1125 validateForXml(shortcut.getTitle()); 1126 validatePersistableBundleForXml(shortcut.getIntentPersistableExtras()); 1127 validatePersistableBundleForXml(shortcut.getExtras()); 1128 1129 shortcut.replaceFlags(0); 1130 } 1131 1132 // KXmlSerializer is strict and doesn't allow certain characters, so we disallow those 1133 // characters. 1134 1135 private static void validatePersistableBundleForXml(PersistableBundle b) { 1136 if (b == null || b.size() == 0) { 1137 return; 1138 } 1139 for (String key : b.keySet()) { 1140 validateForXml(key); 1141 final Object value = b.get(key); 1142 if (value == null) { 1143 continue; 1144 } else if (value instanceof String) { 1145 validateForXml((String) value); 1146 } else if (value instanceof String[]) { 1147 for (String v : (String[]) value) { 1148 validateForXml(v); 1149 } 1150 } else if (value instanceof PersistableBundle) { 1151 validatePersistableBundleForXml((PersistableBundle) value); 1152 } 1153 } 1154 } 1155 1156 private static void validateForXml(String s) { 1157 if (TextUtils.isEmpty(s)) { 1158 return; 1159 } 1160 for (int i = s.length() - 1; i >= 0; i--) { 1161 if (!isAllowedInXml(s.charAt(i))) { 1162 throw new IllegalArgumentException("Unsupported character detected in: " + s); 1163 } 1164 } 1165 } 1166 1167 private static boolean isAllowedInXml(char c) { 1168 return (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd); 1169 } 1170 1171 // === APIs === 1172 1173 @Override 1174 public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, 1175 @UserIdInt int userId) { 1176 verifyCaller(packageName, userId); 1177 1178 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); 1179 final int size = newShortcuts.size(); 1180 1181 synchronized (mLock) { 1182 getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId); 1183 1184 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); 1185 1186 // Throttling. 1187 if (!ps.tryApiCall(this)) { 1188 return false; 1189 } 1190 enforceMaxDynamicShortcuts(size); 1191 1192 // Validate the shortcuts. 1193 for (int i = 0; i < size; i++) { 1194 fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false); 1195 } 1196 1197 // First, remove all un-pinned; dynamic shortcuts 1198 ps.deleteAllDynamicShortcuts(this); 1199 1200 // Then, add/update all. We need to make sure to take over "pinned" flag. 1201 for (int i = 0; i < size; i++) { 1202 final ShortcutInfo newShortcut = newShortcuts.get(i); 1203 ps.addDynamicShortcut(this, newShortcut); 1204 } 1205 } 1206 userPackageChanged(packageName, userId); 1207 return true; 1208 } 1209 1210 @Override 1211 public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, 1212 @UserIdInt int userId) { 1213 verifyCaller(packageName, userId); 1214 1215 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); 1216 final int size = newShortcuts.size(); 1217 1218 synchronized (mLock) { 1219 getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId); 1220 1221 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); 1222 1223 // Throttling. 1224 if (!ps.tryApiCall(this)) { 1225 return false; 1226 } 1227 1228 for (int i = 0; i < size; i++) { 1229 final ShortcutInfo source = newShortcuts.get(i); 1230 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true); 1231 1232 final ShortcutInfo target = ps.findShortcutById(source.getId()); 1233 if (target != null) { 1234 final boolean replacingIcon = (source.getIcon() != null); 1235 if (replacingIcon) { 1236 removeIcon(userId, target); 1237 } 1238 1239 target.copyNonNullFieldsFrom(source); 1240 1241 if (replacingIcon) { 1242 saveIconAndFixUpShortcut(userId, target); 1243 } 1244 } 1245 } 1246 } 1247 userPackageChanged(packageName, userId); 1248 1249 return true; 1250 } 1251 1252 @Override 1253 public boolean addDynamicShortcut(String packageName, ShortcutInfo newShortcut, 1254 @UserIdInt int userId) { 1255 verifyCaller(packageName, userId); 1256 1257 synchronized (mLock) { 1258 getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId); 1259 1260 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); 1261 1262 // Throttling. 1263 if (!ps.tryApiCall(this)) { 1264 return false; 1265 } 1266 1267 // Validate the shortcut. 1268 fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false); 1269 1270 // Add it. 1271 ps.addDynamicShortcut(this, newShortcut); 1272 } 1273 userPackageChanged(packageName, userId); 1274 1275 return true; 1276 } 1277 1278 @Override 1279 public void deleteDynamicShortcut(String packageName, String shortcutId, 1280 @UserIdInt int userId) { 1281 verifyCaller(packageName, userId); 1282 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId must be provided"); 1283 1284 synchronized (mLock) { 1285 getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(this, shortcutId); 1286 } 1287 userPackageChanged(packageName, userId); 1288 } 1289 1290 @Override 1291 public void deleteAllDynamicShortcuts(String packageName, @UserIdInt int userId) { 1292 verifyCaller(packageName, userId); 1293 1294 synchronized (mLock) { 1295 getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(this); 1296 } 1297 userPackageChanged(packageName, userId); 1298 } 1299 1300 @Override 1301 public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName, 1302 @UserIdInt int userId) { 1303 verifyCaller(packageName, userId); 1304 synchronized (mLock) { 1305 return getShortcutsWithQueryLocked( 1306 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, 1307 ShortcutInfo::isDynamic); 1308 } 1309 } 1310 1311 @Override 1312 public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName, 1313 @UserIdInt int userId) { 1314 verifyCaller(packageName, userId); 1315 synchronized (mLock) { 1316 return getShortcutsWithQueryLocked( 1317 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, 1318 ShortcutInfo::isPinned); 1319 } 1320 } 1321 1322 private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName, 1323 @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) { 1324 1325 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 1326 1327 getPackageShortcutsLocked(packageName, userId).findAll(this, ret, query, cloneFlags, 1328 /* callingLauncher= */ null); 1329 1330 return new ParceledListSlice<>(ret); 1331 } 1332 1333 @Override 1334 public int getMaxDynamicShortcutCount(String packageName, @UserIdInt int userId) 1335 throws RemoteException { 1336 verifyCaller(packageName, userId); 1337 1338 return mMaxDynamicShortcuts; 1339 } 1340 1341 @Override 1342 public int getRemainingCallCount(String packageName, @UserIdInt int userId) { 1343 verifyCaller(packageName, userId); 1344 1345 synchronized (mLock) { 1346 return mMaxDailyUpdates 1347 - getPackageShortcutsLocked(packageName, userId).getApiCallCount(this); 1348 } 1349 } 1350 1351 @Override 1352 public long getRateLimitResetTime(String packageName, @UserIdInt int userId) { 1353 verifyCaller(packageName, userId); 1354 1355 synchronized (mLock) { 1356 return getNextResetTimeLocked(); 1357 } 1358 } 1359 1360 @Override 1361 public int getIconMaxDimensions(String packageName, int userId) throws RemoteException { 1362 synchronized (mLock) { 1363 return mMaxIconDimension; 1364 } 1365 } 1366 1367 /** 1368 * Reset all throttling, for developer options and command line. Only system/shell can call it. 1369 */ 1370 @Override 1371 public void resetThrottling() { 1372 enforceSystemOrShell(); 1373 1374 resetThrottlingInner(getCallingUserId()); 1375 } 1376 1377 void resetThrottlingInner(@UserIdInt int userId) { 1378 synchronized (mLock) { 1379 getUserShortcutsLocked(userId).resetThrottling(); 1380 } 1381 scheduleSaveUser(userId); 1382 Slog.i(TAG, "ShortcutManager: throttling counter reset"); 1383 } 1384 1385 // We override this method in unit tests to do a simpler check. 1386 boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) { 1387 return hasShortcutHostPermissionInner(callingPackage, userId); 1388 } 1389 1390 // This method is extracted so we can directly call this method from unit tests, 1391 // even when hasShortcutPermission() is overridden. 1392 @VisibleForTesting 1393 boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) { 1394 synchronized (mLock) { 1395 long start = System.currentTimeMillis(); 1396 1397 final ShortcutUser user = getUserShortcutsLocked(userId); 1398 1399 final List<ResolveInfo> allHomeCandidates = new ArrayList<>(); 1400 1401 // Default launcher from package manager. 1402 final ComponentName defaultLauncher = injectPackageManagerInternal() 1403 .getHomeActivitiesAsUser(allHomeCandidates, userId); 1404 1405 ComponentName detected; 1406 if (defaultLauncher != null) { 1407 detected = defaultLauncher; 1408 if (DEBUG) { 1409 Slog.v(TAG, "Default launcher from PM: " + detected); 1410 } 1411 } else { 1412 detected = user.getLauncherComponent(); 1413 1414 // TODO: Make sure it's still enabled. 1415 if (DEBUG) { 1416 Slog.v(TAG, "Cached launcher: " + detected); 1417 } 1418 } 1419 1420 if (detected == null) { 1421 // If we reach here, that means it's the first check since the user was created, 1422 // and there's already multiple launchers and there's no default set. 1423 // Find the system one with the highest priority. 1424 // (We need to check the priority too because of FallbackHome in Settings.) 1425 // If there's no system launcher yet, then no one can access shortcuts, until 1426 // the user explicitly 1427 final int size = allHomeCandidates.size(); 1428 1429 int lastPriority = Integer.MIN_VALUE; 1430 for (int i = 0; i < size; i++) { 1431 final ResolveInfo ri = allHomeCandidates.get(i); 1432 if (!ri.activityInfo.applicationInfo.isSystemApp()) { 1433 continue; 1434 } 1435 if (DEBUG) { 1436 Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d", 1437 ri.activityInfo.getComponentName(), ri.priority)); 1438 } 1439 if (ri.priority < lastPriority) { 1440 continue; 1441 } 1442 detected = ri.activityInfo.getComponentName(); 1443 lastPriority = ri.priority; 1444 } 1445 } 1446 final long end = System.currentTimeMillis(); 1447 if (DEBUG) { 1448 Slog.v(TAG, String.format("hasShortcutPermission took %d ms", end - start)); 1449 } 1450 if (detected != null) { 1451 if (DEBUG) { 1452 Slog.v(TAG, "Detected launcher: " + detected); 1453 } 1454 user.setLauncherComponent(this, detected); 1455 return detected.getPackageName().equals(callingPackage); 1456 } else { 1457 // Default launcher not found. 1458 return false; 1459 } 1460 } 1461 } 1462 1463 // === House keeping === 1464 1465 @VisibleForTesting 1466 void cleanUpPackageLocked(String packageName, int userId) { 1467 final boolean wasUserLoaded = isUserLoadedLocked(userId); 1468 1469 final ShortcutUser mUser = getUserShortcutsLocked(userId); 1470 boolean doNotify = false; 1471 1472 // First, remove the package from the package list (if the package is a publisher). 1473 if (mUser.getPackages().remove(packageName) != null) { 1474 doNotify = true; 1475 } 1476 // Also remove from the launcher list (if the package is a launcher). 1477 mUser.getLaunchers().remove(packageName); 1478 1479 // Then remove pinned shortcuts from all launchers. 1480 for (int i = mUser.getLaunchers().size() - 1; i >= 0; i--) { 1481 mUser.getLaunchers().valueAt(i).cleanUpPackage(packageName); 1482 } 1483 // Now there may be orphan shortcuts because we removed pinned shortucts at the previous 1484 // step. Remove them too. 1485 for (int i = mUser.getPackages().size() - 1; i >= 0; i--) { 1486 mUser.getPackages().valueAt(i).refreshPinnedFlags(this); 1487 } 1488 1489 // Remove the package info too. 1490 mUser.getPackageInfos().remove(packageName); 1491 1492 scheduleSaveUser(userId); 1493 1494 if (doNotify) { 1495 notifyListeners(packageName, userId); 1496 } 1497 1498 if (!wasUserLoaded) { 1499 // Note this will execute the scheduled save. 1500 unloadUserLocked(userId); 1501 } 1502 } 1503 1504 /** 1505 * Entry point from {@link LauncherApps}. 1506 */ 1507 private class LocalService extends ShortcutServiceInternal { 1508 @Override 1509 public List<ShortcutInfo> getShortcuts( 1510 @NonNull String callingPackage, long changedSince, 1511 @Nullable String packageName, @Nullable ComponentName componentName, 1512 int queryFlags, int userId) { 1513 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 1514 final int cloneFlag = 1515 ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) == 0) 1516 ? ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER 1517 : ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO; 1518 1519 synchronized (mLock) { 1520 if (packageName != null) { 1521 getShortcutsInnerLocked( 1522 callingPackage, packageName, changedSince, 1523 componentName, queryFlags, userId, ret, cloneFlag); 1524 } else { 1525 final ArrayMap<String, ShortcutPackage> packages = 1526 getUserShortcutsLocked(userId).getPackages(); 1527 for (int i = packages.size() - 1; i >= 0; i--) { 1528 getShortcutsInnerLocked( 1529 callingPackage, packages.keyAt(i), changedSince, 1530 componentName, queryFlags, userId, ret, cloneFlag); 1531 } 1532 } 1533 } 1534 return ret; 1535 } 1536 1537 private void getShortcutsInnerLocked(@NonNull String callingPackage, 1538 @Nullable String packageName,long changedSince, 1539 @Nullable ComponentName componentName, int queryFlags, 1540 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) { 1541 getPackageShortcutsLocked(packageName, userId).findAll(ShortcutService.this, ret, 1542 (ShortcutInfo si) -> { 1543 if (si.getLastChangedTimestamp() < changedSince) { 1544 return false; 1545 } 1546 if (componentName != null 1547 && !componentName.equals(si.getActivityComponent())) { 1548 return false; 1549 } 1550 final boolean matchDynamic = 1551 ((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0) 1552 && si.isDynamic(); 1553 final boolean matchPinned = 1554 ((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0) 1555 && si.isPinned(); 1556 return matchDynamic || matchPinned; 1557 }, cloneFlag, callingPackage); 1558 } 1559 1560 @Override 1561 public List<ShortcutInfo> getShortcutInfo( 1562 @NonNull String callingPackage, 1563 @NonNull String packageName, @Nullable List<String> ids, int userId) { 1564 // Calling permission must be checked by LauncherAppsImpl. 1565 Preconditions.checkStringNotEmpty(packageName, "packageName"); 1566 1567 final ArrayList<ShortcutInfo> ret = new ArrayList<>(ids.size()); 1568 final ArraySet<String> idSet = new ArraySet<>(ids); 1569 synchronized (mLock) { 1570 getPackageShortcutsLocked(packageName, userId).findAll( 1571 ShortcutService.this, ret, 1572 (ShortcutInfo si) -> idSet.contains(si.getId()), 1573 ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER, callingPackage); 1574 } 1575 return ret; 1576 } 1577 1578 @Override 1579 public void pinShortcuts(@NonNull String callingPackage, @NonNull String packageName, 1580 @NonNull List<String> shortcutIds, int userId) { 1581 // Calling permission must be checked by LauncherAppsImpl. 1582 Preconditions.checkStringNotEmpty(packageName, "packageName"); 1583 Preconditions.checkNotNull(shortcutIds, "shortcutIds"); 1584 1585 synchronized (mLock) { 1586 getUserShortcutsLocked(userId).ensurePackageInfo( 1587 ShortcutService.this, callingPackage, userId); 1588 1589 getLauncherShortcuts(callingPackage, userId).pinShortcuts( 1590 ShortcutService.this, packageName, shortcutIds); 1591 } 1592 userPackageChanged(packageName, userId); 1593 } 1594 1595 @Override 1596 public Intent createShortcutIntent(@NonNull String callingPackage, 1597 @NonNull String packageName, @NonNull String shortcutId, int userId) { 1598 // Calling permission must be checked by LauncherAppsImpl. 1599 Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty"); 1600 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty"); 1601 1602 synchronized (mLock) { 1603 final ShortcutInfo fullShortcut = 1604 getPackageShortcutsLocked(packageName, userId) 1605 .findShortcutById(shortcutId); 1606 return fullShortcut == null ? null : fullShortcut.getIntent(); 1607 } 1608 } 1609 1610 @Override 1611 public void addListener(@NonNull ShortcutChangeListener listener) { 1612 synchronized (mLock) { 1613 mListeners.add(Preconditions.checkNotNull(listener)); 1614 } 1615 } 1616 1617 @Override 1618 public int getShortcutIconResId(@NonNull String callingPackage, 1619 @NonNull ShortcutInfo shortcut, int userId) { 1620 Preconditions.checkNotNull(shortcut, "shortcut"); 1621 1622 synchronized (mLock) { 1623 final ShortcutInfo shortcutInfo = getPackageShortcutsLocked( 1624 shortcut.getPackageName(), userId).findShortcutById(shortcut.getId()); 1625 return (shortcutInfo != null && shortcutInfo.hasIconResource()) 1626 ? shortcutInfo.getIconResourceId() : 0; 1627 } 1628 } 1629 1630 @Override 1631 public ParcelFileDescriptor getShortcutIconFd(@NonNull String callingPackage, 1632 @NonNull ShortcutInfo shortcutIn, int userId) { 1633 Preconditions.checkNotNull(shortcutIn, "shortcut"); 1634 1635 synchronized (mLock) { 1636 final ShortcutInfo shortcutInfo = getPackageShortcutsLocked( 1637 shortcutIn.getPackageName(), userId).findShortcutById(shortcutIn.getId()); 1638 if (shortcutInfo == null || !shortcutInfo.hasIconFile()) { 1639 return null; 1640 } 1641 try { 1642 if (shortcutInfo.getBitmapPath() == null) { 1643 Slog.w(TAG, "null bitmap detected in getShortcutIconFd()"); 1644 return null; 1645 } 1646 return ParcelFileDescriptor.open( 1647 new File(shortcutInfo.getBitmapPath()), 1648 ParcelFileDescriptor.MODE_READ_ONLY); 1649 } catch (FileNotFoundException e) { 1650 Slog.e(TAG, "Icon file not found: " + shortcutInfo.getBitmapPath()); 1651 return null; 1652 } 1653 } 1654 } 1655 1656 @Override 1657 public boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) { 1658 return ShortcutService.this.hasShortcutHostPermission(callingPackage, userId); 1659 } 1660 } 1661 1662 /** 1663 * Package event callbacks. 1664 */ 1665 @VisibleForTesting 1666 final PackageMonitor mPackageMonitor = new PackageMonitor() { 1667 @Override 1668 public void onPackageAdded(String packageName, int uid) { 1669 handlePackageAdded(packageName, getChangingUserId()); 1670 } 1671 1672 @Override 1673 public void onPackageUpdateFinished(String packageName, int uid) { 1674 handlePackageUpdateFinished(packageName, getChangingUserId()); 1675 } 1676 1677 @Override 1678 public void onPackageRemoved(String packageName, int uid) { 1679 handlePackageRemoved(packageName, getChangingUserId()); 1680 } 1681 }; 1682 1683 /** 1684 * Called when a user is unlocked. Check all known packages still exist, and otherwise 1685 * perform cleanup. 1686 */ 1687 private void cleanupGonePackages(@UserIdInt int userId) { 1688 if (DEBUG) { 1689 Slog.d(TAG, "cleanupGonePackages() userId=" + userId); 1690 } 1691 ArrayList<String> gonePackages = null; 1692 1693 final ShortcutUser user = getUserShortcutsLocked(userId); 1694 final ArrayMap<String, ShortcutPackageInfo> infos = user.getPackageInfos(); 1695 for (int i = infos.size() -1; i >= 0; i--) { 1696 final ShortcutPackageInfo info = infos.valueAt(i); 1697 if (info.isShadow()) { 1698 continue; 1699 } 1700 if (isPackageInstalled(info.getPackageName(), userId)) { 1701 continue; 1702 } 1703 gonePackages = ArrayUtils.add(gonePackages, info.getPackageName()); 1704 } 1705 if (gonePackages != null) { 1706 synchronized (mLock) { 1707 for (int i = gonePackages.size() - 1; i >= 0; i--) { 1708 cleanUpPackageLocked(gonePackages.get(i), userId); 1709 } 1710 } 1711 } 1712 } 1713 1714 private void handlePackageAdded(String packageName, @UserIdInt int userId) { 1715 if (DEBUG) { 1716 Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId)); 1717 } 1718 synchronized (mLock) { 1719 final ArrayMap<String, ShortcutPackageInfo> infos = 1720 getUserShortcutsLocked(userId).getPackageInfos(); 1721 final ShortcutPackageInfo existing = infos.get(packageName); 1722 1723 if (existing != null && existing.isShadow()) { 1724 Slog.w(TAG, "handlePackageAdded: TODO Restore not implemented"); 1725 } 1726 } 1727 } 1728 1729 private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) { 1730 if (DEBUG) { 1731 Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d", packageName, userId)); 1732 } 1733 synchronized (mLock) { 1734 final ShortcutPackageInfo spi = 1735 getUserShortcutsLocked(userId).getPackageInfos().get(packageName); 1736 if (spi != null) { 1737 spi.refreshAndSave(this, userId); 1738 } 1739 } 1740 } 1741 1742 private void handlePackageRemoved(String packageName, @UserIdInt int userId) { 1743 if (DEBUG) { 1744 Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, userId)); 1745 } 1746 synchronized (mLock) { 1747 cleanUpPackageLocked(packageName, userId); 1748 } 1749 } 1750 1751 // === Backup & restore === 1752 1753 PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) { 1754 return injectPackageInfo(packageName, userId, true); 1755 } 1756 1757 int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) { 1758 try { 1759 return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS 1760 , userId); 1761 } catch (RemoteException e) { 1762 // Shouldn't happen. 1763 Slog.wtf(TAG, "RemoteException", e); 1764 return -1; 1765 } 1766 } 1767 1768 @VisibleForTesting 1769 PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId, 1770 boolean getSignatures) { 1771 try { 1772 return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS 1773 | (getSignatures ? PackageManager.GET_SIGNATURES : 0) 1774 , userId); 1775 } catch (RemoteException e) { 1776 // Shouldn't happen. 1777 Slog.wtf(TAG, "RemoteException", e); 1778 return null; 1779 } 1780 } 1781 1782 @VisibleForTesting 1783 ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) { 1784 try { 1785 return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId); 1786 } catch (RemoteException e) { 1787 // Shouldn't happen. 1788 Slog.wtf(TAG, "RemoteException", e); 1789 return null; 1790 } 1791 } 1792 1793 private boolean isApplicationFlagSet(String packageName, int userId, int flags) { 1794 final ApplicationInfo ai = injectApplicationInfo(packageName, userId); 1795 return (ai != null) && ((ai.flags & flags) == flags); 1796 } 1797 1798 boolean shouldBackupApp(String packageName, int userId) { 1799 return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP); 1800 } 1801 1802 private boolean isPackageInstalled(String packageName, int userId) { 1803 return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED); 1804 } 1805 1806 // === Dump === 1807 1808 @Override 1809 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1810 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 1811 != PackageManager.PERMISSION_GRANTED) { 1812 pw.println("Permission Denial: can't dump UserManager from from pid=" 1813 + Binder.getCallingPid() 1814 + ", uid=" + Binder.getCallingUid() 1815 + " without permission " 1816 + android.Manifest.permission.DUMP); 1817 return; 1818 } 1819 dumpInner(pw); 1820 } 1821 1822 @VisibleForTesting 1823 void dumpInner(PrintWriter pw) { 1824 synchronized (mLock) { 1825 final long now = injectCurrentTimeMillis(); 1826 pw.print("Now: ["); 1827 pw.print(now); 1828 pw.print("] "); 1829 pw.print(formatTime(now)); 1830 1831 pw.print(" Raw last reset: ["); 1832 pw.print(mRawLastResetTime); 1833 pw.print("] "); 1834 pw.print(formatTime(mRawLastResetTime)); 1835 1836 final long last = getLastResetTimeLocked(); 1837 pw.print(" Last reset: ["); 1838 pw.print(last); 1839 pw.print("] "); 1840 pw.print(formatTime(last)); 1841 1842 final long next = getNextResetTimeLocked(); 1843 pw.print(" Next reset: ["); 1844 pw.print(next); 1845 pw.print("] "); 1846 pw.print(formatTime(next)); 1847 pw.println(); 1848 1849 pw.print(" Max icon dim: "); 1850 pw.print(mMaxIconDimension); 1851 pw.print(" Icon format: "); 1852 pw.print(mIconPersistFormat); 1853 pw.print(" Icon quality: "); 1854 pw.print(mIconPersistQuality); 1855 pw.println(); 1856 1857 1858 for (int i = 0; i < mUsers.size(); i++) { 1859 pw.println(); 1860 mUsers.valueAt(i).dump(this, pw, " "); 1861 } 1862 } 1863 } 1864 1865 static String formatTime(long time) { 1866 Time tobj = new Time(); 1867 tobj.set(time); 1868 return tobj.format("%Y-%m-%d %H:%M:%S"); 1869 } 1870 1871 // === Shell support === 1872 1873 @Override 1874 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 1875 String[] args, ResultReceiver resultReceiver) throws RemoteException { 1876 1877 enforceShell(); 1878 1879 (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver); 1880 } 1881 1882 static class CommandException extends Exception { 1883 public CommandException(String message) { 1884 super(message); 1885 } 1886 } 1887 1888 /** 1889 * Handle "adb shell cmd". 1890 */ 1891 private class MyShellCommand extends ShellCommand { 1892 1893 private int mUserId = UserHandle.USER_SYSTEM; 1894 1895 private void parseOptions(boolean takeUser) 1896 throws CommandException { 1897 String opt; 1898 while ((opt = getNextOption()) != null) { 1899 switch (opt) { 1900 case "--user": 1901 if (takeUser) { 1902 mUserId = UserHandle.parseUserArg(getNextArgRequired()); 1903 break; 1904 } 1905 // fallthrough 1906 default: 1907 throw new CommandException("Unknown option: " + opt); 1908 } 1909 } 1910 } 1911 1912 @Override 1913 public int onCommand(String cmd) { 1914 if (cmd == null) { 1915 return handleDefaultCommands(cmd); 1916 } 1917 final PrintWriter pw = getOutPrintWriter(); 1918 try { 1919 switch (cmd) { 1920 case "reset-package-throttling": 1921 handleResetPackageThrottling(); 1922 break; 1923 case "reset-throttling": 1924 handleResetThrottling(); 1925 break; 1926 case "override-config": 1927 handleOverrideConfig(); 1928 break; 1929 case "reset-config": 1930 handleResetConfig(); 1931 break; 1932 case "clear-default-launcher": 1933 handleClearDefaultLauncher(); 1934 break; 1935 case "get-default-launcher": 1936 handleGetDefaultLauncher(); 1937 break; 1938 case "refresh-default-launcher": 1939 handleRefreshDefaultLauncher(); 1940 break; 1941 default: 1942 return handleDefaultCommands(cmd); 1943 } 1944 } catch (CommandException e) { 1945 pw.println("Error: " + e.getMessage()); 1946 return 1; 1947 } 1948 pw.println("Success"); 1949 return 0; 1950 } 1951 1952 @Override 1953 public void onHelp() { 1954 final PrintWriter pw = getOutPrintWriter(); 1955 pw.println("Usage: cmd shortcut COMMAND [options ...]"); 1956 pw.println(); 1957 pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE"); 1958 pw.println(" Reset throttling for a package"); 1959 pw.println(); 1960 pw.println("cmd shortcut reset-throttling"); 1961 pw.println(" Reset throttling for all packages and users"); 1962 pw.println(); 1963 pw.println("cmd shortcut override-config CONFIG"); 1964 pw.println(" Override the configuration for testing (will last until reboot)"); 1965 pw.println(); 1966 pw.println("cmd shortcut reset-config"); 1967 pw.println(" Reset the configuration set with \"update-config\""); 1968 pw.println(); 1969 pw.println("cmd shortcut clear-default-launcher [--user USER_ID]"); 1970 pw.println(" Clear the cached default launcher"); 1971 pw.println(); 1972 pw.println("cmd shortcut get-default-launcher [--user USER_ID]"); 1973 pw.println(" Show the cached default launcher"); 1974 pw.println(); 1975 pw.println("cmd shortcut refresh-default-launcher [--user USER_ID]"); 1976 pw.println(" Refresh the cached default launcher"); 1977 pw.println(); 1978 } 1979 1980 private int handleResetThrottling() throws CommandException { 1981 parseOptions(/* takeUser =*/ true); 1982 1983 resetThrottlingInner(mUserId); 1984 return 0; 1985 } 1986 1987 private void handleResetPackageThrottling() throws CommandException { 1988 parseOptions(/* takeUser =*/ true); 1989 1990 final String packageName = getNextArgRequired(); 1991 1992 synchronized (mLock) { 1993 getPackageShortcutsLocked(packageName, mUserId).resetRateLimitingForCommandLine(); 1994 saveUserLocked(mUserId); 1995 } 1996 } 1997 1998 private void handleOverrideConfig() throws CommandException { 1999 final String config = getNextArgRequired(); 2000 2001 synchronized (mLock) { 2002 if (!updateConfigurationLocked(config)) { 2003 throw new CommandException("override-config failed. See logcat for details."); 2004 } 2005 } 2006 } 2007 2008 private void handleResetConfig() { 2009 synchronized (mLock) { 2010 loadConfigurationLocked(); 2011 } 2012 } 2013 2014 private void clearLauncher() { 2015 synchronized (mLock) { 2016 getUserShortcutsLocked(mUserId).setLauncherComponent( 2017 ShortcutService.this, null); 2018 } 2019 } 2020 2021 private void showLauncher() { 2022 synchronized (mLock) { 2023 // This ensures to set the cached launcher. Package name doesn't matter. 2024 hasShortcutHostPermissionInner("-", mUserId); 2025 2026 getOutPrintWriter().println("Launcher: " 2027 + getUserShortcutsLocked(mUserId).getLauncherComponent()); 2028 } 2029 } 2030 2031 private void handleClearDefaultLauncher() throws CommandException { 2032 parseOptions(/* takeUser =*/ true); 2033 2034 clearLauncher(); 2035 } 2036 2037 private void handleGetDefaultLauncher() throws CommandException { 2038 parseOptions(/* takeUser =*/ true); 2039 2040 showLauncher(); 2041 } 2042 2043 private void handleRefreshDefaultLauncher() throws CommandException { 2044 parseOptions(/* takeUser =*/ true); 2045 2046 clearLauncher(); 2047 showLauncher(); 2048 } 2049 } 2050 2051 // === Unit test support === 2052 2053 // Injection point. 2054 @VisibleForTesting 2055 long injectCurrentTimeMillis() { 2056 return System.currentTimeMillis(); 2057 } 2058 2059 // Injection point. 2060 @VisibleForTesting 2061 int injectBinderCallingUid() { 2062 return getCallingUid(); 2063 } 2064 2065 private int getCallingUserId() { 2066 return UserHandle.getUserId(injectBinderCallingUid()); 2067 } 2068 2069 // Injection point. 2070 @VisibleForTesting 2071 long injectClearCallingIdentity() { 2072 return Binder.clearCallingIdentity(); 2073 } 2074 2075 // Injection point. 2076 @VisibleForTesting 2077 void injectRestoreCallingIdentity(long token) { 2078 Binder.restoreCallingIdentity(token); 2079 } 2080 2081 final void wtf(String message) { 2082 Slog.wtf(TAG, message, /* exception= */ null); 2083 } 2084 2085 void wtf(String message, Exception e) { 2086 Slog.wtf(TAG, message, e); 2087 } 2088 2089 @VisibleForTesting 2090 File injectSystemDataPath() { 2091 return Environment.getDataSystemDirectory(); 2092 } 2093 2094 @VisibleForTesting 2095 File injectUserDataPath(@UserIdInt int userId) { 2096 return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER); 2097 } 2098 2099 @VisibleForTesting 2100 boolean injectIsLowRamDevice() { 2101 return ActivityManager.isLowRamDeviceStatic(); 2102 } 2103 2104 @VisibleForTesting 2105 PackageManagerInternal injectPackageManagerInternal() { 2106 return mPackageManagerInternal; 2107 } 2108 2109 @VisibleForTesting 2110 File getUserBitmapFilePath(@UserIdInt int userId) { 2111 return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS); 2112 } 2113 2114 @VisibleForTesting 2115 SparseArray<ShortcutUser> getShortcutsForTest() { 2116 return mUsers; 2117 } 2118 2119 @VisibleForTesting 2120 int getMaxDynamicShortcutsForTest() { 2121 return mMaxDynamicShortcuts; 2122 } 2123 2124 @VisibleForTesting 2125 int getMaxDailyUpdatesForTest() { 2126 return mMaxDailyUpdates; 2127 } 2128 2129 @VisibleForTesting 2130 long getResetIntervalForTest() { 2131 return mResetInterval; 2132 } 2133 2134 @VisibleForTesting 2135 int getMaxIconDimensionForTest() { 2136 return mMaxIconDimension; 2137 } 2138 2139 @VisibleForTesting 2140 CompressFormat getIconPersistFormatForTest() { 2141 return mIconPersistFormat; 2142 } 2143 2144 @VisibleForTesting 2145 int getIconPersistQualityForTest() { 2146 return mIconPersistQuality; 2147 } 2148 2149 @VisibleForTesting 2150 ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) { 2151 synchronized (mLock) { 2152 final ShortcutUser user = mUsers.get(userId); 2153 if (user == null) return null; 2154 2155 final ShortcutPackage pkg = user.getPackages().get(packageName); 2156 if (pkg == null) return null; 2157 2158 return pkg.findShortcutById(shortcutId); 2159 } 2160 } 2161 2162 @VisibleForTesting 2163 ShortcutPackageInfo getPackageInfoForTest(String packageName, int userId) { 2164 synchronized (mLock) { 2165 final ShortcutUser user = mUsers.get(userId); 2166 if (user == null) return null; 2167 2168 return user.getPackageInfos().get(packageName); 2169 } 2170 } 2171} 2172