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

Commit 490df4cc authored by Robert Horvath's avatar Robert Horvath
Browse files

Implement package exemptions for Low Power Standby

Packages listed in the Low Power Standby policy are now subject to Low
Power Standby restrictions.

Bug: 234002812
Test: atest LowPowerStandbyControllerTest LowPowerStandbyTest
Change-Id: Iab20d7bfe6a6f4a36eb4d63a1bab18b275832513
parent 46ccaf46
Loading
Loading
Loading
Loading
+76 −5
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.os.PowerManager.LowPowerStandbyPolicy;
import android.os.PowerManagerInternal;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
@@ -133,7 +134,7 @@ public class LowPowerStandbyController {
    @GuardedBy("mLock")
    private boolean mEnableCustomPolicy;

    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    private final BroadcastReceiver mIdleBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()) {
@@ -150,6 +151,41 @@ public class LowPowerStandbyController {
        }
    };

    private final BroadcastReceiver mPackageBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (DEBUG) {
                Slog.d(TAG, "Received package intent: action=" + intent.getAction() + ", data="
                        + intent.getData());
            }
            final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
            if (replacing) {
                return;
            }
            final Uri intentUri = intent.getData();
            final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart()
                    : null;
            synchronized (mLock) {
                final LowPowerStandbyPolicy policy = getPolicy();
                if (policy.getExemptPackages().contains(packageName)) {
                    enqueueNotifyAllowlistChangedLocked();
                }
            }
        }
    };

    private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (DEBUG) {
                Slog.d(TAG, "Received user intent: action=" + intent.getAction());
            }
            synchronized (mLock) {
                enqueueNotifyAllowlistChangedLocked();
            }
        }
    };

    @GuardedBy("mLock")
    private AlarmManager mAlarmManager;
    @GuardedBy("mLock")
@@ -599,11 +635,25 @@ public class LowPowerStandbyController {
        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);

        mContext.registerReceiver(mBroadcastReceiver, intentFilter);
        mContext.registerReceiver(mIdleBroadcastReceiver, intentFilter);

        IntentFilter packageFilter = new IntentFilter();
        packageFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        packageFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
        mContext.registerReceiver(mPackageBroadcastReceiver, packageFilter);

        final IntentFilter userFilter = new IntentFilter();
        userFilter.addAction(Intent.ACTION_USER_ADDED);
        userFilter.addAction(Intent.ACTION_USER_REMOVED);
        mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
    }

    private void unregisterBroadcastReceiver() {
        mContext.unregisterReceiver(mBroadcastReceiver);
        mContext.unregisterReceiver(mIdleBroadcastReceiver);
        mContext.unregisterReceiver(mPackageBroadcastReceiver);
        mContext.unregisterReceiver(mUserReceiver);
    }

    @GuardedBy("mLock")
