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