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

Commit 12f5c369 authored by Suprabh Shukla's avatar Suprabh Shukla
Browse files

Use frozen state callback to drop listener alarms

Exact listener alarms are currently dropped few seconds after being in
cached to avoid a violation of the exactness guarantee if the app gets
frozen. Now that a callback from activity manager is available, it is
better to use it directly instead of a delayed handler message.

This also improves the efficiency of the logic by avoiding the linear
query in the MessageQueue on the main system-server handler thread
which can be quite busy.

Test: atest FrameworksMockingServicesTests:com.android.server.alarm

Bug: 324470945
Change-Id: I0a05259db98c63c0c17b98aa11a4e52bbdb73d67
parent 4f7007cf
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ java_library {

    static_libs: [
        "modules-utils-fastxmlserializer",
        "service-jobscheduler-alarm.flags-aconfig-java",
        "service-jobscheduler-job.flags-aconfig-java",
    ],

+12 −0
Original line number Diff line number Diff line
@@ -27,3 +27,15 @@ java_aconfig_library {
    aconfig_declarations: "service-job.flags-aconfig",
    visibility: ["//frameworks/base:__subpackages__"],
}

// Alarm
aconfig_declarations {
    name: "alarm_flags",
    package: "com.android.server.alarm",
    srcs: ["alarm.aconfig"],
}

java_aconfig_library {
    name: "service-jobscheduler-alarm.flags-aconfig-java",
    aconfig_declarations: "alarm_flags",
}
+11 −0
Original line number Diff line number Diff line
package: "com.android.server.alarm"

flag {
    name: "use_frozen_state_to_drop_listener_alarms"
    namespace: "backstage_power"
    description: "Use frozen state callback to drop listener alarms for cached apps"
    bug: "324470945"
    metadata {
      purpose: PURPOSE_BUGFIX
    }
}
+65 −13
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.alarm;

import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.AlarmManager.ELAPSED_REALTIME;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
@@ -75,6 +76,7 @@ import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AlarmManager;
@@ -103,6 +105,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -145,6 +148,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.LocalLog;
@@ -293,6 +297,7 @@ public class AlarmManagerService extends SystemService {

    private final Injector mInjector;
    int mBroadcastRefCount = 0;
    boolean mUseFrozenStateToDropListenerAlarms;
    MetricsHelper mMetricsHelper;
    PowerManager.WakeLock mWakeLock;
    SparseIntArray mAlarmsPerUid = new SparseIntArray();
@@ -1856,15 +1861,47 @@ public class AlarmManagerService extends SystemService {
    @Override
    public void onStart() {
        mInjector.init();
        mHandler = new AlarmHandler();

        mOptsWithFgs.setPendingIntentBackgroundActivityLaunchAllowed(false);
        mOptsWithFgsForAlarmClock.setPendingIntentBackgroundActivityLaunchAllowed(false);
        mOptsWithoutFgs.setPendingIntentBackgroundActivityLaunchAllowed(false);
        mOptsTimeBroadcast.setPendingIntentBackgroundActivityLaunchAllowed(false);
        mActivityOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false);
        mBroadcastOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false);

        mMetricsHelper = new MetricsHelper(getContext(), mLock);
        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);

        mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms();
        if (mUseFrozenStateToDropListenerAlarms) {
            final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> {
                final int size = frozenStates.length;
                if (uids.length != size) {
                    Slog.wtf(TAG, "Got different length arrays in frozen state callback!"
                            + " uids.length: " + uids.length + " frozenStates.length: " + size);
                    // Cannot process received data in any meaningful way.
                    return;
                }
                final IntArray affectedUids = new IntArray();
                for (int i = 0; i < size; i++) {
                    if (frozenStates[i] != UID_FROZEN_STATE_FROZEN) {
                        continue;
                    }
                    if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED,
                            uids[i])) {
                        continue;
                    }
                    affectedUids.add(uids[i]);
                }
                if (affectedUids.size() > 0) {
                    removeExactListenerAlarms(affectedUids.toArray());
                }
            };
            final ActivityManager am = getContext().getSystemService(ActivityManager.class);
            am.registerUidFrozenStateChangedCallback(new HandlerExecutor(mHandler), callback);
        }

        mListenerDeathRecipient = new IBinder.DeathRecipient() {
            @Override
            public void binderDied() {
@@ -1880,7 +1917,6 @@ public class AlarmManagerService extends SystemService {
        };

        synchronized (mLock) {
            mHandler = new AlarmHandler();
            mConstants = new Constants(mHandler);

            mAlarmStore = new LazyAlarmStore();
@@ -1960,6 +1996,21 @@ public class AlarmManagerService extends SystemService {
        publishBinderService(Context.ALARM_SERVICE, mService);
    }

    private void removeExactListenerAlarms(int... whichUids) {
        synchronized (mLock) {
            removeAlarmsInternalLocked(a -> {
                if (!ArrayUtils.contains(whichUids, a.uid) || a.listener == null
                        || a.windowLength != 0) {
                    return false;
                }
                Slog.w(TAG, "Alarm " + a.listenerTag + " being removed for "
                        + UserHandle.formatUid(a.uid) + ":" + a.packageName
                        + " because the app got frozen");
                return true;
            }, REMOVE_REASON_LISTENER_CACHED);
        }
    }

    void refreshExactAlarmCandidates() {
        final String[] candidates = mLocalPermissionManager.getAppOpPermissionPackages(
                Manifest.permission.SCHEDULE_EXACT_ALARM);
@@ -3074,6 +3125,14 @@ public class AlarmManagerService extends SystemService {
            mConstants.dump(pw);
            pw.println();

            pw.println("Feature Flags:");
            pw.increaseIndent();
            pw.print(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS,
                    mUseFrozenStateToDropListenerAlarms);
            pw.decreaseIndent();
            pw.println();
            pw.println();

            if (mConstants.USE_TARE_POLICY == EconomyManager.ENABLED_MODE_ON) {
                pw.println("TARE details:");
                pw.increaseIndent();
@@ -4959,18 +5018,7 @@ public class AlarmManagerService extends SystemService {
                    break;
                case REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED:
                    uid = (Integer) msg.obj;
                    synchronized (mLock) {
                        removeAlarmsInternalLocked(a -> {
                            if (a.uid != uid || a.listener == null || a.windowLength != 0) {
                                return false;
                            }
                            // TODO (b/265195908): Change to .w once we have some data on breakages.
                            Slog.wtf(TAG, "Alarm " + a.listenerTag + " being removed for "
                                    + UserHandle.formatUid(a.uid) + ":" + a.packageName
                                    + " because the app went into cached state");
                            return true;
                        }, REMOVE_REASON_LISTENER_CACHED);
                    }
                    removeExactListenerAlarms(uid);
                    break;
                default:
                    // nope, just ignore it
@@ -5322,6 +5370,10 @@ public class AlarmManagerService extends SystemService {

        @Override
        public void handleUidCachedChanged(int uid, boolean cached) {
            if (mUseFrozenStateToDropListenerAlarms) {
                // Use ActivityManager#UidFrozenStateChangedCallback instead.
                return;
            }
            if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, uid)) {
                return;
            }
+118 −3
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@
package com.android.server.alarm;

import static android.Manifest.permission.SCHEDULE_EXACT_ALARM;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
import static android.app.AlarmManager.ELAPSED_REALTIME;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.app.AlarmManager.EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED;
@@ -42,6 +44,7 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.NULL_DEFAULT;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
@@ -135,6 +138,7 @@ import android.net.Uri;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -145,9 +149,11 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.flag.util.FlagSetException;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.ArraySet;
import android.util.Log;
@@ -215,6 +221,7 @@ public final class AlarmManagerServiceTest {
    private AppStateTrackerImpl.Listener mListener;
    private AlarmManagerService.UninstallReceiver mPackageChangesReceiver;
    private AlarmManagerService.ChargingReceiver mChargingReceiver;
    private ActivityManager.UidFrozenStateChangedCallback mUidFrozenStateCallback;
    private IAppOpsCallback mIAppOpsCallback;
    private IAlarmManager mBinder;
    @Mock
@@ -240,6 +247,8 @@ public final class AlarmManagerServiceTest {
    @Mock
    private ActivityManagerInternal mActivityManagerInternal;
    @Mock
    private ActivityManager mActivityManager;
    @Mock
    private PackageManagerInternal mPackageManagerInternal;
    @Mock
    private AppStateTrackerImpl mAppStateTracker;
@@ -403,15 +412,31 @@ public final class AlarmManagerServiceTest {
            .mockStatic(PermissionChecker.class)
            .mockStatic(PermissionManagerService.class)
            .mockStatic(ServiceManager.class)
            .mockStatic(Settings.Global.class)
            .mockStatic(SystemProperties.class)
            .spyStatic(UserHandle.class)
            .afterSessionFinished(
                    () -> LocalServices.removeServiceForTest(AlarmManagerInternal.class))
            .build();

    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(NULL_DEFAULT);

    /**
     * Have to do this to switch the {@link Flags} implementation to {@link FakeFeatureFlagsImpl}.
     * All methods that need any flag enabled should use the
     * {@link android.platform.test.annotations.EnableFlags} annotation, in which case disabling
     * the flag will fail with an exception that we will swallow here.
     */
    private void disableFlagsNotSetByAnnotation() {
        try {
            mSetFlagsRule.disableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS);
        } catch (FlagSetException fse) {
            // Expected if the test about to be run requires this enabled.
        }
    }

    @Before
    public final void setUp() {
    public void setUp() {
        doReturn(mIActivityManager).when(ActivityManager::getService);
        doReturn(mDeviceIdleInternal).when(
                () -> LocalServices.getService(DeviceIdleInternal.class));
@@ -469,6 +494,7 @@ public final class AlarmManagerServiceTest {

        when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
        when(mMockContext.getSystemService(BatteryManager.class)).thenReturn(mBatteryManager);
        when(mMockContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);

        registerAppIds(new String[]{TEST_CALLING_PACKAGE},
                new Integer[]{UserHandle.getAppId(TEST_CALLING_UID)});
@@ -479,7 +505,18 @@ public final class AlarmManagerServiceTest {
        mService = new AlarmManagerService(mMockContext, mInjector);
        spyOn(mService);

        disableFlagsNotSetByAnnotation();

        mService.onStart();

        if (Flags.useFrozenStateToDropListenerAlarms()) {
            final ArgumentCaptor<ActivityManager.UidFrozenStateChangedCallback> frozenCaptor =
                    ArgumentCaptor.forClass(ActivityManager.UidFrozenStateChangedCallback.class);
            verify(mActivityManager).registerUidFrozenStateChangedCallback(
                    any(HandlerExecutor.class), frozenCaptor.capture());
            mUidFrozenStateCallback = frozenCaptor.getValue();
        }

        // Unable to mock mMockContext to return a mock stats manager.
        // So just mocking the whole MetricsHelper instance.
        mService.mMetricsHelper = mock(MetricsHelper.class);
@@ -3741,9 +3778,87 @@ public final class AlarmManagerServiceTest {
        mListener.handleUidCachedChanged(TEST_CALLING_UID, true);
        assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
        assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
        assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));

        mListener.handleUidCachedChanged(TEST_CALLING_UID_2, true);
        assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
        assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
        assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
    }

    private void executeUidFrozenStateCallback(int[] uids, int[] frozenStates) {
        assertNotNull(mUidFrozenStateCallback);
        mUidFrozenStateCallback.onUidFrozenStateChanged(uids, frozenStates);
    }

    @EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
    @Test
    public void exactListenerAlarmsRemovedOnFrozen() {
        mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);

        setTestAlarmWithListener(ELAPSED_REALTIME, 31, getNewListener(() -> {}), WINDOW_EXACT,
                TEST_CALLING_UID);
        setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID);
        setTestAlarm(ELAPSED_REALTIME, 54, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
                TEST_CALLING_UID, null);
        setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null);

        setTestAlarmWithListener(ELAPSED_REALTIME, 21, getNewListener(() -> {}), WINDOW_EXACT,
                TEST_CALLING_UID_2);
        setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2);
        setTestAlarm(ELAPSED_REALTIME, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
                TEST_CALLING_UID_2, null);
        setTestAlarm(RTC, 549, 234, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID_2, null);

        assertEquals(8, mService.mAlarmStore.size());

        executeUidFrozenStateCallback(
                new int[] {TEST_CALLING_UID, TEST_CALLING_UID_2},
                new int[] {UID_FROZEN_STATE_FROZEN, UID_FROZEN_STATE_UNFROZEN});
        assertEquals(7, mService.mAlarmStore.size());

        executeUidFrozenStateCallback(
                new int[] {TEST_CALLING_UID_2}, new int[] {UID_FROZEN_STATE_FROZEN});
        assertEquals(6, mService.mAlarmStore.size());
    }

    @EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
    @Test
    public void alarmCountOnListenerFrozen() {
        mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);

        // Set some alarms for TEST_CALLING_UID.
        final int numExactListenerUid1 = 17;
        for (int i = 0; i < numExactListenerUid1; i++) {
            setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i,
                    getNewListener(() -> {}));
        }
        setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID);
        setTestAlarm(ELAPSED_REALTIME, 54, getNewMockPendingIntent());
        setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null);

        // Set some alarms for TEST_CALLING_UID_2.
        final int numExactListenerUid2 = 11;
        for (int i = 0; i < numExactListenerUid2; i++) {
            setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i,
                    getNewListener(() -> {}), WINDOW_EXACT, TEST_CALLING_UID_2);
        }
        setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2);
        setTestAlarm(RTC_WAKEUP, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
                TEST_CALLING_UID_2, null);

        assertEquals(numExactListenerUid1 + 3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
        assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));

        executeUidFrozenStateCallback(
                new int[] {TEST_CALLING_UID, TEST_CALLING_UID_2},
                new int[] {UID_FROZEN_STATE_FROZEN, UID_FROZEN_STATE_UNFROZEN});
        assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
        assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));

        executeUidFrozenStateCallback(
                new int[] {TEST_CALLING_UID_2}, new int[] {UID_FROZEN_STATE_FROZEN});
        assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
        assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
    }