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