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