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

Commit 4deb852d authored by Suprabh Shukla's avatar Suprabh Shukla
Browse files

Added runtime flag for forced-app-standby

Added a global setting which can be configured to get metrics on the
battery savings from this feature.

Test: atest \
FrameworksServicesTests:\
com.android.server.job.BackgroundRestrictionsTest

Fixes: 70579515
Change-Id: I541f2aac39847f9bf372240cafc4e0f0e5ebdc86
parent 7ae96a6d
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -9801,6 +9801,14 @@ public final class Settings {
         */
        public static final java.lang.String APP_STANDBY_ENABLED = "app_standby_enabled";

        /**
         * Feature flag to enable or disable the Forced App Standby feature.
         * Type: int (0 for false, 1 for true)
         * Default: 1
         * @hide
         */
        public static final String FORCED_APP_STANDBY_ENABLED = "forced_app_standby_enabled";

        /**
         * Whether or not Network Watchlist feature is enabled.
         * Type: int (0 for false, 1 for true)
+1 −0
Original line number Diff line number Diff line
@@ -211,6 +211,7 @@ public class SettingsBackupTest {
                    Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
                    Settings.Global.FANCY_IME_ANIMATIONS,
                    Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
                    Settings.Global.FORCED_APP_STANDBY_ENABLED,
                    Settings.Global.FSTRIM_MANDATORY_INTERVAL,
                    Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
                    Settings.Global.GLOBAL_HTTP_PROXY_HOST,
+1 −1
Original line number Diff line number Diff line
@@ -1162,12 +1162,12 @@ class AlarmManagerService extends SystemService {
            // ignored; both services live in system_server
        }
        publishBinderService(Context.ALARM_SERVICE, mService);
        mForceAppStandbyTracker.start();
    }

    @Override
    public void onBootPhase(int phase) {
        if (phase == PHASE_SYSTEM_SERVICES_READY) {
            mForceAppStandbyTracker.start();
            mConstants.start(getContext().getContentResolver());
            mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
            mLocalDeviceIdleController
+84 −15
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -33,8 +34,10 @@ import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;

@@ -64,10 +67,11 @@ import java.util.List;
 * Doing this would be error-prone, so we punt it for now, but we should revisit it later.
 *
 * Test:
   atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
 * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
 */
public class ForceAppStandbyTracker {
    private static final String TAG = "ForceAppStandbyTracker";
    private static final boolean DEBUG = false;

    @GuardedBy("ForceAppStandbyTracker.class")
    private static ForceAppStandbyTracker sInstance;
@@ -107,7 +111,43 @@ public class ForceAppStandbyTracker {
    boolean mStarted;

    @GuardedBy("mLock")
    boolean mForceAllAppsStandby;
    boolean mForceAllAppsStandby;   // True if device is in extreme battery saver mode

    @GuardedBy("mLock")
    boolean mForcedAppStandbyEnabled;   // True if the forced app standby feature is enabled

    private class FeatureFlagObserver extends ContentObserver {
        FeatureFlagObserver() {
            super(null);
        }

        void register() {
            mContext.getContentResolver().registerContentObserver(
                    Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED),
                    false, this);
        }

        boolean isForcedAppStandbyEnabled() {
            return Settings.Global.getInt(mContext.getContentResolver(),
                    Settings.Global.FORCED_APP_STANDBY_ENABLED, 1) == 1;
        }

        @Override
        public void onChange(boolean selfChange) {
            final boolean enabled = isForcedAppStandbyEnabled();
            synchronized (mLock) {
                if (mForcedAppStandbyEnabled == enabled) {
                    return;
                }
                mForcedAppStandbyEnabled = enabled;
                if (DEBUG) {
                    Slog.d(TAG,
                            "Forced app standby feature flag changed: " + mForcedAppStandbyEnabled);
                }
            }
            mHandler.notifyFeatureFlagChanged();
        }
    }

    public static abstract class Listener {
        /**
@@ -246,6 +286,9 @@ public class ForceAppStandbyTracker {
            mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager());
            mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService());
            mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal());
            final FeatureFlagObserver flagObserver = new FeatureFlagObserver();
            flagObserver.register();
            mForcedAppStandbyEnabled = flagObserver.isForcedAppStandbyEnabled();

            try {
                mIActivityManager.registerUidObserver(new UidObserver(),
@@ -418,25 +461,30 @@ public class ForceAppStandbyTracker {
    }

    private final class UidObserver extends IUidObserver.Stub {
        @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
        @Override
        public void onUidStateChanged(int uid, int procState, long procStateSeq) {
        }

        @Override public void onUidGone(int uid, boolean disabled) {
        @Override
        public void onUidGone(int uid, boolean disabled) {
            uidToBackground(uid, /*remove=*/ true);
        }

        @Override public void onUidActive(int uid) {
        @Override
        public void onUidActive(int uid) {
            uidToForeground(uid);
        }

        @Override public void onUidIdle(int uid, boolean disabled) {
        @Override
        public void onUidIdle(int uid, boolean disabled) {
            // Just to avoid excessive memcpy, don't remove from the array in this case.
            uidToBackground(uid, /*remove=*/ false);
        }

        @Override public void onUidCachedChanged(int uid, boolean cached) {
        @Override
        public void onUidCachedChanged(int uid, boolean cached) {
        }
    }
    };

    private final class AppOpsWatcher extends IAppOpsCallback.Stub {
        @Override
@@ -481,8 +529,8 @@ public class ForceAppStandbyTracker {
        private static final int MSG_ALL_WHITELIST_CHANGED = 4;
        private static final int MSG_TEMP_WHITELIST_CHANGED = 5;
        private static final int MSG_FORCE_ALL_CHANGED = 6;

        private static final int MSG_USER_REMOVED = 7;
        private static final int MSG_FEATURE_FLAG_CHANGED = 8;

        public MyHandler(Looper looper) {
            super(looper);
@@ -491,6 +539,7 @@ public class ForceAppStandbyTracker {
        public void notifyUidForegroundStateChanged(int uid) {
            obtainMessage(MSG_UID_STATE_CHANGED, uid, 0).sendToTarget();
        }

        public void notifyRunAnyAppOpsChanged(int uid, @NonNull String packageName) {
            obtainMessage(MSG_RUN_ANY_CHANGED, uid, 0, packageName).sendToTarget();
        }
@@ -511,12 +560,16 @@ public class ForceAppStandbyTracker {
            obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget();
        }

        public void notifyFeatureFlagChanged() {
            obtainMessage(MSG_FEATURE_FLAG_CHANGED).sendToTarget();
        }

        public void doUserRemoved(int userId) {
            obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
        }

        @Override
        public void dispatchMessage(Message msg) {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_USER_REMOVED:
                    handleUserRemoved(msg.arg1);
@@ -562,6 +615,19 @@ public class ForceAppStandbyTracker {
                        l.onForceAllAppsStandbyChanged(sender);
                    }
                    return;
                case MSG_FEATURE_FLAG_CHANGED:
                    // Feature flag for forced app standby changed.
                    final boolean unblockAlarms;
                    synchronized (mLock) {
                        unblockAlarms = !mForcedAppStandbyEnabled && !mForceAllAppsStandby;
                    }
                    for (Listener l: cloneListeners()) {
                        l.updateAllJobs();
                        if (unblockAlarms) {
                            l.unblockAllUnrestrictedAlarms();
                        }
                    }
                    return;
                case MSG_USER_REMOVED:
                    handleUserRemoved(msg.arg1);
                    return;
@@ -701,7 +767,7 @@ public class ForceAppStandbyTracker {
                return true;
            }

            return isRunAnyRestrictedLocked(uid, packageName);
            return mForcedAppStandbyEnabled && isRunAnyRestrictedLocked(uid, packageName);
        }
    }

@@ -765,6 +831,9 @@ public class ForceAppStandbyTracker {

    public void dump(PrintWriter pw, String indent) {
        synchronized (mLock) {
            pw.print(indent);
            pw.println("Forced App Standby Feature enabled: " + mForcedAppStandbyEnabled);

            pw.print(indent);
            pw.print("Force all apps standby: ");
            pw.println(isForceAllAppsStandbyEnabled());
+16 −2
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
@@ -50,7 +51,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * TODO: Also add a test for temp power whitelist
 * Tests that background restrictions on jobs work as expected.
 * This test requires test-apps/JobTestApp to be installed on the device.
 * To run this test from root of checkout:
@@ -144,15 +144,29 @@ public class BackgroundRestrictionsTest {
                awaitJobStop(DEFAULT_WAIT_TIMEOUT));
    }

    @Test
    public void testFeatureFlag() throws Exception {
        Settings.Global.putInt(mContext.getContentResolver(),
                Settings.Global.FORCED_APP_STANDBY_ENABLED, 0);
        scheduleAndAssertJobStarted();
        setAppOpsModeAllowed(false);
        mIActivityManager.makePackageIdle(TEST_APP_PACKAGE, UserHandle.USER_CURRENT);
        assertFalse("Job stopped even when feature flag was disabled",
                awaitJobStop(DEFAULT_WAIT_TIMEOUT));
    }

    @After
    public void tearDown() throws Exception {
        Intent cancelJobsIntent = new Intent(TestJobActivity.ACTION_CANCEL_JOBS);
        final Intent cancelJobsIntent = new Intent(TestJobActivity.ACTION_CANCEL_JOBS);
        cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
        cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivity(cancelJobsIntent);
        mContext.unregisterReceiver(mJobStateChangeReceiver);
        Thread.sleep(500); // To avoid race with register in the next setUp
        setAppOpsModeAllowed(true);
        setPowerWhiteListed(false);
        Settings.Global.putInt(mContext.getContentResolver(),
                Settings.Global.FORCED_APP_STANDBY_ENABLED, 1);
    }

    private void setPowerWhiteListed(boolean whitelist) throws RemoteException {