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