@@ -814,7 +864,10 @@ public class LowPowerStandbyController {

        int policyAllowedReasonsChanged = policyA.getAllowedReasons() ^ policyB.getAllowedReasons();

        return (policyAllowedReasonsChanged & allowedReasonsInUse) != 0;
        boolean exemptPackagesChanged = !policyA.getExemptPackages().equals(
                policyB.getExemptPackages());

        return (policyAllowedReasonsChanged & allowedReasonsInUse) != 0 || exemptPackagesChanged;
    }

    void dump(PrintWriter pw) {
@@ -1033,9 +1086,12 @@ public class LowPowerStandbyController {

    @GuardedBy("mLock")
    private int[] getAllowlistUidsLocked() {
        final UserManager userManager = mContext.getSystemService(UserManager.class);
        final List<UserHandle> userHandles = userManager.getUserHandles(true);
        final ArraySet<Integer> uids = new ArraySet<>(mUidAllowedReasons.size());
        final LowPowerStandbyPolicy policy = getPolicy();

        final int policyAllowedReasons = getPolicy().getAllowedReasons();
        final int policyAllowedReasons = policy.getAllowedReasons();
        for (int i = 0; i < mUidAllowedReasons.size(); i++) {
            Integer uid = mUidAllowedReasons.keyAt(i);
            if ((mUidAllowedReasons.valueAt(i) & policyAllowedReasons) != 0) {
@@ -1043,6 +1099,12 @@ public class LowPowerStandbyController {
            }
        }

        for (int appId : getExemptPackageAppIdsLocked()) {
            for (int uid : uidsForAppId(appId, userHandles)) {
                uids.add(uid);
            }
        }

        int[] allowlistUids = new int[uids.size()];
        for (int i = 0; i < uids.size(); i++) {
            allowlistUids[i] = uids.valueAt(i);
@@ -1051,6 +1113,15 @@ public class LowPowerStandbyController {
        return allowlistUids;
    }

    private int[] uidsForAppId(int appUid, List<UserHandle> userHandles) {
        final int appId = UserHandle.getAppId(appUid);
        final int[] uids = new int[userHandles.size()];
        for (int i = 0; i < userHandles.size(); i++) {
            uids[i] = userHandles.get(i).getUid(appId);
        }
        return uids;
    }

    @GuardedBy("mLock")
    private void enqueueNotifyAllowlistChangedLocked() {
        final long now = mClock.elapsedRealtime();
+138 −1
Original line number Diff line number Diff line
@@ -33,18 +33,25 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import android.app.AlarmManager;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.IPowerManager;
import android.os.PowerManager;
import android.os.PowerManager.LowPowerStandbyPolicy;
import android.os.PowerManagerInternal;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.test.TestLooper;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
@@ -69,6 +76,7 @@ import org.mockito.MockitoAnnotations;

import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
@@ -79,7 +87,12 @@ import java.util.concurrent.TimeUnit;
 */
public class LowPowerStandbyControllerTest {
    private static final int STANDBY_TIMEOUT = 5000;

    private static final String TEST_PKG1 = "PKG1";
    private static final String TEST_PKG2 = "PKG2";
    private static final int TEST_PKG1_APP_ID = 123;
    private static final int TEST_PKG2_APP_ID = 456;
    private static final int USER_ID_1 = 0;
    private static final int USER_ID_2 = 10;
    private static final LowPowerStandbyPolicy EMPTY_POLICY = new LowPowerStandbyPolicy(
            "Test policy", Collections.emptySet(), 0, Collections.emptySet());

@@ -95,6 +108,10 @@ public class LowPowerStandbyControllerTest {
    @Mock
    private AlarmManager mAlarmManagerMock;
    @Mock
    private PackageManager mPackageManagerMock;
    @Mock
    private UserManager mUserManagerMock;
    @Mock
    private IPowerManager mIPowerManagerMock;
    @Mock
    private PowerManagerInternal mPowerManagerInternalMock;
@@ -106,7 +123,9 @@ public class LowPowerStandbyControllerTest {
        MockitoAnnotations.initMocks(this);

        mContextSpy = spy(new BroadcastInterceptingContext(InstrumentationRegistry.getContext()));
        when(mContextSpy.getPackageManager()).thenReturn(mPackageManagerMock);
        when(mContextSpy.getSystemService(AlarmManager.class)).thenReturn(mAlarmManagerMock);
        when(mContextSpy.getSystemService(UserManager.class)).thenReturn(mUserManagerMock);
        PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, null, null);
        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
        addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
@@ -132,6 +151,11 @@ public class LowPowerStandbyControllerTest {
        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
        when(mContextSpy.getContentResolver()).thenReturn(cr);

        when(mUserManagerMock.getUserHandles(true)).thenReturn(List.of(
                UserHandle.of(USER_ID_1), UserHandle.of(USER_ID_2)));
        when(mPackageManagerMock.getPackageUid(eq(TEST_PKG1), any())).thenReturn(TEST_PKG1_APP_ID);
        when(mPackageManagerMock.getPackageUid(eq(TEST_PKG2), any())).thenReturn(TEST_PKG2_APP_ID);

        mClock = new OffsettableClock.Stopped();
        mTestLooper = new TestLooper(mClock::now);

@@ -535,6 +559,110 @@ public class LowPowerStandbyControllerTest {
        assertFalse(mController.isAllowed(LOW_POWER_STANDBY_FEATURE_WAKE_ON_LAN));
    }

    @Test
    public void testSetExemptPackages_uidPerUserIsExempt() throws Exception {
        mController.systemReady();
        mController.setEnabled(true);
        mController.setPolicy(policyWithExemptPackages(TEST_PKG1, TEST_PKG2));
        mTestLooper.dispatchAll();

        int[] expectedUidAllowlist = {
                UserHandle.getUid(USER_ID_1, TEST_PKG1_APP_ID),
                UserHandle.getUid(USER_ID_1, TEST_PKG2_APP_ID),
                UserHandle.getUid(USER_ID_2, TEST_PKG1_APP_ID),
                UserHandle.getUid(USER_ID_2, TEST_PKG2_APP_ID)
        };
        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(expectedUidAllowlist);
        verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(expectedUidAllowlist);
    }

    @Test
    public void testExemptPackageIsRemoved_servicesAreNotified() throws Exception {
        mController.systemReady();
        mController.setEnabled(true);
        mController.setPolicy(policyWithExemptPackages(TEST_PKG1));
        mTestLooper.dispatchAll();

        int[] expectedUidAllowlist = {
                UserHandle.getUid(USER_ID_1, TEST_PKG1_APP_ID),
                UserHandle.getUid(USER_ID_2, TEST_PKG1_APP_ID),
        };
        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(expectedUidAllowlist);
        verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(expectedUidAllowlist);
        verifyNoMoreInteractions(mPowerManagerInternalMock, mNetworkPolicyManagerInternalMock);

        reset(mPackageManagerMock);
        when(mPackageManagerMock.getPackageUid(eq(TEST_PKG1), any()))
                .thenThrow(PackageManager.NameNotFoundException.class);

        Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
        intent.setData(Uri.fromParts(IntentFilter.SCHEME_PACKAGE, TEST_PKG1, null));
        intent.putExtra(Intent.EXTRA_REPLACING, false);
        mContextSpy.sendBroadcast(intent);
        mTestLooper.dispatchAll();

        verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[0]);
        verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(new int[0]);
    }

    @Test
    public void testUsersChanged_packagesExemptForNewUser() throws Exception {
        mController.systemReady();
        mController.setEnabled(true);
        mController.setPolicy(policyWithExemptPackages(TEST_PKG1));
        mTestLooper.dispatchAll();

        InOrder inOrder = inOrder(mPowerManagerInternalMock);

        inOrder.verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {
                UserHandle.getUid(USER_ID_1, TEST_PKG1_APP_ID),
                UserHandle.getUid(USER_ID_2, TEST_PKG1_APP_ID),
        });
        inOrder.verifyNoMoreInteractions();

        when(mUserManagerMock.getUserHandles(true)).thenReturn(List.of(UserHandle.of(USER_ID_1)));
        Intent intent = new Intent(Intent.ACTION_USER_REMOVED);
        intent.putExtra(Intent.EXTRA_USER, UserHandle.of(USER_ID_2));
        mContextSpy.sendBroadcast(intent);
        mTestLooper.dispatchAll();

        inOrder.verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {
                UserHandle.getUid(USER_ID_1, TEST_PKG1_APP_ID)
        });
        inOrder.verifyNoMoreInteractions();

        when(mUserManagerMock.getUserHandles(true)).thenReturn(
                List.of(UserHandle.of(USER_ID_1), UserHandle.of(USER_ID_2)));
        intent = new Intent(Intent.ACTION_USER_ADDED);
        intent.putExtra(Intent.EXTRA_USER, UserHandle.of(USER_ID_2));
        mContextSpy.sendBroadcast(intent);
        mTestLooper.dispatchAll();

        inOrder.verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{
                UserHandle.getUid(USER_ID_1, TEST_PKG1_APP_ID),
                UserHandle.getUid(USER_ID_2, TEST_PKG1_APP_ID)
        });
        inOrder.verifyNoMoreInteractions();
    }

    @Test
    public void testIsExempt_exemptIfDisabled() throws Exception {
        mController.systemReady();
        mController.setEnabled(false);
        mTestLooper.dispatchAll();

        assertTrue(mController.isPackageExempt(TEST_PKG1_APP_ID));
    }

    @Test
    public void testIsExempt_notExemptIfEnabled() throws Exception {
        mController.systemReady();
        mController.setEnabled(true);
        mTestLooper.dispatchAll();

        assertFalse(mController.isPackageExempt(TEST_PKG1_APP_ID));
    }

    private void setInteractive() throws Exception {
        when(mIPowerManagerMock.isInteractive()).thenReturn(true);
        mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));
@@ -568,6 +696,15 @@ public class LowPowerStandbyControllerTest {
        );
    }

    private LowPowerStandbyPolicy policyWithExemptPackages(String... exemptPackages) {
        return new LowPowerStandbyPolicy(
                "Test policy",
                new ArraySet<>(exemptPackages),
                0,
                Collections.emptySet()
        );
    }

    private void advanceTime(long timeMs) {
        mClock.fastForward(timeMs);
        mTestLooper.dispatchAll();