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

Commit 6ba52979 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

BroadcastQueue: active counters, usage reporting.

Other parts of the OS (such as OOM adjust calculations) rely on the
number of active broadcasts maintained in ProcessReceiverRecord, so
this change adds them to the new stack.  The new stack doesn't need
to do membership tests, so it migrates towards a faster reference
count approach.  Tests to confirm behavior of both stacks.

Dispatch usage events expected by PackageManager and UsageStats and
skip broadcasts to apps in full backup to match previous behavior.

Bug: 245771249
Test: atest FrameworksMockingServicesTests:BroadcastQueueTest
Change-Id: Ia3faf26d01ec9d662d2377a0b47b28914cfe8e03
parent 05ec4717
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -277,7 +277,6 @@ class BroadcastProcessQueue {

    public void setActiveDeliveryState(@DeliveryState int deliveryState) {
        checkState(isActive(), "isActive");
        mActive.setDeliveryState(mActiveIndex, deliveryState);

        // Emit tracing events for the broadcast we're dispatching; the cookie
        // here is unique within the track
@@ -291,10 +290,15 @@ class BroadcastProcessQueue {
            case BroadcastRecord.DELIVERY_SKIPPED:
            case BroadcastRecord.DELIVERY_TIMEOUT:
            case BroadcastRecord.DELIVERY_FAILURE:
                // Only end trace events previously started by us
                if (getActiveDeliveryState() == BroadcastRecord.DELIVERY_SCHEDULED) {
                    Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                            traceTrackName, cookie);
                }
                break;
        }

        mActive.setDeliveryState(mActiveIndex, deliveryState);
    }

    public @NonNull BroadcastRecord getActive() {
+1 −9
Original line number Diff line number Diff line
@@ -46,7 +46,6 @@ import android.app.BroadcastOptions;
import android.app.IApplicationThread;
import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.IIntentReceiver;
@@ -1509,19 +1508,12 @@ public class BroadcastQueueImpl extends BroadcastQueue {
        if (targetPackage == null) {
            return;
        }
        getUsageStatsManagerInternal().reportBroadcastDispatched(
        mService.mUsageStatsService.reportBroadcastDispatched(
                r.callingUid, targetPackage, UserHandle.of(r.userId),
                r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
                mService.getUidStateLocked(targetUid));
    }

    @NonNull
    private UsageStatsManagerInternal getUsageStatsManagerInternal() {
        final UsageStatsManagerInternal usageStatsManagerInternal =
                LocalServices.getService(UsageStatsManagerInternal.class);
        return usageStatsManagerInternal;
    }

    private void maybeAddAllowBackgroundActivityStartsToken(ProcessRecord proc, BroadcastRecord r) {
        if (r == null || proc == null || !r.allowBackgroundActivityStarts) {
            return;
+82 −17
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.app.ActivityManager;
import android.app.IApplicationThread;
import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.UidObserver;
import android.app.usage.UsageEvents.Event;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Intent;
@@ -97,7 +98,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    // TODO: record broadcast state change timing statistics
    // TODO: record historical broadcast statistics

    // TODO: pause queues for apps involved in backup/restore
    // TODO: pause queues when background services are running
    // TODO: pause queues when processes are frozen

@@ -471,6 +471,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
        checkState(queue.isActive(), "isActive");

        final ProcessRecord app = queue.app;
        final BroadcastRecord r = queue.getActive();
        final Object receiver = queue.getActiveReceiver();

        app.mReceivers.incrementCurReceivers();

        // If someone already skipped us, finish immediately; typically due to a
        // component being disabled
        if (queue.getActiveDeliveryState() == BroadcastRecord.DELIVERY_SKIPPED) {
@@ -478,33 +484,32 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
            return;
        }

        queue.setActiveDeliveryState(BroadcastRecord.DELIVERY_SCHEDULED);

        final ProcessRecord app = queue.app;
        final BroadcastRecord r = queue.getActive();
        final Object receiver = queue.getActiveReceiver();

        if (!r.timeoutExempt) {
            final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
            mLocalHandler.sendMessageDelayed(
                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT, queue), timeout);
        if (app.isInFullBackup()) {
            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
            return;
        }

        // TODO: apply temp allowlist exemptions
        // TODO: apply background activity launch exemptions

        if (mSkipPolicy.shouldSkip(r, receiver)) {
            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
            return;
        }

        final Intent receiverIntent = r.getReceiverIntent(receiver);
        if (receiverIntent == null) {
            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
            return;
        }

        if (!r.timeoutExempt) {
            final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
            mLocalHandler.sendMessageDelayed(
                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT, queue), timeout);
        }

        // TODO: apply temp allowlist exemptions
        // TODO: apply background activity launch exemptions

        if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
        queue.setActiveDeliveryState(BroadcastRecord.DELIVERY_SCHEDULED);

        final IApplicationThread thread = app.getThread();
        if (thread != null) {
            try {
@@ -513,6 +518,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
                            ((BroadcastFilter) receiver).receiverList.receiver, receiverIntent,
                            r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky,
                            r.userId, app.mState.getReportedProcState());
                    notifyScheduleRegisteredReceiver(app, r);

                    // TODO: consider making registered receivers of unordered
                    // broadcasts report results to detect ANRs
@@ -523,6 +529,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
                    thread.scheduleReceiver(receiverIntent, ((ResolveInfo) receiver).activityInfo,
                            null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
                            app.mState.getReportedProcState());
                    notifyScheduleReceiver(app, r, receiverIntent);
                }
            } catch (RemoteException e) {
                finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
@@ -546,6 +553,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
    private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
            @DeliveryState int deliveryState) {
        checkState(queue.isActive(), "isActive");

        final ProcessRecord app = queue.app;
        final BroadcastRecord r = queue.getActive();

        if (app != null) {
            app.mReceivers.decrementCurReceivers();
        }

        queue.setActiveDeliveryState(deliveryState);

        if (deliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
@@ -554,7 +569,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        }

        if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
            if (queue.app != null && !queue.app.isDebugging()) {
            if (app != null && !app.isDebugging()) {
                mService.appNotResponding(queue.app, TimeoutRecord
                        .forBroadcastReceiver("Broadcast of " + queue.getActive().toShortString()));
            }
@@ -727,6 +742,56 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
        }
    }

    /**
     * Inform other parts of OS that the given broadcast was just scheduled for
     * a registered receiver, typically for internal bookkeeping.
     */
    private void notifyScheduleRegisteredReceiver(@NonNull ProcessRecord app,
            @NonNull BroadcastRecord r) {
        reportUsageStatsBroadcastDispatched(app, r);
    }

    /**
     * Inform other parts of OS that the given broadcast was just scheduled for
     * a manifest receiver, typically for internal bookkeeping.
     */
    private void notifyScheduleReceiver(@NonNull ProcessRecord app,
            @NonNull BroadcastRecord r, @NonNull Intent receiverIntent) {
        reportUsageStatsBroadcastDispatched(app, r);

        final boolean targetedBroadcast = r.intent.getComponent() != null;
        final boolean targetedSelf = Objects.equals(r.callerPackage,
                receiverIntent.getComponent().getPackageName());
        if (targetedBroadcast && !targetedSelf) {
            mService.mUsageStatsService.reportEvent(receiverIntent.getComponent().getPackageName(),
                    r.userId, Event.APP_COMPONENT_USED);
        }

        mService.notifyPackageUse(receiverIntent.getComponent().getPackageName(),
                PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
    }

    private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app,
            @NonNull BroadcastRecord r) {
        final long idForResponseEvent = (r.options != null)
                ? r.options.getIdForResponseEvent() : 0L;
        if (idForResponseEvent <= 0) return;

        final String targetPackage;
        if (r.intent.getPackage() != null) {
            targetPackage = r.intent.getPackage();
        } else if (r.intent.getComponent() != null) {
            targetPackage = r.intent.getComponent().getPackageName();
        } else {
            targetPackage = null;
        }
        if (targetPackage == null) return;

        mService.mUsageStatsService.reportBroadcastDispatched(r.callingUid, targetPackage,
                UserHandle.of(r.userId), idForResponseEvent, SystemClock.elapsedRealtime(),
                mService.getUidStateLocked(app.uid));
    }

    private @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull ProcessRecord app) {
        return getOrCreateProcessQueue(app.processName, app.info.uid);
    }
+33 −1
Original line number Diff line number Diff line
@@ -34,29 +34,61 @@ final class ProcessReceiverRecord {
     */
    private final ArraySet<BroadcastRecord> mCurReceivers = new ArraySet<BroadcastRecord>();

    private int mCurReceiversSize;

    /**
     * All IIntentReceivers that are registered from this process.
     */
    private final ArraySet<ReceiverList> mReceivers = new ArraySet<>();

    int numberOfCurReceivers() {
        return mCurReceivers.size();
        return mCurReceiversSize;
    }

    void incrementCurReceivers() {
        mCurReceiversSize++;
    }

    void decrementCurReceivers() {
        mCurReceiversSize--;
    }

    /**
     * @deprecated we're moving towards tracking only a reference count to
     *             improve performance.
     */
    @Deprecated
    BroadcastRecord getCurReceiverAt(int index) {
        return mCurReceivers.valueAt(index);
    }

    /**
     * @deprecated we're moving towards tracking only a reference count to
     *             improve performance.
     */
    @Deprecated
    boolean hasCurReceiver(BroadcastRecord receiver) {
        return mCurReceivers.contains(receiver);
    }

    /**
     * @deprecated we're moving towards tracking only a reference count to
     *             improve performance.
     */
    @Deprecated
    void addCurReceiver(BroadcastRecord receiver) {
        mCurReceivers.add(receiver);
        mCurReceiversSize = mCurReceivers.size();
    }

    /**
     * @deprecated we're moving towards tracking only a reference count to
     *             improve performance.
     */
    @Deprecated
    void removeCurReceiver(BroadcastRecord receiver) {
        mCurReceivers.remove(receiver);
        mCurReceiversSize = mCurReceivers.size();
    }

    int numberOfReceivers() {
+59 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.am;

import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -35,6 +36,8 @@ import android.app.Activity;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -42,6 +45,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.os.Binder;
@@ -111,6 +115,8 @@ public class BroadcastQueueTest {
    private ProcessList mProcessList;
    @Mock
    private PackageManagerInternal mPackageManagerInt;
    @Mock
    private UsageStatsManagerInternal mUsageStatsManagerInt;

    private ActivityManagerService mAms;
    private BroadcastQueue mQueue;
@@ -120,6 +126,11 @@ public class BroadcastQueueTest {
     */
    private SparseArray<ReceiverList> mRegisteredReceivers = new SparseArray<>();

    /**
     * Collection of all active processes during current test run.
     */
    private List<ProcessRecord> mActiveProcesses = new ArrayList<>();

    @Parameters(name = "impl={0}")
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] { {Impl.DEFAULT}, {Impl.MODERN} });
@@ -153,6 +164,7 @@ public class BroadcastQueueTest {
        realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
        realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
        realAms.mPackageManagerInt = mPackageManagerInt;
        realAms.mUsageStatsService = mUsageStatsManagerInt;
        realAms.mProcessesReady = true;
        mAms = spy(realAms);
        doAnswer((invocation) -> {
@@ -202,6 +214,11 @@ public class BroadcastQueueTest {
    @After
    public void tearDown() throws Exception {
        mHandlerThread.quit();

        // Verify that all processes have finished handling broadcasts
        for (ProcessRecord app : mActiveProcesses) {
            assertTrue(app.toShortString(), app.mReceivers.numberOfCurReceivers() == 0);
        }
    }

    private class TestInjector extends Injector {
@@ -239,6 +256,7 @@ public class BroadcastQueueTest {
            boolean wedged) throws Exception {
        final ProcessRecord r = new ProcessRecord(mAms, ai, processName, ai.uid);
        r.setPid(mNextPid.getAndIncrement());
        mActiveProcesses.add(r);

        final IApplicationThread thread = mock(IApplicationThread.class);
        final IBinder threadBinder = new Binder();
@@ -257,6 +275,7 @@ public class BroadcastQueueTest {
            Log.v(TAG, "Intercepting scheduleReceiver() for "
                    + Arrays.toString(invocation.getArguments()));
            if (!wedged) {
                assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
                mHandlerThread.getThreadHandler().post(() -> {
                    synchronized (mAms) {
                        mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
@@ -273,6 +292,7 @@ public class BroadcastQueueTest {
                    + Arrays.toString(invocation.getArguments()));
            final boolean ordered = invocation.getArgument(5);
            if (!wedged && ordered) {
                assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
                mHandlerThread.getThreadHandler().post(() -> {
                    synchronized (mAms) {
                        mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
@@ -533,7 +553,10 @@ public class BroadcastQueueTest {
                        makeRegisteredReceiver(receiverYellowApp))));

        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
        airplane.setComponent(new ComponentName(PACKAGE_YELLOW, CLASS_YELLOW));
        final BroadcastOptions options = BroadcastOptions.makeBasic();
        options.recordResponseEventWhileInBackground(42L);
        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, options,
                List.of(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));

        waitForIdle();
@@ -544,6 +567,23 @@ public class BroadcastQueueTest {
        verifyScheduleReceiver(receiverBlueApp, timezone);
        verifyScheduleRegisteredReceiver(receiverYellowApp, timezone);
        verifyScheduleReceiver(receiverYellowApp, airplane);

        // Confirm that we've reported relevant packages as being used, but
        // only called for manifest receivers
        verify(mAms, never()).notifyPackageUse(eq(PACKAGE_RED),
                eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
        verify(mAms, times(1)).notifyPackageUse(eq(PACKAGE_GREEN),
                eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
        verify(mAms, times(1)).notifyPackageUse(eq(PACKAGE_BLUE),
                eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
        verify(mAms, times(1)).notifyPackageUse(eq(PACKAGE_YELLOW),
                eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));

        // Confirm that we've reported expected usage events
        verify(mAms.mUsageStatsService).reportBroadcastDispatched(eq(callerApp.uid),
                eq(PACKAGE_YELLOW), eq(UserHandle.SYSTEM), eq(42L), anyLong(), anyInt());
        verify(mAms.mUsageStatsService).reportEvent(eq(PACKAGE_YELLOW), eq(UserHandle.USER_SYSTEM),
                eq(Event.APP_COMPONENT_USED));
    }

    /**
@@ -601,4 +641,22 @@ public class BroadcastQueueTest {
        verifyScheduleReceiver(times(1), receiverApp, airplane,
                new ComponentName(PACKAGE_GREEN, CLASS_BLUE));
    }

    /**
     * Verify that we skip broadcasts to an app being backed up.
     */
    @Test
    public void testBackup() throws Exception {
        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
        final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
        receiverApp.setInFullBackup(true);

        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));

        waitForIdle();
        verifyScheduleReceiver(never(), receiverApp, airplane,
                new ComponentName(PACKAGE_GREEN, CLASS_GREEN));
    }
}