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