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
This commit is contained in:
Robert Horvath
2022-11-28 16:03:27 +01:00
parent 46ccaf46ff
commit 490df4cc10
2 changed files with 214 additions and 6 deletions

View File

@@ -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();

View File

@@ -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();