Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7b82cbec authored by Jing Ji's avatar Jing Ji
Browse files

Track the number of PendingIntent per UID

Log a warning if there are too many PendingIntent from a UID

Bug: 148238407
Bug: 159460420
Test: Manual - Get & release tons of PendingIntent, check the logs
Test: atest \
FrameworksMockingServicesTests: com.android.server.am.PendingIntentControllerTest
Change-Id: I47d2c7c3557be352b1af6c957067e9418a24ced2
parent 26dfdc58
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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<Integer> IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES = new ArraySet<Integer>();

    /**
     * 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<String> mDefaultImperceptibleKillExemptPackages;
    private List<Integer> 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.
+3 −4
Original line number Diff line number Diff line
@@ -405,9 +405,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -2545,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;
@@ -2642,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;
+97 −1
Original line number Diff line number Diff line
@@ -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<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>> 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<RingBuffer<String>> 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<String> 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);
            }
        }
    }
}
+3 −1
Original line number Diff line number Diff line
@@ -187,7 +187,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
                + " intent="
                + (requestIntent != null
                        ? requestIntent.toShortString(false, true, false, false) : "<null>")
                + " 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<PendingIntentRecord> current = controller.mIntentSenderRecords.get(key);
            if (current == ref) {
                controller.mIntentSenderRecords.remove(key);
                controller.decrementUidStatLocked(this);
            }
        }
    }
+4 −1
Original line number Diff line number Diff line
@@ -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();
    }