ShortcutService.java revision 157b1628fd84dc3ef0355fddd8d281618f94d33e
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 app. (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_APPLICATIONS = 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 // === APIs === 1482 1483 @Override 1484 public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, 1485 @UserIdInt int userId) { 1486 verifyCaller(packageName, userId); 1487 1488 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); 1489 final int size = newShortcuts.size(); 1490 1491 synchronized (mLock) { 1492 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); 1493 1494 ps.ensureImmutableShortcutsNotIncluded(newShortcuts); 1495 1496 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET); 1497 1498 // Throttling. 1499 if (!ps.tryApiCall()) { 1500 return false; 1501 } 1502 1503 // Validate the shortcuts. 1504 for (int i = 0; i < size; i++) { 1505 fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false); 1506 } 1507 1508 // First, remove all un-pinned; dynamic shortcuts 1509 ps.deleteAllDynamicShortcuts(); 1510 1511 // Then, add/update all. We need to make sure to take over "pinned" flag. 1512 for (int i = 0; i < size; i++) { 1513 final ShortcutInfo newShortcut = newShortcuts.get(i); 1514 ps.addOrUpdateDynamicShortcut(newShortcut); 1515 } 1516 } 1517 packageShortcutsChanged(packageName, userId); 1518 1519 verifyStates(); 1520 1521 return true; 1522 } 1523 1524 @Override 1525 public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, 1526 @UserIdInt int userId) { 1527 verifyCaller(packageName, userId); 1528 1529 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); 1530 final int size = newShortcuts.size(); 1531 1532 synchronized (mLock) { 1533 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); 1534 1535 ps.ensureImmutableShortcutsNotIncluded(newShortcuts); 1536 1537 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE); 1538 1539 // Throttling. 1540 if (!ps.tryApiCall()) { 1541 return false; 1542 } 1543 1544 for (int i = 0; i < size; i++) { 1545 final ShortcutInfo source = newShortcuts.get(i); 1546 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true); 1547 1548 final ShortcutInfo target = ps.findShortcutById(source.getId()); 1549 if (target != null) { 1550 if (target.isEnabled() != source.isEnabled()) { 1551 Slog.w(TAG, 1552 "ShortcutInfo.enabled cannot be changed with updateShortcuts()"); 1553 } 1554 1555 final boolean replacingIcon = (source.getIcon() != null); 1556 if (replacingIcon) { 1557 removeIcon(userId, target); 1558 } 1559 1560 if (source.getActivity() != null && 1561 !source.getActivity().equals(target.getActivity())) { 1562 // TODO When activity is changing, check the dynamic count. 1563 } 1564 1565 // Note copyNonNullFieldsFrom() does the "updatable with?" check too. 1566 target.copyNonNullFieldsFrom(source); 1567 1568 if (replacingIcon) { 1569 saveIconAndFixUpShortcut(userId, target); 1570 } 1571 1572 // When we're updating any resource related fields, re-extract the res names and 1573 // the values. 1574 if (replacingIcon || source.hasStringResources()) { 1575 fixUpShortcutResourceNamesAndValues(target); 1576 } 1577 } 1578 } 1579 } 1580 packageShortcutsChanged(packageName, userId); 1581 1582 verifyStates(); 1583 1584 return true; 1585 } 1586 1587 @Override 1588 public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, 1589 @UserIdInt int userId) { 1590 verifyCaller(packageName, userId); 1591 1592 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); 1593 final int size = newShortcuts.size(); 1594 1595 synchronized (mLock) { 1596 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); 1597 1598 ps.ensureImmutableShortcutsNotIncluded(newShortcuts); 1599 1600 ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD); 1601 1602 // Throttling. 1603 if (!ps.tryApiCall()) { 1604 return false; 1605 } 1606 for (int i = 0; i < size; i++) { 1607 final ShortcutInfo newShortcut = newShortcuts.get(i); 1608 1609 // Validate the shortcut. 1610 fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false); 1611 1612 // Add it. 1613 ps.addOrUpdateDynamicShortcut(newShortcut); 1614 } 1615 } 1616 packageShortcutsChanged(packageName, userId); 1617 1618 verifyStates(); 1619 1620 return true; 1621 } 1622 1623 @Override 1624 public void disableShortcuts(String packageName, List shortcutIds, 1625 String disabledMessage, int disabledMessageResId, @UserIdInt int userId) { 1626 verifyCaller(packageName, userId); 1627 Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); 1628 1629 synchronized (mLock) { 1630 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); 1631 1632 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds); 1633 1634 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 1635 ps.disableWithId(Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)), 1636 disabledMessage, disabledMessageResId, 1637 /* overrideImmutable=*/ false); 1638 } 1639 } 1640 packageShortcutsChanged(packageName, userId); 1641 1642 verifyStates(); 1643 } 1644 1645 @Override 1646 public void enableShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) { 1647 verifyCaller(packageName, userId); 1648 Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); 1649 1650 synchronized (mLock) { 1651 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); 1652 1653 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds); 1654 1655 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 1656 ps.enableWithId((String) shortcutIds.get(i)); 1657 } 1658 } 1659 packageShortcutsChanged(packageName, userId); 1660 1661 verifyStates(); 1662 } 1663 1664 @Override 1665 public void removeDynamicShortcuts(String packageName, List shortcutIds, 1666 @UserIdInt int userId) { 1667 verifyCaller(packageName, userId); 1668 Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); 1669 1670 synchronized (mLock) { 1671 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); 1672 1673 ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds); 1674 1675 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 1676 ps.deleteDynamicWithId( 1677 Preconditions.checkStringNotEmpty((String) shortcutIds.get(i))); 1678 } 1679 } 1680 packageShortcutsChanged(packageName, userId); 1681 1682 verifyStates(); 1683 } 1684 1685 @Override 1686 public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) { 1687 verifyCaller(packageName, userId); 1688 1689 synchronized (mLock) { 1690 getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(); 1691 } 1692 packageShortcutsChanged(packageName, userId); 1693 1694 verifyStates(); 1695 } 1696 1697 @Override 1698 public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName, 1699 @UserIdInt int userId) { 1700 verifyCaller(packageName, userId); 1701 synchronized (mLock) { 1702 return getShortcutsWithQueryLocked( 1703 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, 1704 ShortcutInfo::isDynamic); 1705 } 1706 } 1707 1708 @Override 1709 public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName, 1710 @UserIdInt int userId) { 1711 verifyCaller(packageName, userId); 1712 synchronized (mLock) { 1713 return getShortcutsWithQueryLocked( 1714 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, 1715 ShortcutInfo::isManifestShortcut); 1716 } 1717 } 1718 1719 @Override 1720 public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName, 1721 @UserIdInt int userId) { 1722 verifyCaller(packageName, userId); 1723 synchronized (mLock) { 1724 return getShortcutsWithQueryLocked( 1725 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, 1726 ShortcutInfo::isPinned); 1727 } 1728 } 1729 1730 private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName, 1731 @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) { 1732 1733 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 1734 1735 getPackageShortcutsLocked(packageName, userId).findAll(ret, query, cloneFlags); 1736 1737 return new ParceledListSlice<>(ret); 1738 } 1739 1740 @Override 1741 public int getMaxDynamicShortcutCount(String packageName, @UserIdInt int userId) 1742 throws RemoteException { 1743 verifyCaller(packageName, userId); 1744 1745 return mMaxDynamicShortcuts; 1746 } 1747 1748 @Override 1749 public int getRemainingCallCount(String packageName, @UserIdInt int userId) { 1750 verifyCaller(packageName, userId); 1751 1752 synchronized (mLock) { 1753 return mMaxUpdatesPerInterval 1754 - getPackageShortcutsLocked(packageName, userId).getApiCallCount(); 1755 } 1756 } 1757 1758 @Override 1759 public long getRateLimitResetTime(String packageName, @UserIdInt int userId) { 1760 verifyCaller(packageName, userId); 1761 1762 synchronized (mLock) { 1763 return getNextResetTimeLocked(); 1764 } 1765 } 1766 1767 @Override 1768 public int getIconMaxDimensions(String packageName, int userId) { 1769 verifyCaller(packageName, userId); 1770 1771 synchronized (mLock) { 1772 return mMaxIconDimension; 1773 } 1774 } 1775 1776 @Override 1777 public void reportShortcutUsed(String packageName, String shortcutId, int userId) { 1778 verifyCaller(packageName, userId); 1779 1780 Preconditions.checkNotNull(shortcutId); 1781 1782 if (DEBUG) { 1783 Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d", 1784 shortcutId, packageName, userId)); 1785 } 1786 1787 synchronized (mLock) { 1788 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); 1789 if (ps.findShortcutById(shortcutId) == null) { 1790 Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s", 1791 packageName, shortcutId)); 1792 return; 1793 } 1794 } 1795 1796 final long token = injectClearCallingIdentity(); 1797 try { 1798 mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId); 1799 } finally { 1800 injectRestoreCallingIdentity(token); 1801 } 1802 } 1803 1804 /** 1805 * Reset all throttling, for developer options and command line. Only system/shell can call it. 1806 */ 1807 @Override 1808 public void resetThrottling() { 1809 enforceSystemOrShell(); 1810 1811 resetThrottlingInner(getCallingUserId()); 1812 } 1813 1814 void resetThrottlingInner(@UserIdInt int userId) { 1815 synchronized (mLock) { 1816 getUserShortcutsLocked(userId).resetThrottling(); 1817 } 1818 scheduleSaveUser(userId); 1819 Slog.i(TAG, "ShortcutManager: throttling counter reset for user " + userId); 1820 } 1821 1822 void resetAllThrottlingInner() { 1823 synchronized (mLock) { 1824 mRawLastResetTime = injectCurrentTimeMillis(); 1825 } 1826 scheduleSaveBaseState(); 1827 Slog.i(TAG, "ShortcutManager: throttling counter reset for all users"); 1828 } 1829 1830 void resetPackageThrottling(String packageName, int userId) { 1831 synchronized (mLock) { 1832 getPackageShortcutsLocked(packageName, userId) 1833 .resetRateLimitingForCommandLineNoSaving(); 1834 saveUserLocked(userId); 1835 } 1836 } 1837 1838 @Override 1839 public void onApplicationActive(String packageName, int userId) { 1840 if (DEBUG) { 1841 Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId); 1842 } 1843 enforceResetThrottlingPermission(); 1844 resetPackageThrottling(packageName, userId); 1845 } 1846 1847 // We override this method in unit tests to do a simpler check. 1848 boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) { 1849 return hasShortcutHostPermissionInner(callingPackage, userId); 1850 } 1851 1852 // This method is extracted so we can directly call this method from unit tests, 1853 // even when hasShortcutPermission() is overridden. 1854 @VisibleForTesting 1855 boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) { 1856 synchronized (mLock) { 1857 final long start = injectElapsedRealtime(); 1858 1859 final ShortcutUser user = getUserShortcutsLocked(userId); 1860 1861 final List<ResolveInfo> allHomeCandidates = new ArrayList<>(); 1862 1863 // Default launcher from package manager. 1864 final long startGetHomeActivitiesAsUser = injectElapsedRealtime(); 1865 final ComponentName defaultLauncher = injectPackageManagerInternal() 1866 .getHomeActivitiesAsUser(allHomeCandidates, userId); 1867 logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser); 1868 1869 ComponentName detected; 1870 if (defaultLauncher != null) { 1871 detected = defaultLauncher; 1872 if (DEBUG) { 1873 Slog.v(TAG, "Default launcher from PM: " + detected); 1874 } 1875 } else { 1876 detected = user.getDefaultLauncherComponent(); 1877 1878 // TODO: Make sure it's still enabled. 1879 if (DEBUG) { 1880 Slog.v(TAG, "Cached launcher: " + detected); 1881 } 1882 } 1883 1884 if (detected == null) { 1885 // If we reach here, that means it's the first check since the user was created, 1886 // and there's already multiple launchers and there's no default set. 1887 // Find the system one with the highest priority. 1888 // (We need to check the priority too because of FallbackHome in Settings.) 1889 // If there's no system launcher yet, then no one can access shortcuts, until 1890 // the user explicitly 1891 final int size = allHomeCandidates.size(); 1892 1893 int lastPriority = Integer.MIN_VALUE; 1894 for (int i = 0; i < size; i++) { 1895 final ResolveInfo ri = allHomeCandidates.get(i); 1896 if (!ri.activityInfo.applicationInfo.isSystemApp()) { 1897 continue; 1898 } 1899 if (DEBUG) { 1900 Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d", 1901 ri.activityInfo.getComponentName(), ri.priority)); 1902 } 1903 if (ri.priority < lastPriority) { 1904 continue; 1905 } 1906 detected = ri.activityInfo.getComponentName(); 1907 lastPriority = ri.priority; 1908 } 1909 } 1910 logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start); 1911 1912 if (detected != null) { 1913 if (DEBUG) { 1914 Slog.v(TAG, "Detected launcher: " + detected); 1915 } 1916 user.setDefaultLauncherComponent(detected); 1917 return detected.getPackageName().equals(callingPackage); 1918 } else { 1919 // Default launcher not found. 1920 return false; 1921 } 1922 } 1923 } 1924 1925 // === House keeping === 1926 1927 private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId) { 1928 synchronized (mLock) { 1929 forEachLoadedUserLocked(user -> 1930 cleanUpPackageLocked(packageName, user.getUserId(), packageUserId)); 1931 } 1932 } 1933 1934 /** 1935 * Remove all the information associated with a package. This will really remove all the 1936 * information, including the restore information (i.e. it'll remove packages even if they're 1937 * shadow). 1938 * 1939 * This is called when an app is uninstalled, or an app gets "clear data"ed. 1940 */ 1941 @VisibleForTesting 1942 void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) { 1943 final boolean wasUserLoaded = isUserLoadedLocked(owningUserId); 1944 1945 final ShortcutUser user = getUserShortcutsLocked(owningUserId); 1946 boolean doNotify = false; 1947 1948 // First, remove the package from the package list (if the package is a publisher). 1949 if (packageUserId == owningUserId) { 1950 if (user.removePackage(packageName) != null) { 1951 doNotify = true; 1952 } 1953 } 1954 1955 // Also remove from the launcher list (if the package is a launcher). 1956 user.removeLauncher(packageUserId, packageName); 1957 1958 // Then remove pinned shortcuts from all launchers. 1959 user.forAllLaunchers(l -> l.cleanUpPackage(packageName, packageUserId)); 1960 1961 // Now there may be orphan shortcuts because we removed pinned shortcuts at the previous 1962 // step. Remove them too. 1963 user.forAllPackages(p -> p.refreshPinnedFlags()); 1964 1965 scheduleSaveUser(owningUserId); 1966 1967 if (doNotify) { 1968 notifyListeners(packageName, owningUserId); 1969 } 1970 1971 if (!wasUserLoaded) { 1972 // Note this will execute the scheduled save. 1973 unloadUserLocked(owningUserId); 1974 } 1975 } 1976 1977 /** 1978 * Entry point from {@link LauncherApps}. 1979 */ 1980 private class LocalService extends ShortcutServiceInternal { 1981 1982 @Override 1983 public List<ShortcutInfo> getShortcuts(int launcherUserId, 1984 @NonNull String callingPackage, long changedSince, 1985 @Nullable String packageName, @Nullable List<String> shortcutIds, 1986 @Nullable ComponentName componentName, 1987 int queryFlags, int userId) { 1988 final ArrayList<ShortcutInfo> ret = new ArrayList<>(); 1989 final boolean cloneKeyFieldOnly = 1990 ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0); 1991 final int cloneFlag = cloneKeyFieldOnly ? ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO 1992 : ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER; 1993 if (packageName == null) { 1994 shortcutIds = null; // LauncherAppsService already threw for it though. 1995 } 1996 1997 synchronized (mLock) { 1998 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 1999 .attemptToRestoreIfNeededAndSave(); 2000 2001 if (packageName != null) { 2002 getShortcutsInnerLocked(launcherUserId, 2003 callingPackage, packageName, shortcutIds, changedSince, 2004 componentName, queryFlags, userId, ret, cloneFlag); 2005 } else { 2006 final List<String> shortcutIdsF = shortcutIds; 2007 getUserShortcutsLocked(userId).forAllPackages(p -> { 2008 getShortcutsInnerLocked(launcherUserId, 2009 callingPackage, p.getPackageName(), shortcutIdsF, changedSince, 2010 componentName, queryFlags, userId, ret, cloneFlag); 2011 }); 2012 } 2013 } 2014 return ret; 2015 } 2016 2017 private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage, 2018 @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince, 2019 @Nullable ComponentName componentName, int queryFlags, 2020 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) { 2021 final ArraySet<String> ids = shortcutIds == null ? null 2022 : new ArraySet<>(shortcutIds); 2023 2024 final ShortcutPackage p = getUserShortcutsLocked(userId) 2025 .getPackageShortcutsIfExists(packageName); 2026 if (p == null) { 2027 return; // No need to instantiate ShortcutPackage. 2028 } 2029 2030 p.findAll(ret, 2031 (ShortcutInfo si) -> { 2032 if (si.getLastChangedTimestamp() < changedSince) { 2033 return false; 2034 } 2035 if (ids != null && !ids.contains(si.getId())) { 2036 return false; 2037 } 2038 if (componentName != null) { 2039 if (si.getActivity() != null 2040 && !si.getActivity().equals(componentName)) { 2041 return false; 2042 } 2043 } 2044 if (((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0) 2045 && si.isDynamic()) { 2046 return true; 2047 } 2048 if (((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0) 2049 && si.isPinned()) { 2050 return true; 2051 } 2052 if (((queryFlags & ShortcutQuery.FLAG_GET_MANIFEST) != 0) 2053 && si.isManifestShortcut()) { 2054 return true; 2055 } 2056 return false; 2057 }, cloneFlag, callingPackage, launcherUserId); 2058 } 2059 2060 @Override 2061 public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, 2062 @NonNull String packageName, @NonNull String shortcutId, int userId) { 2063 Preconditions.checkStringNotEmpty(packageName, "packageName"); 2064 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 2065 2066 synchronized (mLock) { 2067 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 2068 .attemptToRestoreIfNeededAndSave(); 2069 2070 final ShortcutInfo si = getShortcutInfoLocked( 2071 launcherUserId, callingPackage, packageName, shortcutId, userId); 2072 return si != null && si.isPinned(); 2073 } 2074 } 2075 2076 private ShortcutInfo getShortcutInfoLocked( 2077 int launcherUserId, @NonNull String callingPackage, 2078 @NonNull String packageName, @NonNull String shortcutId, int userId) { 2079 Preconditions.checkStringNotEmpty(packageName, "packageName"); 2080 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); 2081 2082 final ShortcutPackage p = getUserShortcutsLocked(userId) 2083 .getPackageShortcutsIfExists(packageName); 2084 if (p == null) { 2085 return null; 2086 } 2087 2088 final ArrayList<ShortcutInfo> list = new ArrayList<>(1); 2089 p.findAll(list, 2090 (ShortcutInfo si) -> shortcutId.equals(si.getId()), 2091 /* clone flags=*/ 0, callingPackage, launcherUserId); 2092 return list.size() == 0 ? null : list.get(0); 2093 } 2094 2095 @Override 2096 public void pinShortcuts(int launcherUserId, 2097 @NonNull String callingPackage, @NonNull String packageName, 2098 @NonNull List<String> shortcutIds, int userId) { 2099 // Calling permission must be checked by LauncherAppsImpl. 2100 Preconditions.checkStringNotEmpty(packageName, "packageName"); 2101 Preconditions.checkNotNull(shortcutIds, "shortcutIds"); 2102 2103 synchronized (mLock) { 2104 final ShortcutLauncher launcher = 2105 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId); 2106 launcher.attemptToRestoreIfNeededAndSave(); 2107 2108 launcher.pinShortcuts(userId, packageName, shortcutIds); 2109 } 2110 packageShortcutsChanged(packageName, userId); 2111 2112 verifyStates(); 2113 } 2114 2115 @Override 2116 public Intent createShortcutIntent(int launcherUserId, 2117 @NonNull String callingPackage, 2118 @NonNull String packageName, @NonNull String shortcutId, int userId) { 2119 // Calling permission must be checked by LauncherAppsImpl. 2120 Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty"); 2121 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty"); 2122 2123 synchronized (mLock) { 2124 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 2125 .attemptToRestoreIfNeededAndSave(); 2126 2127 // Make sure the shortcut is actually visible to the launcher. 2128 final ShortcutInfo si = getShortcutInfoLocked( 2129 launcherUserId, callingPackage, packageName, shortcutId, userId); 2130 // "si == null" should suffice here, but check the flags too just to make sure. 2131 if (si == null || !si.isEnabled() || !si.isAlive()) { 2132 return null; 2133 } 2134 return si.getIntent(); 2135 } 2136 } 2137 2138 @Override 2139 public void addListener(@NonNull ShortcutChangeListener listener) { 2140 synchronized (mLock) { 2141 mListeners.add(Preconditions.checkNotNull(listener)); 2142 } 2143 } 2144 2145 @Override 2146 public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage, 2147 @NonNull String packageName, @NonNull String shortcutId, int userId) { 2148 Preconditions.checkNotNull(callingPackage, "callingPackage"); 2149 Preconditions.checkNotNull(packageName, "packageName"); 2150 Preconditions.checkNotNull(shortcutId, "shortcutId"); 2151 2152 synchronized (mLock) { 2153 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 2154 .attemptToRestoreIfNeededAndSave(); 2155 2156 final ShortcutPackage p = getUserShortcutsLocked(userId) 2157 .getPackageShortcutsIfExists(packageName); 2158 if (p == null) { 2159 return 0; 2160 } 2161 2162 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 2163 return (shortcutInfo != null && shortcutInfo.hasIconResource()) 2164 ? shortcutInfo.getIconResourceId() : 0; 2165 } 2166 } 2167 2168 @Override 2169 public ParcelFileDescriptor getShortcutIconFd(int launcherUserId, 2170 @NonNull String callingPackage, @NonNull String packageName, 2171 @NonNull String shortcutId, int userId) { 2172 Preconditions.checkNotNull(callingPackage, "callingPackage"); 2173 Preconditions.checkNotNull(packageName, "packageName"); 2174 Preconditions.checkNotNull(shortcutId, "shortcutId"); 2175 2176 synchronized (mLock) { 2177 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) 2178 .attemptToRestoreIfNeededAndSave(); 2179 2180 final ShortcutPackage p = getUserShortcutsLocked(userId) 2181 .getPackageShortcutsIfExists(packageName); 2182 if (p == null) { 2183 return null; 2184 } 2185 2186 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); 2187 if (shortcutInfo == null || !shortcutInfo.hasIconFile()) { 2188 return null; 2189 } 2190 try { 2191 if (shortcutInfo.getBitmapPath() == null) { 2192 Slog.w(TAG, "null bitmap detected in getShortcutIconFd()"); 2193 return null; 2194 } 2195 return ParcelFileDescriptor.open( 2196 new File(shortcutInfo.getBitmapPath()), 2197 ParcelFileDescriptor.MODE_READ_ONLY); 2198 } catch (FileNotFoundException e) { 2199 Slog.e(TAG, "Icon file not found: " + shortcutInfo.getBitmapPath()); 2200 return null; 2201 } 2202 } 2203 } 2204 2205 @Override 2206 public boolean hasShortcutHostPermission(int launcherUserId, 2207 @NonNull String callingPackage) { 2208 return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId); 2209 } 2210 2211 /** 2212 * Called by AM when the system locale changes *within the AM lock. ABSOLUTELY do not take 2213 * any locks in this method. 2214 */ 2215 @Override 2216 public void onSystemLocaleChangedNoLock() { 2217 // DO NOT HOLD ANY LOCKS HERE. 2218 2219 // We want to reset throttling for all packages for all users. But we can't just do so 2220 // here because: 2221 // - We can't load/save users that are locked. 2222 // - Even for loaded users, resetting the counters would require us to hold mLock. 2223 // 2224 // So we use a "pull" model instead. In here, we just increment the "locale change 2225 // sequence number". Each ShortcutUser has the "last known locale change sequence". 2226 // 2227 // This allows ShortcutUser's to detect the system locale change, so they can reset 2228 // counters. 2229 2230 // Ignore all callback during system boot. 2231 if (mBootCompleted.get()) { 2232 mLocaleChangeSequenceNumber.incrementAndGet(); 2233 if (DEBUG) { 2234 Slog.d(TAG, "onSystemLocaleChangedNoLock: " + mLocaleChangeSequenceNumber.get()); 2235 } 2236 injectPostToHandler(() -> handleLocaleChanged()); 2237 } 2238 } 2239 } 2240 2241 void handleLocaleChanged() { 2242 if (DEBUG) { 2243 Slog.d(TAG, "handleLocaleChanged"); 2244 } 2245 scheduleSaveBaseState(); 2246 2247 final long token = injectClearCallingIdentity(); 2248 try { 2249 forEachLoadedUserLocked(u -> u.forAllPackages(p -> p.resolveResourceStrings())); 2250 } finally { 2251 injectRestoreCallingIdentity(token); 2252 } 2253 } 2254 2255 /** 2256 * Package event callbacks. 2257 */ 2258 @VisibleForTesting 2259 final PackageMonitor mPackageMonitor = new PackageMonitor() { 2260 @Override 2261 public void onPackageAdded(String packageName, int uid) { 2262 handlePackageAdded(packageName, getChangingUserId()); 2263 } 2264 2265 @Override 2266 public void onPackageUpdateFinished(String packageName, int uid) { 2267 handlePackageUpdateFinished(packageName, getChangingUserId()); 2268 } 2269 2270 @Override 2271 public void onPackageRemoved(String packageName, int uid) { 2272 handlePackageRemoved(packageName, getChangingUserId()); 2273 } 2274 2275 @Override 2276 public void onPackageDataCleared(String packageName, int uid) { 2277 handlePackageDataCleared(packageName, getChangingUserId()); 2278 } 2279 }; 2280 2281 /** 2282 * Called when a user is unlocked. 2283 * - Check all known packages still exist, and otherwise perform cleanup. 2284 * - If a package still exists, check the version code. If it's been updated, may need to 2285 * update timestamps of its shortcuts. 2286 */ 2287 @VisibleForTesting 2288 void checkPackageChanges(@UserIdInt int ownerUserId) { 2289 if (DEBUG) { 2290 Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId); 2291 } 2292 2293 final long start = injectElapsedRealtime(); 2294 try { 2295 final ArrayList<PackageWithUser> gonePackages = new ArrayList<>(); 2296 2297 synchronized (mLock) { 2298 final ShortcutUser user = getUserShortcutsLocked(ownerUserId); 2299 2300 // Find packages that have been uninstalled. 2301 user.forAllPackageItems(spi -> { 2302 if (spi.getPackageInfo().isShadow()) { 2303 return; // Don't delete shadow information. 2304 } 2305 if (!isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) { 2306 gonePackages.add(PackageWithUser.of(spi)); 2307 } 2308 }); 2309 if (gonePackages.size() > 0) { 2310 for (int i = gonePackages.size() - 1; i >= 0; i--) { 2311 final PackageWithUser pu = gonePackages.get(i); 2312 cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId); 2313 } 2314 } 2315 2316 // Then for each installed app, publish manifest shortcuts when needed. 2317 forInstalledApplications(ownerUserId, ai -> { 2318 user.handlePackageAddedOrUpdated(ai.packageName); 2319 }); 2320 } 2321 } finally { 2322 logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start); 2323 } 2324 } 2325 2326 private void handlePackageAdded(String packageName, @UserIdInt int userId) { 2327 if (DEBUG) { 2328 Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId)); 2329 } 2330 synchronized (mLock) { 2331 final ShortcutUser user = getUserShortcutsLocked(userId); 2332 user.attemptToRestoreIfNeededAndSave(this, packageName, userId); 2333 user.handlePackageAddedOrUpdated(packageName); 2334 } 2335 } 2336 2337 private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) { 2338 if (DEBUG) { 2339 Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d", 2340 packageName, userId)); 2341 } 2342 synchronized (mLock) { 2343 final ShortcutUser user = getUserShortcutsLocked(userId); 2344 user.attemptToRestoreIfNeededAndSave(this, packageName, userId); 2345 2346 if (isPackageInstalled(packageName, userId)) { 2347 user.handlePackageAddedOrUpdated(packageName); 2348 } 2349 } 2350 } 2351 2352 private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) { 2353 if (DEBUG) { 2354 Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, 2355 packageUserId)); 2356 } 2357 cleanUpPackageForAllLoadedUsers(packageName, packageUserId); 2358 } 2359 2360 private void handlePackageDataCleared(String packageName, int packageUserId) { 2361 if (DEBUG) { 2362 Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName, 2363 packageUserId)); 2364 } 2365 cleanUpPackageForAllLoadedUsers(packageName, packageUserId); 2366 } 2367 2368 // === PackageManager interaction === 2369 2370 @Nullable 2371 PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) { 2372 return injectPackageInfo(packageName, userId, true); 2373 } 2374 2375 @Nullable 2376 PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) { 2377 return injectPackageInfo(packageName, userId, false); 2378 } 2379 2380 int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) { 2381 final long token = injectClearCallingIdentity(); 2382 try { 2383 return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS 2384 , userId); 2385 } catch (RemoteException e) { 2386 // Shouldn't happen. 2387 Slog.wtf(TAG, "RemoteException", e); 2388 return -1; 2389 } finally { 2390 injectRestoreCallingIdentity(token); 2391 } 2392 } 2393 2394 @Nullable 2395 @VisibleForTesting 2396 PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId, 2397 boolean getSignatures) { 2398 final long start = injectElapsedRealtime(); 2399 final long token = injectClearCallingIdentity(); 2400 try { 2401 return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS 2402 | (getSignatures ? PackageManager.GET_SIGNATURES : 0) 2403 , userId); 2404 } catch (RemoteException e) { 2405 // Shouldn't happen. 2406 Slog.wtf(TAG, "RemoteException", e); 2407 return null; 2408 } finally { 2409 injectRestoreCallingIdentity(token); 2410 2411 logDurationStat( 2412 (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO), 2413 start); 2414 } 2415 } 2416 2417 @Nullable 2418 @VisibleForTesting 2419 ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) { 2420 final long start = injectElapsedRealtime(); 2421 final long token = injectClearCallingIdentity(); 2422 try { 2423 return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId); 2424 } catch (RemoteException e) { 2425 // Shouldn't happen. 2426 Slog.wtf(TAG, "RemoteException", e); 2427 return null; 2428 } finally { 2429 injectRestoreCallingIdentity(token); 2430 2431 logDurationStat(Stats.GET_APPLICATION_INFO, start); 2432 } 2433 } 2434 2435 @Nullable 2436 @VisibleForTesting 2437 PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) { 2438 final long start = injectElapsedRealtime(); 2439 final long token = injectClearCallingIdentity(); 2440 try { 2441 return mIPackageManager.getPackageInfo(packageName, 2442 PACKAGE_MATCH_FLAGS | PackageManager.GET_ACTIVITIES 2443 | PackageManager.GET_META_DATA, userId); 2444 } catch (RemoteException e) { 2445 // Shouldn't happen. 2446 Slog.wtf(TAG, "RemoteException", e); 2447 return null; 2448 } finally { 2449 injectRestoreCallingIdentity(token); 2450 2451 logDurationStat(Stats.GET_ACTIVITIES_WITH_METADATA, start); 2452 } 2453 } 2454 2455 @Nullable 2456 @VisibleForTesting 2457 List<ApplicationInfo> injectInstalledApplications(@UserIdInt int userId) { 2458 final long start = injectElapsedRealtime(); 2459 final long token = injectClearCallingIdentity(); 2460 try { 2461 final ParceledListSlice<ApplicationInfo> parceledList = 2462 mIPackageManager.getInstalledApplications(PACKAGE_MATCH_FLAGS, userId); 2463 if (parceledList == null) { 2464 return Collections.emptyList(); 2465 } 2466 return parceledList.getList(); 2467 } catch (RemoteException e) { 2468 // Shouldn't happen. 2469 Slog.wtf(TAG, "RemoteException", e); 2470 return null; 2471 } finally { 2472 injectRestoreCallingIdentity(token); 2473 2474 logDurationStat(Stats.GET_INSTALLED_APPLICATIONS, start); 2475 } 2476 } 2477 2478 private void forInstalledApplications(@UserIdInt int userId, 2479 Consumer<ApplicationInfo> callback) { 2480 final List<ApplicationInfo> list = injectInstalledApplications(userId); 2481 for (int i = list.size() - 1; i >= 0; i--) { 2482 final ApplicationInfo ai = list.get(i); 2483 2484 if ((ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { 2485 callback.accept(ai); 2486 } 2487 } 2488 } 2489 2490 private boolean isApplicationFlagSet(String packageName, int userId, int flags) { 2491 final ApplicationInfo ai = injectApplicationInfo(packageName, userId); 2492 return (ai != null) && ((ai.flags & flags) == flags); 2493 } 2494 2495 boolean isPackageInstalled(String packageName, int userId) { 2496 return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED); 2497 } 2498 2499 @Nullable 2500 XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) { 2501 return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key); 2502 } 2503 2504 @Nullable 2505 Resources injectGetResourcesForApplicationAsUser(String packageName, int userId) { 2506 final long start = injectElapsedRealtime(); 2507 final long token = injectClearCallingIdentity(); 2508 try { 2509 return mContext.getPackageManager().getResourcesForApplicationAsUser( 2510 packageName, userId); 2511 } catch (NameNotFoundException e) { 2512 Slog.e(TAG, "Resources for package " + packageName + " not found"); 2513 return null; 2514 } finally { 2515 injectRestoreCallingIdentity(token); 2516 2517 logDurationStat(Stats.GET_APPLICATION_RESOURCES, start); 2518 } 2519 } 2520 2521 // === Backup & restore === 2522 2523 boolean shouldBackupApp(String packageName, int userId) { 2524 return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP); 2525 } 2526 2527 boolean shouldBackupApp(PackageInfo pi) { 2528 return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0; 2529 } 2530 2531 @Override 2532 public byte[] getBackupPayload(@UserIdInt int userId) { 2533 enforceSystem(); 2534 if (DEBUG) { 2535 Slog.d(TAG, "Backing up user " + userId); 2536 } 2537 synchronized (mLock) { 2538 final ShortcutUser user = getUserShortcutsLocked(userId); 2539 if (user == null) { 2540 Slog.w(TAG, "Can't backup: user not found: id=" + userId); 2541 return null; 2542 } 2543 2544 user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave()); 2545 2546 // Then save. 2547 final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024); 2548 try { 2549 saveUserInternalLocked(userId, os, /* forBackup */ true); 2550 } catch (XmlPullParserException|IOException e) { 2551 // Shouldn't happen. 2552 Slog.w(TAG, "Backup failed.", e); 2553 return null; 2554 } 2555 return os.toByteArray(); 2556 } 2557 } 2558 2559 @Override 2560 public void applyRestore(byte[] payload, @UserIdInt int userId) { 2561 enforceSystem(); 2562 if (DEBUG) { 2563 Slog.d(TAG, "Restoring user " + userId); 2564 } 2565 final ShortcutUser user; 2566 final ByteArrayInputStream is = new ByteArrayInputStream(payload); 2567 try { 2568 user = loadUserInternal(userId, is, /* fromBackup */ true); 2569 } catch (XmlPullParserException|IOException e) { 2570 Slog.w(TAG, "Restoration failed.", e); 2571 return; 2572 } 2573 synchronized (mLock) { 2574 mUsers.put(userId, user); 2575 2576 // Then purge all the save images. 2577 final File bitmapPath = getUserBitmapFilePath(userId); 2578 final boolean success = FileUtils.deleteContents(bitmapPath); 2579 if (!success) { 2580 Slog.w(TAG, "Failed to delete " + bitmapPath); 2581 } 2582 2583 saveUserLocked(userId); 2584 } 2585 } 2586 2587 // === Dump === 2588 2589 @Override 2590 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2591 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 2592 != PackageManager.PERMISSION_GRANTED) { 2593 pw.println("Permission Denial: can't dump UserManager from from pid=" 2594 + Binder.getCallingPid() 2595 + ", uid=" + Binder.getCallingUid() 2596 + " without permission " 2597 + android.Manifest.permission.DUMP); 2598 return; 2599 } 2600 dumpInner(pw, args); 2601 } 2602 2603 @VisibleForTesting 2604 void dumpInner(PrintWriter pw, String[] args) { 2605 synchronized (mLock) { 2606 final long now = injectCurrentTimeMillis(); 2607 pw.print("Now: ["); 2608 pw.print(now); 2609 pw.print("] "); 2610 pw.print(formatTime(now)); 2611 2612 pw.print(" Raw last reset: ["); 2613 pw.print(mRawLastResetTime); 2614 pw.print("] "); 2615 pw.print(formatTime(mRawLastResetTime)); 2616 2617 final long last = getLastResetTimeLocked(); 2618 pw.print(" Last reset: ["); 2619 pw.print(last); 2620 pw.print("] "); 2621 pw.print(formatTime(last)); 2622 2623 final long next = getNextResetTimeLocked(); 2624 pw.print(" Next reset: ["); 2625 pw.print(next); 2626 pw.print("] "); 2627 pw.print(formatTime(next)); 2628 2629 pw.print(" Locale change seq#: "); 2630 pw.print(mLocaleChangeSequenceNumber.get()); 2631 pw.println(); 2632 2633 pw.print(" Config:"); 2634 pw.print(" Max icon dim: "); 2635 pw.println(mMaxIconDimension); 2636 pw.print(" Icon format: "); 2637 pw.println(mIconPersistFormat); 2638 pw.print(" Icon quality: "); 2639 pw.println(mIconPersistQuality); 2640 pw.print(" saveDelayMillis: "); 2641 pw.println(mSaveDelayMillis); 2642 pw.print(" resetInterval: "); 2643 pw.println(mResetInterval); 2644 pw.print(" maxUpdatesPerInterval: "); 2645 pw.println(mMaxUpdatesPerInterval); 2646 pw.print(" maxDynamicShortcuts: "); 2647 pw.println(mMaxDynamicShortcuts); 2648 pw.println(); 2649 2650 pw.println(" Stats:"); 2651 synchronized (mStatLock) { 2652 final String p = " "; 2653 dumpStatLS(pw, p, Stats.GET_DEFAULT_HOME, "getHomeActivities()"); 2654 dumpStatLS(pw, p, Stats.LAUNCHER_PERMISSION_CHECK, "Launcher permission check"); 2655 2656 dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()"); 2657 dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)"); 2658 dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo"); 2659 dumpStatLS(pw, p, Stats.CLEANUP_DANGLING_BITMAPS, "cleanupDanglingBitmaps"); 2660 dumpStatLS(pw, p, Stats.GET_ACTIVITIES_WITH_METADATA, "getActivities+metadata"); 2661 dumpStatLS(pw, p, Stats.GET_INSTALLED_APPLICATIONS, "getInstalledApplications"); 2662 dumpStatLS(pw, p, Stats.CHECK_PACKAGE_CHANGES, "checkPackageChanges"); 2663 dumpStatLS(pw, p, Stats.GET_APPLICATION_RESOURCES, "getApplicationResources"); 2664 dumpStatLS(pw, p, Stats.RESOURCE_NAME_LOOKUP, "resourceNameLookup"); 2665 } 2666 2667 for (int i = 0; i < mUsers.size(); i++) { 2668 pw.println(); 2669 mUsers.valueAt(i).dump(pw, " "); 2670 } 2671 2672 pw.println(); 2673 pw.println(" UID state:"); 2674 2675 for (int i = 0; i < mUidState.size(); i++) { 2676 final int uid = mUidState.keyAt(i); 2677 final int state = mUidState.valueAt(i); 2678 pw.print(" UID="); 2679 pw.print(uid); 2680 pw.print(" state="); 2681 pw.print(state); 2682 if (isProcessStateForeground(state)) { 2683 pw.print(" [FG]"); 2684 } 2685 pw.print(" last FG="); 2686 pw.print(mUidLastForegroundElapsedTime.get(uid)); 2687 pw.println(); 2688 } 2689 } 2690 } 2691 2692 static String formatTime(long time) { 2693 Time tobj = new Time(); 2694 tobj.set(time); 2695 return tobj.format("%Y-%m-%d %H:%M:%S"); 2696 } 2697 2698 private void dumpStatLS(PrintWriter pw, String prefix, int statId, String label) { 2699 pw.print(prefix); 2700 final int count = mCountStats[statId]; 2701 final long dur = mDurationStats[statId]; 2702 pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms", 2703 label, count, dur, 2704 (count == 0 ? 0 : ((double) dur) / count))); 2705 } 2706 2707 // === Shell support === 2708 2709 @Override 2710 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 2711 String[] args, ResultReceiver resultReceiver) throws RemoteException { 2712 2713 enforceShell(); 2714 2715 (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver); 2716 } 2717 2718 static class CommandException extends Exception { 2719 public CommandException(String message) { 2720 super(message); 2721 } 2722 } 2723 2724 /** 2725 * Handle "adb shell cmd". 2726 */ 2727 private class MyShellCommand extends ShellCommand { 2728 2729 private int mUserId = UserHandle.USER_SYSTEM; 2730 2731 private void parseOptions(boolean takeUser) 2732 throws CommandException { 2733 String opt; 2734 while ((opt = getNextOption()) != null) { 2735 switch (opt) { 2736 case "--user": 2737 if (takeUser) { 2738 mUserId = UserHandle.parseUserArg(getNextArgRequired()); 2739 break; 2740 } 2741 // fallthrough 2742 default: 2743 throw new CommandException("Unknown option: " + opt); 2744 } 2745 } 2746 } 2747 2748 @Override 2749 public int onCommand(String cmd) { 2750 if (cmd == null) { 2751 return handleDefaultCommands(cmd); 2752 } 2753 final PrintWriter pw = getOutPrintWriter(); 2754 try { 2755 switch (cmd) { 2756 case "reset-package-throttling": 2757 handleResetPackageThrottling(); 2758 break; 2759 case "reset-throttling": 2760 handleResetThrottling(); 2761 break; 2762 case "reset-all-throttling": 2763 handleResetAllThrottling(); 2764 break; 2765 case "override-config": 2766 handleOverrideConfig(); 2767 break; 2768 case "reset-config": 2769 handleResetConfig(); 2770 break; 2771 case "clear-default-launcher": 2772 handleClearDefaultLauncher(); 2773 break; 2774 case "get-default-launcher": 2775 handleGetDefaultLauncher(); 2776 break; 2777 case "refresh-default-launcher": 2778 handleRefreshDefaultLauncher(); 2779 break; 2780 case "unload-user": 2781 handleUnloadUser(); 2782 break; 2783 case "clear-shortcuts": 2784 handleClearShortcuts(); 2785 break; 2786 default: 2787 return handleDefaultCommands(cmd); 2788 } 2789 } catch (CommandException e) { 2790 pw.println("Error: " + e.getMessage()); 2791 return 1; 2792 } 2793 pw.println("Success"); 2794 return 0; 2795 } 2796 2797 @Override 2798 public void onHelp() { 2799 final PrintWriter pw = getOutPrintWriter(); 2800 pw.println("Usage: cmd shortcut COMMAND [options ...]"); 2801 pw.println(); 2802 pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE"); 2803 pw.println(" Reset throttling for a package"); 2804 pw.println(); 2805 pw.println("cmd shortcut reset-throttling [--user USER_ID]"); 2806 pw.println(" Reset throttling for all packages and users"); 2807 pw.println(); 2808 pw.println("cmd shortcut reset-all-throttling"); 2809 pw.println(" Reset the throttling state for all users"); 2810 pw.println(); 2811 pw.println("cmd shortcut override-config CONFIG"); 2812 pw.println(" Override the configuration for testing (will last until reboot)"); 2813 pw.println(); 2814 pw.println("cmd shortcut reset-config"); 2815 pw.println(" Reset the configuration set with \"update-config\""); 2816 pw.println(); 2817 pw.println("cmd shortcut clear-default-launcher [--user USER_ID]"); 2818 pw.println(" Clear the cached default launcher"); 2819 pw.println(); 2820 pw.println("cmd shortcut get-default-launcher [--user USER_ID]"); 2821 pw.println(" Show the cached default launcher"); 2822 pw.println(); 2823 pw.println("cmd shortcut refresh-default-launcher [--user USER_ID]"); 2824 pw.println(" Refresh the cached default launcher"); 2825 pw.println(); 2826 pw.println("cmd shortcut unload-user [--user USER_ID]"); 2827 pw.println(" Unload a user from the memory"); 2828 pw.println(" (This should not affect any observable behavior)"); 2829 pw.println(); 2830 pw.println("cmd shortcut clear-shortcuts [--user USER_ID] PACKAGE"); 2831 pw.println(" Remove all shortcuts from a package, including pinned shortcuts"); 2832 pw.println(); 2833 } 2834 2835 private void handleResetThrottling() throws CommandException { 2836 parseOptions(/* takeUser =*/ true); 2837 2838 Slog.i(TAG, "cmd: handleResetThrottling"); 2839 2840 resetThrottlingInner(mUserId); 2841 } 2842 2843 private void handleResetAllThrottling() { 2844 Slog.i(TAG, "cmd: handleResetAllThrottling"); 2845 2846 resetAllThrottlingInner(); 2847 } 2848 2849 private void handleResetPackageThrottling() throws CommandException { 2850 parseOptions(/* takeUser =*/ true); 2851 2852 final String packageName = getNextArgRequired(); 2853 2854 Slog.i(TAG, "cmd: handleResetPackageThrottling: " + packageName); 2855 2856 resetPackageThrottling(packageName, mUserId); 2857 } 2858 2859 private void handleOverrideConfig() throws CommandException { 2860 final String config = getNextArgRequired(); 2861 2862 Slog.i(TAG, "cmd: handleOverrideConfig: " + config); 2863 2864 synchronized (mLock) { 2865 if (!updateConfigurationLocked(config)) { 2866 throw new CommandException("override-config failed. See logcat for details."); 2867 } 2868 } 2869 } 2870 2871 private void handleResetConfig() { 2872 Slog.i(TAG, "cmd: handleResetConfig"); 2873 2874 synchronized (mLock) { 2875 loadConfigurationLocked(); 2876 } 2877 } 2878 2879 private void clearLauncher() { 2880 synchronized (mLock) { 2881 getUserShortcutsLocked(mUserId).setDefaultLauncherComponent(null); 2882 } 2883 } 2884 2885 private void showLauncher() { 2886 synchronized (mLock) { 2887 // This ensures to set the cached launcher. Package name doesn't matter. 2888 hasShortcutHostPermissionInner("-", mUserId); 2889 2890 getOutPrintWriter().println("Launcher: " 2891 + getUserShortcutsLocked(mUserId).getDefaultLauncherComponent()); 2892 } 2893 } 2894 2895 private void handleClearDefaultLauncher() throws CommandException { 2896 parseOptions(/* takeUser =*/ true); 2897 2898 clearLauncher(); 2899 } 2900 2901 private void handleGetDefaultLauncher() throws CommandException { 2902 parseOptions(/* takeUser =*/ true); 2903 2904 showLauncher(); 2905 } 2906 2907 private void handleRefreshDefaultLauncher() throws CommandException { 2908 parseOptions(/* takeUser =*/ true); 2909 2910 clearLauncher(); 2911 showLauncher(); 2912 } 2913 2914 private void handleUnloadUser() throws CommandException { 2915 parseOptions(/* takeUser =*/ true); 2916 2917 Slog.i(TAG, "cmd: handleUnloadUser: " + mUserId); 2918 2919 ShortcutService.this.handleCleanupUser(mUserId); 2920 } 2921 2922 private void handleClearShortcuts() throws CommandException { 2923 parseOptions(/* takeUser =*/ true); 2924 final String packageName = getNextArgRequired(); 2925 2926 Slog.i(TAG, "cmd: handleClearShortcuts: " + mUserId + ", " + packageName); 2927 2928 ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId); 2929 } 2930 } 2931 2932 // === Unit test support === 2933 2934 // Injection point. 2935 @VisibleForTesting 2936 long injectCurrentTimeMillis() { 2937 return System.currentTimeMillis(); 2938 } 2939 2940 @VisibleForTesting 2941 long injectElapsedRealtime() { 2942 return SystemClock.elapsedRealtime(); 2943 } 2944 2945 // Injection point. 2946 @VisibleForTesting 2947 int injectBinderCallingUid() { 2948 return getCallingUid(); 2949 } 2950 2951 private int getCallingUserId() { 2952 return UserHandle.getUserId(injectBinderCallingUid()); 2953 } 2954 2955 // Injection point. 2956 @VisibleForTesting 2957 long injectClearCallingIdentity() { 2958 return Binder.clearCallingIdentity(); 2959 } 2960 2961 // Injection point. 2962 @VisibleForTesting 2963 void injectRestoreCallingIdentity(long token) { 2964 Binder.restoreCallingIdentity(token); 2965 } 2966 2967 final void wtf(String message) { 2968 wtf( message, /* exception= */ null); 2969 } 2970 2971 // Injection point. 2972 void wtf(String message, Exception e) { 2973 Slog.wtf(TAG, message, e); 2974 } 2975 2976 @VisibleForTesting 2977 File injectSystemDataPath() { 2978 return Environment.getDataSystemDirectory(); 2979 } 2980 2981 @VisibleForTesting 2982 File injectUserDataPath(@UserIdInt int userId) { 2983 return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER); 2984 } 2985 2986 @VisibleForTesting 2987 boolean injectIsLowRamDevice() { 2988 return ActivityManager.isLowRamDeviceStatic(); 2989 } 2990 2991 @VisibleForTesting 2992 void injectRegisterUidObserver(IUidObserver observer, int which) { 2993 try { 2994 ActivityManagerNative.getDefault().registerUidObserver(observer, which); 2995 } catch (RemoteException shouldntHappen) { 2996 } 2997 } 2998 2999 @VisibleForTesting 3000 PackageManagerInternal injectPackageManagerInternal() { 3001 return mPackageManagerInternal; 3002 } 3003 3004 File getUserBitmapFilePath(@UserIdInt int userId) { 3005 return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS); 3006 } 3007 3008 @VisibleForTesting 3009 SparseArray<ShortcutUser> getShortcutsForTest() { 3010 return mUsers; 3011 } 3012 3013 @VisibleForTesting 3014 int getMaxDynamicShortcutsForTest() { 3015 return mMaxDynamicShortcuts; 3016 } 3017 3018 @VisibleForTesting 3019 int getMaxUpdatesPerIntervalForTest() { 3020 return mMaxUpdatesPerInterval; 3021 } 3022 3023 @VisibleForTesting 3024 long getResetIntervalForTest() { 3025 return mResetInterval; 3026 } 3027 3028 @VisibleForTesting 3029 int getMaxIconDimensionForTest() { 3030 return mMaxIconDimension; 3031 } 3032 3033 @VisibleForTesting 3034 CompressFormat getIconPersistFormatForTest() { 3035 return mIconPersistFormat; 3036 } 3037 3038 @VisibleForTesting 3039 int getIconPersistQualityForTest() { 3040 return mIconPersistQuality; 3041 } 3042 3043 @VisibleForTesting 3044 ShortcutPackage getPackageShortcutForTest(String packageName, int userId) { 3045 synchronized (mLock) { 3046 final ShortcutUser user = mUsers.get(userId); 3047 if (user == null) return null; 3048 3049 return user.getAllPackagesForTest().get(packageName); 3050 } 3051 } 3052 3053 @VisibleForTesting 3054 ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) { 3055 synchronized (mLock) { 3056 final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId); 3057 if (pkg == null) return null; 3058 3059 return pkg.findShortcutById(shortcutId); 3060 } 3061 } 3062 3063 /** 3064 * Control whether {@link #verifyStates} should be performed. We always perform it during unit 3065 * tests. 3066 */ 3067 @VisibleForTesting 3068 boolean injectShouldPerformVerification() { 3069 return DEBUG; 3070 } 3071 3072 /** 3073 * Check various internal states and throws if there's any inconsistency. 3074 * This is normally only enabled during unit tests. 3075 */ 3076 final void verifyStates() { 3077 if (injectShouldPerformVerification()) { 3078 verifyStatesInner(); 3079 } 3080 } 3081 3082 private void verifyStatesInner() { 3083 synchronized (this) { 3084 forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates)); 3085 } 3086 } 3087} 3088