diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 4ff421e6cdf5c..135ac9a7846e1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -87,6 +87,7 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_PROCESS_START_ASYNC = "process_start_async"; static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time"; static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration"; + static final String KEY_PENDINGINTENT_WARNING_THRESHOLD = "pendingintent_warning_threshold"; private static final int DEFAULT_MAX_CACHED_PROCESSES = 32; private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000; @@ -119,6 +120,7 @@ final class ActivityManagerConstants extends ContentObserver { private static final boolean DEFAULT_PROCESS_START_ASYNC = true; private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000; private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000; + private static final int DEFAULT_PENDINGINTENT_WARNING_THRESHOLD = 2000; // Flag stored in the DeviceConfig API. /** @@ -328,6 +330,12 @@ final class ActivityManagerConstants extends ContentObserver { */ public ArraySet IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES = new ArraySet(); + /** + * The threshold for the amount of PendingIntent for each UID, there will be + * warning logs if the number goes beyond this threshold. + */ + public int PENDINGINTENT_WARNING_THRESHOLD = DEFAULT_PENDINGINTENT_WARNING_THRESHOLD; + private List mDefaultImperceptibleKillExemptPackages; private List mDefaultImperceptibleKillExemptProcStates; @@ -562,6 +570,8 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_MEMORY_INFO_THROTTLE_TIME); TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION, DEFAULT_TOP_TO_FGS_GRACE_DURATION); + PENDINGINTENT_WARNING_THRESHOLD = mParser.getInt(KEY_PENDINGINTENT_WARNING_THRESHOLD, + DEFAULT_PENDINGINTENT_WARNING_THRESHOLD); // For new flags that are intended for server-side experiments, please use the new // DeviceConfig package. diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 157feb30c24d7..be859069950ee 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2543,7 +2543,8 @@ public class ActivityManagerService extends IActivityManager.Stub mUiHandler = injector.getUiHandler(null /* service */); mUserController = hasHandlerThread ? new UserController(this) : null; mPendingIntentController = hasHandlerThread - ? new PendingIntentController(handlerThread.getLooper(), mUserController) : null; + ? new PendingIntentController(handlerThread.getLooper(), mUserController, + mConstants) : null; mProcStartHandlerThread = null; mProcStartHandler = null; mHiddenApiBlacklist = null; @@ -2640,7 +2641,7 @@ public class ActivityManagerService extends IActivityManager.Stub mUserController = new UserController(this); mPendingIntentController = new PendingIntentController( - mHandlerThread.getLooper(), mUserController); + mHandlerThread.getLooper(), mUserController, mConstants); if (SystemProperties.getInt("sys.use_fifo_ui", 0) != 0) { mUseFifoUiScheduling = true; diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java index eacf08889b1d4..c62df56a88c52 100644 --- a/services/core/java/com/android/server/am/PendingIntentController.java +++ b/services/core/java/com/android/server/am/PendingIntentController.java @@ -41,8 +41,12 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseIntArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.os.IResultReceiver; +import com.android.internal.util.RingBuffer; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.AlarmManagerInternal; import com.android.server.LocalServices; @@ -52,6 +56,7 @@ import com.android.server.wm.SafeActivityOptions; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; @@ -66,6 +71,9 @@ public class PendingIntentController { private static final String TAG = TAG_WITH_CLASS_NAME ? "PendingIntentController" : TAG_AM; private static final String TAG_MU = TAG + POSTFIX_MU; + /** @see {@link #mRecentIntentsPerUid}. */ + private static final int RECENT_N = 10; + /** Lock for internal state. */ final Object mLock = new Object(); final Handler mH; @@ -77,10 +85,22 @@ public class PendingIntentController { final HashMap> mIntentSenderRecords = new HashMap<>(); - PendingIntentController(Looper looper, UserController userController) { + /** The number of PendingIntentRecord per uid */ + @GuardedBy("mLock") + private final SparseIntArray mIntentsPerUid = new SparseIntArray(); + + /** The recent PendingIntentRecord, up to {@link #RECENT_N} per uid */ + @GuardedBy("mLock") + private final SparseArray> mRecentIntentsPerUid = new SparseArray<>(); + + private final ActivityManagerConstants mConstants; + + PendingIntentController(Looper looper, UserController userController, + ActivityManagerConstants constants) { mH = new Handler(looper); mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mUserController = userController; + mConstants = constants; } void onActivityManagerInternalAdded() { @@ -136,12 +156,14 @@ public class PendingIntentController { } makeIntentSenderCanceled(rec); mIntentSenderRecords.remove(key); + decrementUidStatLocked(rec); } if (noCreate) { return rec; } rec = new PendingIntentRecord(this, key, callingUid); mIntentSenderRecords.put(key, rec.ref); + incrementUidStatLocked(rec); return rec; } } @@ -198,6 +220,7 @@ public class PendingIntentController { didSomething = true; it.remove(); makeIntentSenderCanceled(pir); + decrementUidStatLocked(pir); if (pir.key.activity != null) { final Message m = PooledLambda.obtainMessage( PendingIntentController::clearPendingResultForActivity, this, @@ -237,6 +260,7 @@ public class PendingIntentController { synchronized (mLock) { makeIntentSenderCanceled(rec); mIntentSenderRecords.remove(rec.key); + decrementUidStatLocked(rec); if (cleanActivity && rec.key.activity != null) { final Message m = PooledLambda.obtainMessage( PendingIntentController::clearPendingResultForActivity, this, @@ -369,9 +393,81 @@ public class PendingIntentController { } } + final int sizeOfIntentsPerUid = mIntentsPerUid.size(); + if (sizeOfIntentsPerUid > 0) { + for (int i = 0; i < sizeOfIntentsPerUid; i++) { + pw.print(" * UID: "); + pw.print(mIntentsPerUid.keyAt(i)); + pw.print(" total: "); + pw.println(mIntentsPerUid.valueAt(i)); + } + } + if (!printed) { pw.println(" (nothing)"); } } } + + /** + * Increment the number of the PendingIntentRecord for the given uid, log a warning + * if there are too many for this uid already. + */ + @GuardedBy("mLock") + void incrementUidStatLocked(final PendingIntentRecord pir) { + final int uid = pir.uid; + final int idx = mIntentsPerUid.indexOfKey(uid); + int newCount = 1; + if (idx >= 0) { + newCount = mIntentsPerUid.valueAt(idx) + 1; + mIntentsPerUid.setValueAt(idx, newCount); + } else { + mIntentsPerUid.put(uid, newCount); + } + + // If the number is within the range [threshold - N + 1, threshold], log it into buffer + final int lowBound = mConstants.PENDINGINTENT_WARNING_THRESHOLD - RECENT_N + 1; + RingBuffer recentHistory = null; + if (newCount == lowBound) { + recentHistory = new RingBuffer(String.class, RECENT_N); + mRecentIntentsPerUid.put(uid, recentHistory); + } else if (newCount > lowBound && newCount <= mConstants.PENDINGINTENT_WARNING_THRESHOLD) { + recentHistory = mRecentIntentsPerUid.get(uid); + } + if (recentHistory == null) { + return; + } + + recentHistory.append(pir.key.toString()); + + // Output the log if we are hitting the threshold + if (newCount == mConstants.PENDINGINTENT_WARNING_THRESHOLD) { + Slog.wtf(TAG, "Too many PendingIntent created for uid " + uid + + ", recent " + RECENT_N + ": " + Arrays.toString(recentHistory.toArray())); + // Clear the buffer, as we don't want to spam the log when the numbers + // are jumping up and down around the threshold. + mRecentIntentsPerUid.remove(uid); + } + } + + /** + * Decrement the number of the PendingIntentRecord for the given uid. + */ + @GuardedBy("mLock") + void decrementUidStatLocked(final PendingIntentRecord pir) { + final int uid = pir.uid; + final int idx = mIntentsPerUid.indexOfKey(uid); + if (idx >= 0) { + final int newCount = mIntentsPerUid.valueAt(idx) - 1; + // If we are going below the low threshold, no need to keep logs. + if (newCount == mConstants.PENDINGINTENT_WARNING_THRESHOLD - RECENT_N) { + mRecentIntentsPerUid.delete(uid); + } + if (newCount == 0) { + mIntentsPerUid.removeAt(idx); + } else { + mIntentsPerUid.setValueAt(idx, newCount); + } + } + } } diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index d54d2d7d2056a..1997dbd6fc37b 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -187,7 +187,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { + " intent=" + (requestIntent != null ? requestIntent.toShortString(false, true, false, false) : "") - + " flags=0x" + Integer.toHexString(flags) + " u=" + userId + "}"; + + " flags=0x" + Integer.toHexString(flags) + " u=" + userId + "}" + + " requestCode=" + requestCode; } String typeName() { @@ -499,6 +500,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub { WeakReference current = controller.mIntentSenderRecords.get(key); if (current == ref) { controller.mIntentSenderRecords.remove(key); + controller.decrementUidStatLocked(this); } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java index e3453a06990fb..b2847ce88a2ca 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java @@ -23,6 +23,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.app.ActivityManager; @@ -81,8 +82,10 @@ public class PendingIntentControllerTest { doReturn(mIPackageManager).when(() -> AppGlobals.getPackageManager()); when(mIPackageManager.getPackageUid(eq(TEST_PACKAGE_NAME), anyInt(), anyInt())).thenReturn( TEST_CALLING_UID); + ActivityManagerConstants constants = mock(ActivityManagerConstants.class); + constants.PENDINGINTENT_WARNING_THRESHOLD = 2000; mPendingIntentController = new PendingIntentController(Looper.getMainLooper(), - mUserController); + mUserController, constants); mPendingIntentController.onActivityManagerInternalAdded(); }