From 490df4cc10948c1c0a4d28a73a3206b3ba4d8ec8 Mon Sep 17 00:00:00 2001 From: Robert Horvath Date: Mon, 28 Nov 2022 16:03:27 +0100 Subject: [PATCH] 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 --- .../power/LowPowerStandbyController.java | 81 +++++++++- .../power/LowPowerStandbyControllerTest.java | 139 +++++++++++++++++- 2 files changed, 214 insertions(+), 6 deletions(-) diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java index c9d69b1839175..223bd551b71dd 100644 --- a/services/core/java/com/android/server/power/LowPowerStandbyController.java +++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java @@ -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 userHandles = userManager.getUserHandles(true); final ArraySet 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 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(); diff --git a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java index 5e6c2ba9520c6..6553ea9075631 100644 --- a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java @@ -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();