Loading core/java/android/content/pm/ShortcutServiceInternal.java +7 −0 Original line number Diff line number Diff line Loading @@ -73,4 +73,11 @@ public abstract class ShortcutServiceInternal { * any locks in this method. */ public abstract void onSystemLocaleChangedNoLock(); /** * Called by PM before sending package broadcasts to other components. PM doesn't hold the PM * lock, but do not take any locks in here anyway, and don't do any heavy tasks, as doing so * would slow down all the package broadcasts. */ public abstract void onPackageBroadcast(Intent intent); } services/core/java/com/android/server/pm/PackageManagerService.java +8 −0 Original line number Diff line number Diff line Loading @@ -159,6 +159,7 @@ import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.ShortcutServiceInternal; import android.content.pm.Signature; import android.content.pm.UserInfo; import android.content.pm.VerifierDeviceIdentity; Loading Loading @@ -11443,6 +11444,9 @@ public class PackageManagerService extends IPackageManager.Stub { } else { resolvedUserIds = userIds; } final ShortcutServiceInternal shortcutService = LocalServices.getService(ShortcutServiceInternal.class); for (int id : resolvedUserIds) { final Intent intent = new Intent(action, pkg != null ? Uri.fromParts("package", pkg, null) : null); Loading @@ -11467,6 +11471,10 @@ public class PackageManagerService extends IPackageManager.Stub { + intent.toShortString(false, true, false, false) + " " + intent.getExtras(), here); } // TODO b/29385425 Consider making lifecycle callbacks for this. if (shortcutService != null) { shortcutService.onPackageBroadcast(intent); } am.broadcastIntent(null, intent, null, finishedReceiver, 0, null, null, null, android.app.AppOpsManager.OP_NONE, null, finishedReceiver != null, false, id); services/core/java/com/android/server/pm/ShortcutPendingTasks.java 0 → 100644 +159 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.pm; import android.annotation.NonNull; import android.util.Slog; import java.io.PrintWriter; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.logging.Handler; /** * Used by {@link ShortcutService} to register tasks to be executed on Handler and also wait for * all pending tasks. * * Tasks can be registered with {@link #addTask(Runnable)}. Call {@link #waitOnAllTasks()} to wait * on all tasks that have been registered. * * In order to avoid deadlocks, {@link #waitOnAllTasks} MUST NOT be called with any lock held, nor * on the handler thread. These conditions are checked by {@link #mWaitThreadChecker} and wtf'ed. * * During unit tests, we can't run tasks asynchronously, so we just run Runnables synchronously, * which also means the "is lock held" check doesn't work properly during unit tests (e.g. normally * when a Runnable is executed on a Handler, the thread doesn't hold any lock, but during the tests * we just run a Runnable on the thread that registers it, so the thread may or may not hold locks.) * So unfortunately we have to disable {@link #mWaitThreadChecker} during unit tests. * * Because of the complications like those, this class should be used only for specific purposes: * - {@link #addTask(Runnable)} should only be used to register tasks on callbacks from lower level * services like the package manager or the activity manager. * * - {@link #waitOnAllTasks} should only be called at the entry point of RPC calls (or the test only * accessors}. */ public class ShortcutPendingTasks { private static final String TAG = "ShortcutPendingTasks"; private static final boolean DEBUG = false || ShortcutService.DEBUG; // DO NOT SUBMIT WITH TRUE. private final Consumer<Runnable> mRunner; private final BooleanSupplier mWaitThreadChecker; private final Consumer<Throwable> mExceptionHandler; /** # of tasks in the queue, including the running one. */ private final AtomicInteger mRunningTaskCount = new AtomicInteger(); /** For dumpsys */ private final AtomicLong mLastTaskStartTime = new AtomicLong(); /** * Constructor. In order to allow injection during unit tests, it doesn't take a * {@link Handler} directly, and instead takes {@code runner} which will post an argument * to a handler. */ public ShortcutPendingTasks(Consumer<Runnable> runner, BooleanSupplier waitThreadChecker, Consumer<Throwable> exceptionHandler) { mRunner = runner; mWaitThreadChecker = waitThreadChecker; mExceptionHandler = exceptionHandler; } private static void dlog(String message) { if (DEBUG) { Slog.d(TAG, message); } } /** * Block until all tasks that are already queued finish. DO NOT call it while holding any lock * or on the handler thread. */ public boolean waitOnAllTasks() { dlog("waitOnAllTasks: enter"); try { // Make sure it's not holding the lock. if (!mWaitThreadChecker.getAsBoolean()) { return false; } // Optimize for the no-task case. if (mRunningTaskCount.get() == 0) { return true; } final CountDownLatch latch = new CountDownLatch(1); addTask(latch::countDown); for (; ; ) { try { if (latch.await(1, TimeUnit.SECONDS)) { return true; } dlog("waitOnAllTasks: Task(s) still running..."); } catch (InterruptedException ignore) { } } } finally { dlog("waitOnAllTasks: exit"); } } /** * Add a new task. This operation is lock-free. */ public void addTask(Runnable task) { mRunningTaskCount.incrementAndGet(); mLastTaskStartTime.set(System.currentTimeMillis()); dlog("Task registered"); mRunner.accept(() -> { try { dlog("Task started"); task.run(); } catch (Throwable th) { mExceptionHandler.accept(th); } finally { dlog("Task finished"); mRunningTaskCount.decrementAndGet(); } }); } public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { pw.print(prefix); pw.print("Pending tasks: # running tasks: "); pw.println(mRunningTaskCount.get()); pw.print(prefix); pw.print(" Last task started time: "); final long lastStarted = mLastTaskStartTime.get(); pw.print(" ["); pw.print(lastStarted); pw.print("] "); pw.println(ShortcutService.formatTime(lastStarted)); } } services/core/java/com/android/server/pm/ShortcutService.java +169 −32 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import android.graphics.Bitmap.CompressFormat; import android.graphics.Canvas; import android.graphics.RectF; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Binder; import android.os.Environment; import android.os.FileUtils; Loading Loading @@ -81,7 +82,6 @@ import android.view.IWindowManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; Loading Loading @@ -122,9 +122,6 @@ import java.util.function.Predicate; /** * TODO: * - Deal with the async nature of PACKAGE_ADD. Basically when a publisher does anything after * it's upgraded, the manager should make sure the upgrade process has been executed. * * - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi. * -> But TypedValue.applyDimension() doesn't differentiate x and y..? * Loading Loading @@ -304,6 +301,8 @@ public class ShortcutService extends IShortcutService.Stub { private final AtomicBoolean mBootCompleted = new AtomicBoolean(); private final ShortcutPendingTasks mPendingTasks; private static final int PACKAGE_MATCH_FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE Loading Loading @@ -355,6 +354,12 @@ public class ShortcutService extends IShortcutService.Stub { @interface ShortcutOperation { } @GuardedBy("mLock") private int mWtfCount = 0; @GuardedBy("mLock") private Exception mLastWtfStacktrace; public ShortcutService(Context context) { this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false); } Loading @@ -371,16 +376,41 @@ public class ShortcutService extends IShortcutService.Stub { mUsageStatsManagerInternal = Preconditions.checkNotNull( LocalServices.getService(UsageStatsManagerInternal.class)); mPendingTasks = new ShortcutPendingTasks( this::injectPostToHandler, this::injectCheckPendingTaskWaitThread, throwable -> wtf(throwable.getMessage(), throwable)); if (onlyForPackageManagerApis) { return; // Don't do anything further. For unit tests only. } mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false); injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE); } /** * Check whether {@link ShortcutPendingTasks#waitOnAllTasks()} can be called on the current * thread. * * During unit tests, all tasks are executed synchronously which makes the lock held check would * misfire, so we override this method to always return true. */ @VisibleForTesting boolean injectCheckPendingTaskWaitThread() { // We shouldn't wait while holding mLock. We should never do this so wtf(). if (Thread.holdsLock(mLock)) { wtf("waitOnAllTasks() called while holding the lock"); return false; } // This shouldn't be called on the handler thread either. if (Thread.currentThread() == mHandler.getLooper().getThread()) { wtf("waitOnAllTasks() called on handler thread"); return false; } return true; } void logDurationStat(int statId, long start) { synchronized (mStatLock) { mCountStats[statId]++; Loading Loading @@ -1486,6 +1516,8 @@ public class ShortcutService extends IShortcutService.Stub { @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); final int size = newShortcuts.size(); Loading Loading @@ -1535,6 +1567,8 @@ public class ShortcutService extends IShortcutService.Stub { @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); final int size = newShortcuts.size(); Loading Loading @@ -1613,6 +1647,8 @@ public class ShortcutService extends IShortcutService.Stub { @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); final int size = newShortcuts.size(); Loading Loading @@ -1663,6 +1699,8 @@ public class ShortcutService extends IShortcutService.Stub { verifyCaller(packageName, userId); Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); Loading Loading @@ -1690,6 +1728,8 @@ public class ShortcutService extends IShortcutService.Stub { verifyCaller(packageName, userId); Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); Loading @@ -1710,6 +1750,8 @@ public class ShortcutService extends IShortcutService.Stub { verifyCaller(packageName, userId); Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); Loading @@ -1732,6 +1774,8 @@ public class ShortcutService extends IShortcutService.Stub { public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(); } Loading @@ -1744,6 +1788,9 @@ public class ShortcutService extends IShortcutService.Stub { public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName, @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { return getShortcutsWithQueryLocked( packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, Loading @@ -1755,6 +1802,9 @@ public class ShortcutService extends IShortcutService.Stub { public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName, @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { return getShortcutsWithQueryLocked( packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, Loading @@ -1766,6 +1816,9 @@ public class ShortcutService extends IShortcutService.Stub { public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName, @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { return getShortcutsWithQueryLocked( packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, Loading Loading @@ -1795,6 +1848,8 @@ public class ShortcutService extends IShortcutService.Stub { public int getRemainingCallCount(String packageName, @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { return mMaxUpdatesPerInterval - getPackageShortcutsLocked(packageName, userId).getApiCallCount(); Loading @@ -1805,6 +1860,8 @@ public class ShortcutService extends IShortcutService.Stub { public long getRateLimitResetTime(String packageName, @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { return getNextResetTimeLocked(); } Loading @@ -1823,6 +1880,8 @@ public class ShortcutService extends IShortcutService.Stub { public void reportShortcutUsed(String packageName, String shortcutId, int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); Preconditions.checkNotNull(shortcutId); if (DEBUG) { Loading Loading @@ -1855,6 +1914,8 @@ public class ShortcutService extends IShortcutService.Stub { public void resetThrottling() { enforceSystemOrShell(); mPendingTasks.waitOnAllTasks(); resetThrottlingInner(getCallingUserId()); } Loading Loading @@ -1887,6 +1948,9 @@ public class ShortcutService extends IShortcutService.Stub { if (DEBUG) { Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId); } mPendingTasks.waitOnAllTasks(); enforceResetThrottlingPermission(); resetPackageThrottling(packageName, userId); } Loading Loading @@ -2049,6 +2113,14 @@ public class ShortcutService extends IShortcutService.Stub { @Nullable String packageName, @Nullable List<String> shortcutIds, @Nullable ComponentName componentName, int queryFlags, int userId) { // When this method is called from onShortcutChangedInner() in LauncherApps, // we're on the handler thread. Do not try to wait on tasks. Not waiting for pending // tasks on this specific case should be fine. if (Thread.currentThread() != mHandler.getLooper().getThread()) { mPendingTasks.waitOnAllTasks(); } final ArrayList<ShortcutInfo> ret = new ArrayList<>(); final boolean cloneKeyFieldOnly = ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0); Loading Loading @@ -2127,6 +2199,8 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkStringNotEmpty(packageName, "packageName"); Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) .attemptToRestoreIfNeededAndSave(); Loading Loading @@ -2164,6 +2238,8 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkStringNotEmpty(packageName, "packageName"); Preconditions.checkNotNull(shortcutIds, "shortcutIds"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { final ShortcutLauncher launcher = getLauncherShortcutsLocked(callingPackage, userId, launcherUserId); Loading @@ -2184,6 +2260,8 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty"); Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) .attemptToRestoreIfNeededAndSave(); Loading Loading @@ -2213,6 +2291,8 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkNotNull(packageName, "packageName"); Preconditions.checkNotNull(shortcutId, "shortcutId"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) .attemptToRestoreIfNeededAndSave(); Loading @@ -2237,6 +2317,8 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkNotNull(packageName, "packageName"); Preconditions.checkNotNull(shortcutId, "shortcutId"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) .attemptToRestoreIfNeededAndSave(); Loading Loading @@ -2297,8 +2379,17 @@ public class ShortcutService extends IShortcutService.Stub { if (DEBUG) { Slog.d(TAG, "onSystemLocaleChangedNoLock: " + mLocaleChangeSequenceNumber.get()); } injectPostToHandler(() -> handleLocaleChanged()); mPendingTasks.addTask(() -> handleLocaleChanged()); } } @Override public void onPackageBroadcast(Intent intent) { if (DEBUG) { Slog.d(TAG, "onPackageBroadcast"); } mPendingTasks.addTask(() -> ShortcutService.this.onPackageBroadcast( new Intent(intent))); } } Loading @@ -2316,37 +2407,49 @@ public class ShortcutService extends IShortcutService.Stub { } } /** * Package event callbacks. */ @VisibleForTesting final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override public void onPackageAdded(String packageName, int uid) { handlePackageAdded(packageName, getChangingUserId()); private void onPackageBroadcast(Intent intent) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); if (userId == UserHandle.USER_NULL) { Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent); return; } @Override public void onPackageUpdateFinished(String packageName, int uid) { handlePackageUpdateFinished(packageName, getChangingUserId()); final String action = intent.getAction(); if (!mUserManager.isUserUnlocked(userId)) { if (DEBUG) { Slog.d(TAG, "Ignoring package broadcast " + action + " for locked/stopped user " + userId); } return; } @Override public void onPackageRemoved(String packageName, int uid) { handlePackageRemoved(packageName, getChangingUserId()); final Uri intentUri = intent.getData(); final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart() : null; if (packageName == null) { Slog.w(TAG, "Intent broadcast does not contain package name: " + intent); return; } @Override public void onPackageDataCleared(String packageName, int uid) { handlePackageDataCleared(packageName, getChangingUserId()); final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { if (replacing) { handlePackageUpdateFinished(packageName, userId); } else { handlePackageAdded(packageName, userId); } } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { if (!replacing) { handlePackageRemoved(packageName, userId); } } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { handlePackageChanged(packageName, userId); @Override public boolean onPackageChanged(String packageName, int uid, String[] components) { handlePackageChanged(packageName, getChangingUserId()); return false; // We don't need to receive onSomePackagesChanged(), so just false. } else if (Intent.ACTION_PACKAGE_DATA_CLEARED.equals(action)) { handlePackageDataCleared(packageName, userId); } } }; /** * Called when a user is unlocked. Loading Loading @@ -2984,6 +3087,18 @@ public class ShortcutService extends IShortcutService.Stub { dumpStatLS(pw, p, Stats.IS_ACTIVITY_ENABLED, "isActivityEnabled"); } pw.println(); pw.print(" #Failures: "); pw.println(mWtfCount); if (mLastWtfStacktrace != null) { pw.print(" Last failure stack trace: "); pw.println(Log.getStackTraceString(mLastWtfStacktrace)); } pw.println(); mPendingTasks.dump(pw, " "); for (int i = 0; i < mUsers.size(); i++) { pw.println(); mUsers.valueAt(i).dump(pw, " "); Loading Loading @@ -3032,6 +3147,8 @@ public class ShortcutService extends IShortcutService.Stub { enforceShell(); mPendingTasks.waitOnAllTasks(); final int status = (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver); resultReceiver.send(status, null); Loading Loading @@ -3303,7 +3420,14 @@ public class ShortcutService extends IShortcutService.Stub { } // Injection point. void wtf(String message, Exception e) { void wtf(String message, Throwable e) { if (e == null) { e = new RuntimeException("Stacktrace"); } synchronized (mLock) { mWtfCount++; mLastWtfStacktrace = new Exception("Last failure was logged here:"); } Slog.wtf(TAG, message, e); } Loading Loading @@ -3376,6 +3500,7 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting ShortcutPackage getPackageShortcutForTest(String packageName, int userId) { mPendingTasks.waitOnAllTasks(); synchronized (mLock) { final ShortcutUser user = mUsers.get(userId); if (user == null) return null; Loading @@ -3386,8 +3511,12 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) { mPendingTasks.waitOnAllTasks(); synchronized (mLock) { final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId); final ShortcutUser user = mUsers.get(userId); if (user == null) return null; final ShortcutPackage pkg = user.getAllPackagesForTest().get(packageName); if (pkg == null) return null; return pkg.findShortcutById(shortcutId); Loading Loading @@ -3422,4 +3551,12 @@ public class ShortcutService extends IShortcutService.Stub { forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates)); } } ShortcutPendingTasks getPendingTasksForTest() { return mPendingTasks; } Object getLockForTest() { return mLock; } } services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +70 −39 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/content/pm/ShortcutServiceInternal.java +7 −0 Original line number Diff line number Diff line Loading @@ -73,4 +73,11 @@ public abstract class ShortcutServiceInternal { * any locks in this method. */ public abstract void onSystemLocaleChangedNoLock(); /** * Called by PM before sending package broadcasts to other components. PM doesn't hold the PM * lock, but do not take any locks in here anyway, and don't do any heavy tasks, as doing so * would slow down all the package broadcasts. */ public abstract void onPackageBroadcast(Intent intent); }
services/core/java/com/android/server/pm/PackageManagerService.java +8 −0 Original line number Diff line number Diff line Loading @@ -159,6 +159,7 @@ import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.ShortcutServiceInternal; import android.content.pm.Signature; import android.content.pm.UserInfo; import android.content.pm.VerifierDeviceIdentity; Loading Loading @@ -11443,6 +11444,9 @@ public class PackageManagerService extends IPackageManager.Stub { } else { resolvedUserIds = userIds; } final ShortcutServiceInternal shortcutService = LocalServices.getService(ShortcutServiceInternal.class); for (int id : resolvedUserIds) { final Intent intent = new Intent(action, pkg != null ? Uri.fromParts("package", pkg, null) : null); Loading @@ -11467,6 +11471,10 @@ public class PackageManagerService extends IPackageManager.Stub { + intent.toShortString(false, true, false, false) + " " + intent.getExtras(), here); } // TODO b/29385425 Consider making lifecycle callbacks for this. if (shortcutService != null) { shortcutService.onPackageBroadcast(intent); } am.broadcastIntent(null, intent, null, finishedReceiver, 0, null, null, null, android.app.AppOpsManager.OP_NONE, null, finishedReceiver != null, false, id);
services/core/java/com/android/server/pm/ShortcutPendingTasks.java 0 → 100644 +159 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.pm; import android.annotation.NonNull; import android.util.Slog; import java.io.PrintWriter; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.logging.Handler; /** * Used by {@link ShortcutService} to register tasks to be executed on Handler and also wait for * all pending tasks. * * Tasks can be registered with {@link #addTask(Runnable)}. Call {@link #waitOnAllTasks()} to wait * on all tasks that have been registered. * * In order to avoid deadlocks, {@link #waitOnAllTasks} MUST NOT be called with any lock held, nor * on the handler thread. These conditions are checked by {@link #mWaitThreadChecker} and wtf'ed. * * During unit tests, we can't run tasks asynchronously, so we just run Runnables synchronously, * which also means the "is lock held" check doesn't work properly during unit tests (e.g. normally * when a Runnable is executed on a Handler, the thread doesn't hold any lock, but during the tests * we just run a Runnable on the thread that registers it, so the thread may or may not hold locks.) * So unfortunately we have to disable {@link #mWaitThreadChecker} during unit tests. * * Because of the complications like those, this class should be used only for specific purposes: * - {@link #addTask(Runnable)} should only be used to register tasks on callbacks from lower level * services like the package manager or the activity manager. * * - {@link #waitOnAllTasks} should only be called at the entry point of RPC calls (or the test only * accessors}. */ public class ShortcutPendingTasks { private static final String TAG = "ShortcutPendingTasks"; private static final boolean DEBUG = false || ShortcutService.DEBUG; // DO NOT SUBMIT WITH TRUE. private final Consumer<Runnable> mRunner; private final BooleanSupplier mWaitThreadChecker; private final Consumer<Throwable> mExceptionHandler; /** # of tasks in the queue, including the running one. */ private final AtomicInteger mRunningTaskCount = new AtomicInteger(); /** For dumpsys */ private final AtomicLong mLastTaskStartTime = new AtomicLong(); /** * Constructor. In order to allow injection during unit tests, it doesn't take a * {@link Handler} directly, and instead takes {@code runner} which will post an argument * to a handler. */ public ShortcutPendingTasks(Consumer<Runnable> runner, BooleanSupplier waitThreadChecker, Consumer<Throwable> exceptionHandler) { mRunner = runner; mWaitThreadChecker = waitThreadChecker; mExceptionHandler = exceptionHandler; } private static void dlog(String message) { if (DEBUG) { Slog.d(TAG, message); } } /** * Block until all tasks that are already queued finish. DO NOT call it while holding any lock * or on the handler thread. */ public boolean waitOnAllTasks() { dlog("waitOnAllTasks: enter"); try { // Make sure it's not holding the lock. if (!mWaitThreadChecker.getAsBoolean()) { return false; } // Optimize for the no-task case. if (mRunningTaskCount.get() == 0) { return true; } final CountDownLatch latch = new CountDownLatch(1); addTask(latch::countDown); for (; ; ) { try { if (latch.await(1, TimeUnit.SECONDS)) { return true; } dlog("waitOnAllTasks: Task(s) still running..."); } catch (InterruptedException ignore) { } } } finally { dlog("waitOnAllTasks: exit"); } } /** * Add a new task. This operation is lock-free. */ public void addTask(Runnable task) { mRunningTaskCount.incrementAndGet(); mLastTaskStartTime.set(System.currentTimeMillis()); dlog("Task registered"); mRunner.accept(() -> { try { dlog("Task started"); task.run(); } catch (Throwable th) { mExceptionHandler.accept(th); } finally { dlog("Task finished"); mRunningTaskCount.decrementAndGet(); } }); } public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { pw.print(prefix); pw.print("Pending tasks: # running tasks: "); pw.println(mRunningTaskCount.get()); pw.print(prefix); pw.print(" Last task started time: "); final long lastStarted = mLastTaskStartTime.get(); pw.print(" ["); pw.print(lastStarted); pw.print("] "); pw.println(ShortcutService.formatTime(lastStarted)); } }
services/core/java/com/android/server/pm/ShortcutService.java +169 −32 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import android.graphics.Bitmap.CompressFormat; import android.graphics.Canvas; import android.graphics.RectF; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Binder; import android.os.Environment; import android.os.FileUtils; Loading Loading @@ -81,7 +82,6 @@ import android.view.IWindowManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; Loading Loading @@ -122,9 +122,6 @@ import java.util.function.Predicate; /** * TODO: * - Deal with the async nature of PACKAGE_ADD. Basically when a publisher does anything after * it's upgraded, the manager should make sure the upgrade process has been executed. * * - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi. * -> But TypedValue.applyDimension() doesn't differentiate x and y..? * Loading Loading @@ -304,6 +301,8 @@ public class ShortcutService extends IShortcutService.Stub { private final AtomicBoolean mBootCompleted = new AtomicBoolean(); private final ShortcutPendingTasks mPendingTasks; private static final int PACKAGE_MATCH_FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE Loading Loading @@ -355,6 +354,12 @@ public class ShortcutService extends IShortcutService.Stub { @interface ShortcutOperation { } @GuardedBy("mLock") private int mWtfCount = 0; @GuardedBy("mLock") private Exception mLastWtfStacktrace; public ShortcutService(Context context) { this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false); } Loading @@ -371,16 +376,41 @@ public class ShortcutService extends IShortcutService.Stub { mUsageStatsManagerInternal = Preconditions.checkNotNull( LocalServices.getService(UsageStatsManagerInternal.class)); mPendingTasks = new ShortcutPendingTasks( this::injectPostToHandler, this::injectCheckPendingTaskWaitThread, throwable -> wtf(throwable.getMessage(), throwable)); if (onlyForPackageManagerApis) { return; // Don't do anything further. For unit tests only. } mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false); injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE); } /** * Check whether {@link ShortcutPendingTasks#waitOnAllTasks()} can be called on the current * thread. * * During unit tests, all tasks are executed synchronously which makes the lock held check would * misfire, so we override this method to always return true. */ @VisibleForTesting boolean injectCheckPendingTaskWaitThread() { // We shouldn't wait while holding mLock. We should never do this so wtf(). if (Thread.holdsLock(mLock)) { wtf("waitOnAllTasks() called while holding the lock"); return false; } // This shouldn't be called on the handler thread either. if (Thread.currentThread() == mHandler.getLooper().getThread()) { wtf("waitOnAllTasks() called on handler thread"); return false; } return true; } void logDurationStat(int statId, long start) { synchronized (mStatLock) { mCountStats[statId]++; Loading Loading @@ -1486,6 +1516,8 @@ public class ShortcutService extends IShortcutService.Stub { @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); final int size = newShortcuts.size(); Loading Loading @@ -1535,6 +1567,8 @@ public class ShortcutService extends IShortcutService.Stub { @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); final int size = newShortcuts.size(); Loading Loading @@ -1613,6 +1647,8 @@ public class ShortcutService extends IShortcutService.Stub { @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList(); final int size = newShortcuts.size(); Loading Loading @@ -1663,6 +1699,8 @@ public class ShortcutService extends IShortcutService.Stub { verifyCaller(packageName, userId); Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); Loading Loading @@ -1690,6 +1728,8 @@ public class ShortcutService extends IShortcutService.Stub { verifyCaller(packageName, userId); Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); Loading @@ -1710,6 +1750,8 @@ public class ShortcutService extends IShortcutService.Stub { verifyCaller(packageName, userId); Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); Loading @@ -1732,6 +1774,8 @@ public class ShortcutService extends IShortcutService.Stub { public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(); } Loading @@ -1744,6 +1788,9 @@ public class ShortcutService extends IShortcutService.Stub { public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName, @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { return getShortcutsWithQueryLocked( packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, Loading @@ -1755,6 +1802,9 @@ public class ShortcutService extends IShortcutService.Stub { public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName, @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { return getShortcutsWithQueryLocked( packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, Loading @@ -1766,6 +1816,9 @@ public class ShortcutService extends IShortcutService.Stub { public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName, @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { return getShortcutsWithQueryLocked( packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, Loading Loading @@ -1795,6 +1848,8 @@ public class ShortcutService extends IShortcutService.Stub { public int getRemainingCallCount(String packageName, @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { return mMaxUpdatesPerInterval - getPackageShortcutsLocked(packageName, userId).getApiCallCount(); Loading @@ -1805,6 +1860,8 @@ public class ShortcutService extends IShortcutService.Stub { public long getRateLimitResetTime(String packageName, @UserIdInt int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { return getNextResetTimeLocked(); } Loading @@ -1823,6 +1880,8 @@ public class ShortcutService extends IShortcutService.Stub { public void reportShortcutUsed(String packageName, String shortcutId, int userId) { verifyCaller(packageName, userId); mPendingTasks.waitOnAllTasks(); Preconditions.checkNotNull(shortcutId); if (DEBUG) { Loading Loading @@ -1855,6 +1914,8 @@ public class ShortcutService extends IShortcutService.Stub { public void resetThrottling() { enforceSystemOrShell(); mPendingTasks.waitOnAllTasks(); resetThrottlingInner(getCallingUserId()); } Loading Loading @@ -1887,6 +1948,9 @@ public class ShortcutService extends IShortcutService.Stub { if (DEBUG) { Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId); } mPendingTasks.waitOnAllTasks(); enforceResetThrottlingPermission(); resetPackageThrottling(packageName, userId); } Loading Loading @@ -2049,6 +2113,14 @@ public class ShortcutService extends IShortcutService.Stub { @Nullable String packageName, @Nullable List<String> shortcutIds, @Nullable ComponentName componentName, int queryFlags, int userId) { // When this method is called from onShortcutChangedInner() in LauncherApps, // we're on the handler thread. Do not try to wait on tasks. Not waiting for pending // tasks on this specific case should be fine. if (Thread.currentThread() != mHandler.getLooper().getThread()) { mPendingTasks.waitOnAllTasks(); } final ArrayList<ShortcutInfo> ret = new ArrayList<>(); final boolean cloneKeyFieldOnly = ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0); Loading Loading @@ -2127,6 +2199,8 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkStringNotEmpty(packageName, "packageName"); Preconditions.checkStringNotEmpty(shortcutId, "shortcutId"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) .attemptToRestoreIfNeededAndSave(); Loading Loading @@ -2164,6 +2238,8 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkStringNotEmpty(packageName, "packageName"); Preconditions.checkNotNull(shortcutIds, "shortcutIds"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { final ShortcutLauncher launcher = getLauncherShortcutsLocked(callingPackage, userId, launcherUserId); Loading @@ -2184,6 +2260,8 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty"); Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) .attemptToRestoreIfNeededAndSave(); Loading Loading @@ -2213,6 +2291,8 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkNotNull(packageName, "packageName"); Preconditions.checkNotNull(shortcutId, "shortcutId"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) .attemptToRestoreIfNeededAndSave(); Loading @@ -2237,6 +2317,8 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkNotNull(packageName, "packageName"); Preconditions.checkNotNull(shortcutId, "shortcutId"); mPendingTasks.waitOnAllTasks(); synchronized (mLock) { getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) .attemptToRestoreIfNeededAndSave(); Loading Loading @@ -2297,8 +2379,17 @@ public class ShortcutService extends IShortcutService.Stub { if (DEBUG) { Slog.d(TAG, "onSystemLocaleChangedNoLock: " + mLocaleChangeSequenceNumber.get()); } injectPostToHandler(() -> handleLocaleChanged()); mPendingTasks.addTask(() -> handleLocaleChanged()); } } @Override public void onPackageBroadcast(Intent intent) { if (DEBUG) { Slog.d(TAG, "onPackageBroadcast"); } mPendingTasks.addTask(() -> ShortcutService.this.onPackageBroadcast( new Intent(intent))); } } Loading @@ -2316,37 +2407,49 @@ public class ShortcutService extends IShortcutService.Stub { } } /** * Package event callbacks. */ @VisibleForTesting final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override public void onPackageAdded(String packageName, int uid) { handlePackageAdded(packageName, getChangingUserId()); private void onPackageBroadcast(Intent intent) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); if (userId == UserHandle.USER_NULL) { Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent); return; } @Override public void onPackageUpdateFinished(String packageName, int uid) { handlePackageUpdateFinished(packageName, getChangingUserId()); final String action = intent.getAction(); if (!mUserManager.isUserUnlocked(userId)) { if (DEBUG) { Slog.d(TAG, "Ignoring package broadcast " + action + " for locked/stopped user " + userId); } return; } @Override public void onPackageRemoved(String packageName, int uid) { handlePackageRemoved(packageName, getChangingUserId()); final Uri intentUri = intent.getData(); final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart() : null; if (packageName == null) { Slog.w(TAG, "Intent broadcast does not contain package name: " + intent); return; } @Override public void onPackageDataCleared(String packageName, int uid) { handlePackageDataCleared(packageName, getChangingUserId()); final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { if (replacing) { handlePackageUpdateFinished(packageName, userId); } else { handlePackageAdded(packageName, userId); } } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { if (!replacing) { handlePackageRemoved(packageName, userId); } } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { handlePackageChanged(packageName, userId); @Override public boolean onPackageChanged(String packageName, int uid, String[] components) { handlePackageChanged(packageName, getChangingUserId()); return false; // We don't need to receive onSomePackagesChanged(), so just false. } else if (Intent.ACTION_PACKAGE_DATA_CLEARED.equals(action)) { handlePackageDataCleared(packageName, userId); } } }; /** * Called when a user is unlocked. Loading Loading @@ -2984,6 +3087,18 @@ public class ShortcutService extends IShortcutService.Stub { dumpStatLS(pw, p, Stats.IS_ACTIVITY_ENABLED, "isActivityEnabled"); } pw.println(); pw.print(" #Failures: "); pw.println(mWtfCount); if (mLastWtfStacktrace != null) { pw.print(" Last failure stack trace: "); pw.println(Log.getStackTraceString(mLastWtfStacktrace)); } pw.println(); mPendingTasks.dump(pw, " "); for (int i = 0; i < mUsers.size(); i++) { pw.println(); mUsers.valueAt(i).dump(pw, " "); Loading Loading @@ -3032,6 +3147,8 @@ public class ShortcutService extends IShortcutService.Stub { enforceShell(); mPendingTasks.waitOnAllTasks(); final int status = (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver); resultReceiver.send(status, null); Loading Loading @@ -3303,7 +3420,14 @@ public class ShortcutService extends IShortcutService.Stub { } // Injection point. void wtf(String message, Exception e) { void wtf(String message, Throwable e) { if (e == null) { e = new RuntimeException("Stacktrace"); } synchronized (mLock) { mWtfCount++; mLastWtfStacktrace = new Exception("Last failure was logged here:"); } Slog.wtf(TAG, message, e); } Loading Loading @@ -3376,6 +3500,7 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting ShortcutPackage getPackageShortcutForTest(String packageName, int userId) { mPendingTasks.waitOnAllTasks(); synchronized (mLock) { final ShortcutUser user = mUsers.get(userId); if (user == null) return null; Loading @@ -3386,8 +3511,12 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) { mPendingTasks.waitOnAllTasks(); synchronized (mLock) { final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId); final ShortcutUser user = mUsers.get(userId); if (user == null) return null; final ShortcutPackage pkg = user.getAllPackagesForTest().get(packageName); if (pkg == null) return null; return pkg.findShortcutById(shortcutId); Loading Loading @@ -3422,4 +3551,12 @@ public class ShortcutService extends IShortcutService.Stub { forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates)); } } ShortcutPendingTasks getPendingTasksForTest() { return mPendingTasks; } Object getLockForTest() { return mLock; } }
services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +70 −39 File changed.Preview size limit exceeded, changes collapsed. Show changes