diff options
4 files changed, 167 insertions, 10 deletions
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index 6d9e3eddf616..887d82c6413f 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -45,6 +45,14 @@ public interface AppStandbyInternal { boolean idle, int bucket, int reason); /** + * Callback to inform listeners that the parole state has changed. This means apps are + * allowed to do work even if they're idle or in a low bucket. + */ + public void onParoleStateChanged(boolean isParoleOn) { + // No-op by default + } + + /** * Optional callback to inform the listener that the app has transitioned into * an active state due to user interaction. */ @@ -92,6 +100,11 @@ public interface AppStandbyInternal { boolean isAppIdleFiltered(String packageName, int appId, int userId, long elapsedRealtime); + /** + * @return true if currently app idle parole mode is on. + */ + boolean isInParole(); + int[] getIdleUidsForUser(int userId); void setAppIdleAsync(String packageName, boolean idle, int userId); diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 24728dd8edca..cb5cb175ff24 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -214,8 +214,7 @@ public class AppStandbyController implements AppStandbyInternal { private AppIdleHistory mAppIdleHistory; @GuardedBy("mPackageAccessListeners") - private ArrayList<AppIdleStateChangeListener> - mPackageAccessListeners = new ArrayList<>(); + private final ArrayList<AppIdleStateChangeListener> mPackageAccessListeners = new ArrayList<>(); /** Whether we've queried the list of carrier privileged apps. */ @GuardedBy("mAppIdleLock") @@ -235,6 +234,7 @@ public class AppStandbyController implements AppStandbyInternal { static final int MSG_FORCE_IDLE_STATE = 4; static final int MSG_CHECK_IDLE_STATES = 5; static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8; + static final int MSG_PAROLE_STATE_CHANGED = 9; static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10; /** Check the state of one app: arg1 = userId, arg2 = uid, obj = (String) packageName */ static final int MSG_CHECK_PACKAGE_IDLE_STATE = 11; @@ -390,7 +390,16 @@ public class AppStandbyController implements AppStandbyInternal { @VisibleForTesting void setAppIdleEnabled(boolean enabled) { - mAppIdleEnabled = enabled; + synchronized (mAppIdleLock) { + if (mAppIdleEnabled != enabled) { + final boolean oldParoleState = isInParole(); + mAppIdleEnabled = enabled; + if (isInParole() != oldParoleState) { + postParoleStateChanged(); + } + } + } + } @Override @@ -563,11 +572,23 @@ public class AppStandbyController implements AppStandbyInternal { if (mIsCharging != isCharging) { if (DEBUG) Slog.d(TAG, "Setting mIsCharging to " + isCharging); mIsCharging = isCharging; + postParoleStateChanged(); } } } @Override + public boolean isInParole() { + return !mAppIdleEnabled || mIsCharging; + } + + private void postParoleStateChanged() { + if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_STATE_CHANGED"); + mHandler.removeMessages(MSG_PAROLE_STATE_CHANGED); + mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED); + } + + @Override public void postCheckIdleStates(int userId) { mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0)); } @@ -1502,6 +1523,15 @@ public class AppStandbyController implements AppStandbyInternal { } } + private void informParoleStateChanged() { + final boolean paroled = isInParole(); + synchronized (mPackageAccessListeners) { + for (AppIdleStateChangeListener listener : mPackageAccessListeners) { + listener.onParoleStateChanged(paroled); + } + } + } + @Override public void flushToDisk(int userId) { synchronized (mAppIdleLock) { @@ -1920,6 +1950,11 @@ public class AppStandbyController implements AppStandbyInternal { args.recycle(); break; + case MSG_PAROLE_STATE_CHANGED: + if (DEBUG) Slog.d(TAG, "Parole state: " + isInParole()); + informParoleStateChanged(); + break; + case MSG_CHECK_PACKAGE_IDLE_STATE: checkAndUpdateStandbyState((String) msg.obj, msg.arg1, msg.arg2, mInjector.elapsedRealtime()); diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index ec941c8aea59..3283fd9b2c51 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -799,7 +799,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { writePolicyAL(); } - enableFirewallChainUL(FIREWALL_CHAIN_STANDBY, true); setRestrictBackgroundUL(mLoadedRestrictBackground, "init_service"); updateRulesForGlobalChangeAL(false); updateNotificationsNL(); @@ -871,6 +870,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { new NetworkRequest.Builder().build(), mNetworkCallback); mAppStandby.addListener(new NetPolicyAppIdleStateChangeListener()); + synchronized (mUidRulesFirstLock) { + updateRulesForAppIdleParoleUL(); + } // Listen for subscriber changes mContext.getSystemService(SubscriptionManager.class).addOnSubscriptionsChangedListener( @@ -3893,6 +3895,39 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } /** + * Toggle the firewall standby chain and inform listeners if the uid rules have effectively + * changed. + */ + @GuardedBy("mUidRulesFirstLock") + private void updateRulesForAppIdleParoleUL() { + final boolean paroled = mAppStandby.isInParole(); + final boolean enableChain = !paroled; + enableFirewallChainUL(FIREWALL_CHAIN_STANDBY, enableChain); + + int ruleCount = mUidFirewallStandbyRules.size(); + for (int i = 0; i < ruleCount; i++) { + final int uid = mUidFirewallStandbyRules.keyAt(i); + int oldRules = mUidRules.get(uid); + if (enableChain) { + // Chain wasn't enabled before and the other power-related + // chains are whitelists, so we can clear the + // MASK_ALL_NETWORKS part of the rules and re-inform listeners if + // the effective rules result in blocking network access. + oldRules &= MASK_METERED_NETWORKS; + } else { + // Skip if it had no restrictions to begin with + if ((oldRules & MASK_ALL_NETWORKS) == 0) continue; + } + final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldRules, paroled); + if (newUidRules == RULE_NONE) { + mUidRules.delete(uid); + } else { + mUidRules.put(uid, newUidRules); + } + } + } + + /** * Update rules that might be changed by {@link #mRestrictBackground}, * {@link #mRestrictPower}, or {@link #mDeviceIdleMode} value. */ @@ -4347,7 +4382,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private void updateRulesForPowerRestrictionsUL(int uid) { final int oldUidRules = mUidRules.get(uid, RULE_NONE); - final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules); + final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules, false); if (newUidRules == RULE_NONE) { mUidRules.delete(uid); @@ -4361,28 +4396,30 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * * @param uid the uid of the app to update rules for * @param oldUidRules the current rules for the uid, in order to determine if there's a change + * @param paroled whether to ignore idle state of apps and only look at other restrictions * * @return the new computed rules for the uid */ - private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules) { + private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules, boolean paroled) { if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) { Trace.traceBegin(Trace.TRACE_TAG_NETWORK, - "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules); + "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules + "/" + + (paroled ? "P" : "-")); } try { - return updateRulesForPowerRestrictionsULInner(uid, oldUidRules); + return updateRulesForPowerRestrictionsULInner(uid, oldUidRules, paroled); } finally { Trace.traceEnd(Trace.TRACE_TAG_NETWORK); } } - private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules) { + private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules, boolean paroled) { if (!isUidValidForBlacklistRules(uid)) { if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid); return RULE_NONE; } - final boolean isIdle = isUidIdle(uid); + final boolean isIdle = !paroled && isUidIdle(uid); final boolean restrictMode = isIdle || mRestrictPower || mDeviceIdleMode; final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid); @@ -4452,6 +4489,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } catch (NameNotFoundException nnfe) { } } + + @Override + public void onParoleStateChanged(boolean isParoleOn) { + synchronized (mUidRulesFirstLock) { + mLogger.paroleStateChanged(isParoleOn); + updateRulesForAppIdleParoleUL(); + } + } } private void dispatchUidRulesChanged(INetworkPolicyListener listener, int uid, int uidRules) { diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 39062f017a73..6718db768fdb 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -366,29 +366,87 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime, false)); } + private static class TestParoleListener extends AppIdleStateChangeListener { + private boolean mIsParoleOn = false; + private CountDownLatch mLatch; + private boolean mIsExpecting = false; + private boolean mExpectedParoleState; + + boolean getParoleState() { + synchronized (this) { + return mIsParoleOn; + } + } + + void rearmLatch(boolean expectedParoleState) { + synchronized (this) { + mLatch = new CountDownLatch(1); + mIsExpecting = true; + mExpectedParoleState = expectedParoleState; + } + } + + void awaitOnLatch(long time) throws Exception { + mLatch.await(time, TimeUnit.MILLISECONDS); + } + + @Override + public void onAppIdleStateChanged(String packageName, int userId, boolean idle, + int bucket, int reason) { + } + + @Override + public void onParoleStateChanged(boolean isParoleOn) { + synchronized (this) { + // Only record information if it is being looked for + if (mLatch != null && mLatch.getCount() > 0) { + mIsParoleOn = isParoleOn; + if (mIsExpecting && isParoleOn == mExpectedParoleState) { + mLatch.countDown(); + } + } + } + } + } + @Test public void testIsAppIdle_Charging() throws Exception { + TestParoleListener paroleListener = new TestParoleListener(); + mController.addListener(paroleListener); + setChargingState(mController, false); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM); assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + assertFalse(mController.isInParole()); + paroleListener.rearmLatch(true); setChargingState(mController, true); + paroleListener.awaitOnLatch(2000); + assertTrue(paroleListener.getParoleState()); assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + assertTrue(mController.isInParole()); + paroleListener.rearmLatch(false); setChargingState(mController, false); + paroleListener.awaitOnLatch(2000); + assertFalse(paroleListener.getParoleState()); assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + assertFalse(mController.isInParole()); } @Test public void testIsAppIdle_Enabled() throws Exception { setChargingState(mController, false); + TestParoleListener paroleListener = new TestParoleListener(); + mController.addListener(paroleListener); + setAppIdleEnabled(mController, true); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM); @@ -396,11 +454,17 @@ public class AppStandbyControllerTests { assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + paroleListener.rearmLatch(false); setAppIdleEnabled(mController, false); + paroleListener.awaitOnLatch(2000); + assertTrue(paroleListener.mIsParoleOn); assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); + paroleListener.rearmLatch(true); setAppIdleEnabled(mController, true); + paroleListener.awaitOnLatch(2000); + assertFalse(paroleListener.getParoleState()); assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0)); assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, false)); |