OSDN Git Service

DO NOT MERGE Extreme battery saver experiment
authorMakoto Onuki <omakoto@google.com>
Wed, 6 Sep 2017 19:16:56 +0000 (12:16 -0700)
committerandroid-build-team Robot <android-build-team-robot@google.com>
Wed, 18 Oct 2017 17:18:53 +0000 (17:18 +0000)
PS1 is just a squashed cherry-picks of:
- https://googleplex-android-review.git.corp.google.com/#/c/platform/frameworks/base/+/2808591/
- https://googleplex-android-review.git.corp.google.com/#/c/platform/frameworks/base/+/2542212/

Test: Tested manually
Bug: 64976537
Change-Id: I99f93471e348bdd31ac08fbd91b27bab8c8e498b
(cherry picked from commit d8a9b29e12b4ef45e6d66fcc54038adfc6c8fdbf)

33 files changed:
core/java/android/app/ActivityManagerInternal.java
core/java/android/app/AppOpsManager.java
core/java/android/app/IActivityManager.aidl
core/java/android/app/PendingIntent.java
core/java/android/provider/Settings.java
core/java/android/util/KeyValueListParser.java
core/java/com/android/internal/os/BatteryStatsImpl.java
packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
services/core/java/com/android/server/AlarmManagerService.java
services/core/java/com/android/server/AppOpsService.java
services/core/java/com/android/server/DeviceIdleController.java
services/core/java/com/android/server/am/ActivityManagerService.java
services/core/java/com/android/server/job/JobSchedulerService.java
services/core/java/com/android/server/job/controllers/BackgroundJobsController.java [new file with mode: 0644]
services/core/java/com/android/server/job/controllers/JobStatus.java
services/core/java/com/android/server/location/GnssLocationProvider.java
services/core/java/com/android/server/power/BatterySaverPolicy.java
services/core/java/com/android/server/power/PowerManagerService.java
services/core/java/com/android/server/wm/WindowManagerService.java
services/core/jni/com_android_server_power_PowerManagerService.cpp
services/tests/servicestests/Android.mk
services/tests/servicestests/AndroidManifest.xml
services/tests/servicestests/AndroidTest.xml
services/tests/servicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml [new file with mode: 0644]
services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java [new file with mode: 0644]
services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java [new file with mode: 0644]
services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
services/tests/servicestests/test-apps/JobTestApp/Android.mk [new file with mode: 0644]
services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml [new file with mode: 0644]
services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java [new file with mode: 0644]
services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java [new file with mode: 0644]

index c8d9839..68891b2 100644 (file)
@@ -177,7 +177,7 @@ public abstract class ActivityManagerInternal {
     /**
      * Allow DeviceIdleController to tell us about what apps are whitelisted.
      */
-    public abstract void setDeviceIdleWhitelist(int[] appids);
+    public abstract void setDeviceIdleWhitelist(int[] userAppids, int[] allAppids);
 
     /**
      * Update information about which app IDs are on the temp whitelist.
index b331d84..4bd85ae 100644 (file)
@@ -252,8 +252,10 @@ public class AppOpsManager {
     public static final int OP_INSTANT_APP_START_FOREGROUND = 68;
     /** @hide Answer incoming phone calls */
     public static final int OP_ANSWER_PHONE_CALLS = 69;
+    /** @hide Run jobs when in background */
+    public static final int OP_RUN_ANY_IN_BACKGROUND = 70;
     /** @hide */
-    public static final int _NUM_OP = 70;
+    public static final int _NUM_OP = 71;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -492,7 +494,8 @@ public class AppOpsManager {
             OP_REQUEST_INSTALL_PACKAGES,
             OP_PICTURE_IN_PICTURE,
             OP_INSTANT_APP_START_FOREGROUND,
-            OP_ANSWER_PHONE_CALLS
+            OP_ANSWER_PHONE_CALLS,
+            OP_RUN_ANY_IN_BACKGROUND,
     };
 
     /**
@@ -570,6 +573,7 @@ public class AppOpsManager {
             OPSTR_PICTURE_IN_PICTURE,
             OPSTR_INSTANT_APP_START_FOREGROUND,
             OPSTR_ANSWER_PHONE_CALLS,
+            null, // OP_RUN_ANY_IN_BACKGROUND
     };
 
     /**
@@ -647,6 +651,7 @@ public class AppOpsManager {
             "PICTURE_IN_PICTURE",
             "INSTANT_APP_START_FOREGROUND",
             "ANSWER_PHONE_CALLS",
+            "RUN_ANY_IN_BACKGROUND",
     };
 
     /**
@@ -724,6 +729,7 @@ public class AppOpsManager {
             null, // no permission for entering picture-in-picture on hide
             Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
             Manifest.permission.ANSWER_PHONE_CALLS,
+            null, // no permission for OP_RUN_ANY_IN_BACKGROUND
     };
 
     /**
@@ -802,6 +808,7 @@ public class AppOpsManager {
             null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
             null, // INSTANT_APP_START_FOREGROUND
             null, // ANSWER_PHONE_CALLS
+            null, // OP_RUN_ANY_IN_BACKGROUND
     };
 
     /**
@@ -879,6 +886,7 @@ public class AppOpsManager {
             false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
             false, // INSTANT_APP_START_FOREGROUND
             false, // ANSWER_PHONE_CALLS
+            false, // OP_RUN_ANY_IN_BACKGROUND
     };
 
     /**
@@ -955,6 +963,7 @@ public class AppOpsManager {
             AppOpsManager.MODE_ALLOWED,  // OP_PICTURE_IN_PICTURE
             AppOpsManager.MODE_DEFAULT,  // OP_INSTANT_APP_START_FOREGROUND
             AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
+            AppOpsManager.MODE_ALLOWED,  // OP_RUN_ANY_IN_BACKGROUND
     };
 
     /**
@@ -1035,6 +1044,7 @@ public class AppOpsManager {
             false, // OP_PICTURE_IN_PICTURE
             false,
             false, // ANSWER_PHONE_CALLS
+            false, // OP_RUN_ANY_IN_BACKGROUND
     };
 
     /**
index 1811748..8dc9853 100644 (file)
@@ -323,6 +323,7 @@ interface IActivityManager {
     int getLaunchedFromUid(in IBinder activityToken);
     void unstableProviderDied(in IBinder connection);
     boolean isIntentSenderAnActivity(in IIntentSender sender);
+    boolean isIntentSenderAForegroundService(in IIntentSender sender);
     int startActivityAsUser(in IApplicationThread caller, in String callingPackage,
             in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho,
             int requestCode, int flags, in ProfilerInfo profilerInfo,
index b7d3f57..a25c226 100644 (file)
@@ -20,18 +20,17 @@ import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.Intent;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
+import android.content.Intent;
 import android.content.IntentSender;
 import android.os.Bundle;
-import android.os.Looper;
-import android.os.RemoteException;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.Process;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.AndroidException;
 
@@ -1012,6 +1011,19 @@ public final class PendingIntent implements Parcelable {
 
     /**
      * @hide
+     * Check whether this PendingIntent will launch a foreground service
+     */
+    public boolean isForegroundService() {
+        try {
+            return ActivityManager.getService()
+                    .isIntentSenderAForegroundService(mTarget);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
      * Return the Intent of this PendingIntent.
      */
     public Intent getIntent() {
index a248bf7..1835c10 100755 (executable)
@@ -71,6 +71,7 @@ import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.MemoryIntArray;
+import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
@@ -1847,6 +1848,13 @@ public final class Settings {
                 if (makeDefault) {
                     arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
                 }
+
+                // Log all EBS relevant config changes.
+                if (Global.LOW_POWER_MODE_TRIGGER_LEVEL.equals(name)
+                        || name.startsWith(Settings.Global.BATTERY_SAVER_CONSTANTS)) {
+                    Slog.w("XXX", "Detected write: " + mCallSetCommand + ", " + name + ", args=" + arg, new RuntimeException("HERE"));
+                }
+
                 IContentProvider cp = mProviderHolder.getProvider(cr);
                 cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
             } catch (RemoteException e) {
@@ -9347,6 +9355,9 @@ public final class Settings {
          */
         public static final String BATTERY_SAVER_CONSTANTS = "battery_saver_constants";
 
+        /** @hide */
+        public static final String BATTERY_SAVER_USE_RED_BAR = "battery_saver_use_red_bar";
+
         /**
          * Battery anomaly detection specific settings
          * This is encoded as a key=value list, separated by commas.
index be531ff..d11c79d 100644 (file)
@@ -17,6 +17,8 @@ package android.util;
 
 import android.text.TextUtils;
 
+import java.util.Collection;
+
 /**
  * Parses a list of key=value pairs, separated by some delimiter, and puts the results in
  * an internal Map. Values can be then queried by key, or if not found, a default value
@@ -147,4 +149,11 @@ public class KeyValueListParser {
         }
         return def;
     }
+
+    /**
+     * @return All the keys.
+     */
+    public Collection<String> getKeys() {
+        return mValues.keySet();
+    }
 }
index f26c0cd..0832bd4 100644 (file)
@@ -143,11 +143,12 @@ public class BatteryStatsImpl extends BatteryStats {
             MAX_HISTORY_BUFFER = 96*1024;  // 96KB
             MAX_MAX_HISTORY_BUFFER = 128*1024; // 128KB
         } else {
-            MAX_HISTORY_ITEMS = 2000;
-            MAX_MAX_HISTORY_ITEMS = 3000;
-            MAX_WAKELOCKS_PER_UID = 100;
-            MAX_HISTORY_BUFFER = 256*1024;  // 256KB
-            MAX_MAX_HISTORY_BUFFER = 320*1024;  // 256KB
+            final int INCREASE_FACTOR = 4;
+            MAX_HISTORY_ITEMS = 2000 * INCREASE_FACTOR;
+            MAX_MAX_HISTORY_ITEMS = 3000 * INCREASE_FACTOR;
+            MAX_WAKELOCKS_PER_UID = 100 * INCREASE_FACTOR;
+            MAX_HISTORY_BUFFER = 256*1024 * INCREASE_FACTOR;
+            MAX_MAX_HISTORY_BUFFER = 320*1024 * INCREASE_FACTOR;
         }
     }
 
index 7d7f9ae..8f1322d 100644 (file)
@@ -334,6 +334,20 @@ public class SettingsProvider extends ContentProvider {
 
     @Override
     public Bundle call(String method, String name, Bundle args) {
+        if (name != null && args != null && Settings.CALL_METHOD_PUT_GLOBAL.equals(method)) {
+            Object value = args.get(Settings.NameValueTable.VALUE);
+
+            if ((value == null)
+                    && (Global.LOW_POWER_MODE_TRIGGER_LEVEL.equals(name)
+                    || name.startsWith(Settings.Global.BATTERY_SAVER_CONSTANTS)
+                    || name.startsWith(Settings.Global.ALARM_MANAGER_CONSTANTS)
+                    || name.startsWith(Settings.Global.JOB_SCHEDULER_CONSTANTS))) {
+                Slog.wtf("XXX", "Detected writing null: " + method + ", " + name);
+                return new Bundle();
+            }
+        }
+
+
         final int requestingUserId = getRequestingUserId(args);
         switch (method) {
             case Settings.CALL_METHOD_GET_GLOBAL: {
index a6fadf9..aecaf41 100644 (file)
@@ -109,6 +109,7 @@ final public class SettingsService extends Binder {
 
         int mUser = -1;     // unspecified
         CommandVerb mVerb = CommandVerb.UNSPECIFIED;
+        private final String NULL_MARKER = "";
         String mTable = null;
         String mKey = null;
         String mValue = null;
@@ -149,6 +150,9 @@ final public class SettingsService extends Binder {
                         mVerb = CommandVerb.GET;
                     } else if ("put".equalsIgnoreCase(arg)) {
                         mVerb = CommandVerb.PUT;
+                    } else if ("putnull".equalsIgnoreCase(arg)) {
+                        mVerb = CommandVerb.PUT;
+                        mValue = NULL_MARKER;
                     } else if ("delete".equalsIgnoreCase(arg)) {
                         mVerb = CommandVerb.DELETE;
                     } else if ("list".equalsIgnoreCase(arg)) {
@@ -255,6 +259,9 @@ final public class SettingsService extends Binder {
                 perr.println("Bad arguments");
                 return -1;
             }
+            if (mValue == NULL_MARKER) { // Note "==", not equals().
+                mValue = null;
+            }
 
             if (mUser == UserHandle.USER_CURRENT) {
                 try {
@@ -473,6 +480,8 @@ final public class SettingsService extends Binder {
                 pw.println("      Change the contents of KEY to VALUE.");
                 pw.println("      TAG to associate with the setting.");
                 pw.println("      {default} to set as the default, case-insensitive only for global/secure namespace");
+                pw.println("  putnull [--user <USER_ID> | current] NAMESPACE KEY [TAG] [default]");
+                pw.println("      Same as \"put\", except it sets null to KEY.");
                 pw.println("  delete NAMESPACE KEY");
                 pw.println("      Delete the entry for KEY.");
                 pw.println("  reset [--user <USER_ID> | current] NAMESPACE {PACKAGE_NAME | RESET_MODE}");
index 200cada..cf3a5b2 100644 (file)
@@ -3369,7 +3369,9 @@ public class StatusBar extends SystemUI implements DemoMode,
         final boolean powerSave = mBatteryController.isPowerSave();
         final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive
                 && windowState != WINDOW_STATE_HIDDEN && !powerSave;
-        if (powerSave && getBarState() == StatusBarState.SHADE) {
+        if (powerSave && getBarState() == StatusBarState.SHADE
+                && (Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.BATTERY_SAVER_USE_RED_BAR, 1) != 0)) {
             mode = MODE_WARNING;
         }
         transitions.transitionTo(mode, anim);
index d1cf025..8f7e7c7 100644 (file)
@@ -37,6 +37,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PermissionInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.BatteryManager;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
@@ -44,8 +45,11 @@ import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -54,6 +58,7 @@ import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.Slog;
@@ -85,8 +90,11 @@ import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
 import static android.app.AlarmManager.ELAPSED_REALTIME;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.LocalLog;
+import com.android.server.power.BatterySaverPolicy.ServiceType;
 
 class AlarmManagerService extends SystemService {
     private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP;
@@ -106,6 +114,7 @@ class AlarmManagerService extends SystemService {
     static final boolean DEBUG_ALARM_CLOCK = localLOGV || false;
     static final boolean DEBUG_LISTENER_CALLBACK = localLOGV || false;
     static final boolean DEBUG_WAKELOCK = localLOGV || false;
+    static final boolean DEBUG_BG_LIMIT = localLOGV || false;
     static final boolean RECORD_ALARMS_IN_HISTORY = true;
     static final boolean RECORD_DEVICE_IDLE_ALARMS = false;
     static final int ALARM_EVENT = 1;
@@ -114,7 +123,7 @@ class AlarmManagerService extends SystemService {
     private final Intent mBackgroundIntent
             = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
     static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder();
-    
+
     static final boolean WAKEUP_STATS = false;
 
     private static final Intent NEXT_ALARM_CLOCK_CHANGED_INTENT =
@@ -125,10 +134,15 @@ class AlarmManagerService extends SystemService {
     final LocalLog mLog = new LocalLog(TAG);
 
     AppOpsManager mAppOps;
+    IAppOpsService mAppOpsService;
     DeviceIdleController.LocalService mLocalDeviceIdleController;
 
     final Object mLock = new Object();
 
+    ArraySet<String> mForcedAppStandbyPackages = new ArraySet<>();
+    SparseBooleanArray mForegroundUids = new SparseBooleanArray();
+    // List of alarms per uid deferred due to user applied background restrictions on the source app
+    SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();
     long mNativeData;
     private long mNextWakeup;
     private long mNextNonWakeup;
@@ -158,6 +172,10 @@ class AlarmManagerService extends SystemService {
     long mAllowWhileIdleMinTime;
     int mNumTimeChanged;
 
+    boolean mForceAppStandby;
+    boolean mForegroundAppExperiment;
+    ChargingReceiver mChargingReceiver;
+
     // Bookkeeping about the identity of the "System UI" package, determined at runtime.
 
     /**
@@ -172,11 +190,7 @@ class AlarmManagerService extends SystemService {
      */
     int mSystemUiUid;
 
-    /**
-     * The current set of user whitelisted apps for device idle mode, meaning these are allowed
-     * to freely schedule alarms.
-     */
-    int[] mDeviceIdleUserWhitelist = new int[0];
+    int[] mDeviceIdleSystemWhitelist = new int[0];
 
     /**
      * For each uid, this is the last time we dispatched an "allow while idle" alarm,
@@ -184,6 +198,9 @@ class AlarmManagerService extends SystemService {
      */
     final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray();
 
+    private static final String ALARM_MANAGER_EXPERIMENT_KEY =
+            Settings.Global.ALARM_MANAGER_CONSTANTS + "_om";
+
     final static class IdleDispatchEntry {
         int uid;
         String pkg;
@@ -225,6 +242,8 @@ class AlarmManagerService extends SystemService {
         private static final String KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION
                 = "allow_while_idle_whitelist_duration";
         private static final String KEY_LISTENER_TIMEOUT = "listener_timeout";
+        private static final String KEY_BG_RESTRICTIONS_ENABLED = "limit_bg_alarms_enabled";
+        private static final String KEY_FOREGROUND_ONLY_EXPERIMENT = "foreground_only";
 
         private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
         private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
@@ -234,6 +253,8 @@ class AlarmManagerService extends SystemService {
 
         private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
 
+        private static final boolean DEFAULT_BACKGROUND_RESTRICTIONS_ENABLED = false;
+
         // Minimum futurity of a new alarm
         public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
 
@@ -253,6 +274,8 @@ class AlarmManagerService extends SystemService {
         // Direct alarm listener callback timeout
         public long LISTENER_TIMEOUT = DEFAULT_LISTENER_TIMEOUT;
 
+        public boolean BACKGROUND_ALARMS_BLOCKED = DEFAULT_BACKGROUND_RESTRICTIONS_ENABLED;
+
         private ContentResolver mResolver;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
         private long mLastAllowWhileIdleWhitelistDuration = -1;
@@ -267,6 +290,9 @@ class AlarmManagerService extends SystemService {
             mResolver = resolver;
             mResolver.registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.ALARM_MANAGER_CONSTANTS), false, this);
+            mResolver.registerContentObserver(Settings.Global.getUriFor(
+                    ALARM_MANAGER_EXPERIMENT_KEY), false, this);
+
             updateConstants();
         }
 
@@ -311,6 +337,25 @@ class AlarmManagerService extends SystemService {
                         DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION);
                 LISTENER_TIMEOUT = mParser.getLong(KEY_LISTENER_TIMEOUT,
                         DEFAULT_LISTENER_TIMEOUT);
+                BACKGROUND_ALARMS_BLOCKED = mParser.getBoolean(KEY_BG_RESTRICTIONS_ENABLED,
+                        DEFAULT_BACKGROUND_RESTRICTIONS_ENABLED);
+                maybeRunPendingBackgroundAlarms();
+                if (DEBUG_BG_LIMIT) {
+                    Slog.d(TAG, "Background limiting enabled");
+                }
+
+                String expSettings =
+                        Settings.Global.getString(mResolver, ALARM_MANAGER_EXPERIMENT_KEY);
+                try {
+                    Slog.d(TAG, "Parsing: " + expSettings);
+                    mParser.setString(expSettings);
+                } catch (IllegalArgumentException e) {
+                    Slog.e(TAG, "Failed to parse experiments key", e);
+                }
+
+                mForegroundAppExperiment =
+                        mParser.getBoolean(KEY_FOREGROUND_ONLY_EXPERIMENT, false);
+                updateForceAppStandby(mForegroundAppExperiment);
 
                 updateAllowWhileIdleMinTimeLocked();
                 updateAllowWhileIdleWhitelistDurationLocked();
@@ -705,7 +750,7 @@ class AlarmManagerService extends SystemService {
     static final BatchTimeOrder sBatchOrder = new BatchTimeOrder();
     final ArrayList<Batch> mAlarmBatches = new ArrayList<>();
 
-    // set to null if in idle mode; while in this mode, any alarms we don't want
+    // set to non-null if in idle mode; while in this mode, any alarms we don't want
     // to run during this time are placed in mPendingWhileIdleAlarms
     Alarm mPendingIdleUntil = null;
     Alarm mNextWakeFromIdle = null;
@@ -814,6 +859,108 @@ class AlarmManagerService extends SystemService {
         setImplLocked(a, true, doValidate);
     }
 
+    /**
+     * Sends alarms that were blocked due to user applied background restrictions - either because
+     * the user lifted those or the uid came to foreground.
+     *
+     * @param uid uid to filter on
+     * @param packageName package to filter on, or null for all packages in uid
+     */
+    void sendPendingBackgroundAlarmsLocked(int uid, String packageName) {
+        final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.get(uid);
+        if (alarmsForUid == null || alarmsForUid.size() == 0) {
+            return;
+        }
+        final ArrayList<Alarm> alarmsToDeliver;
+        if (packageName != null) {
+            if (DEBUG_BG_LIMIT) {
+                Slog.d(TAG, "Sending blocked alarms for uid " + uid + ", package " + packageName);
+            }
+            alarmsToDeliver = new ArrayList<>();
+            for (int i = alarmsForUid.size() - 1; i >= 0; i--) {
+                final Alarm a = alarmsForUid.get(i);
+                if (a.matches(packageName)) {
+                    alarmsToDeliver.add(alarmsForUid.remove(i));
+                }
+            }
+            if (alarmsForUid.size() == 0) {
+                mPendingBackgroundAlarms.remove(uid);
+            }
+        } else {
+            if (DEBUG_BG_LIMIT) {
+                Slog.d(TAG, "Sending blocked alarms for uid " + uid);
+            }
+            alarmsToDeliver = alarmsForUid;
+            mPendingBackgroundAlarms.remove(uid);
+        }
+        deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
+    }
+
+    void sendPendingBackgroundAlarmsForAppIdLocked(int appId) {
+        final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+        for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
+            final int uid = mPendingBackgroundAlarms.keyAt(i);
+            final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
+            if (UserHandle.getAppId(uid) == appId) {
+                alarmsToDeliver.addAll(alarmsForUid);
+                mPendingBackgroundAlarms.removeAt(i);
+            }
+        }
+        deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
+    }
+
+    private void deliverPendingBackgroundAlarmsLocked(ArrayList<Alarm> alarms, long nowELAPSED) {
+        final int N = alarms.size();
+        boolean hasWakeup = false;
+        for (int i = 0; i < N; i++) {
+            final Alarm alarm = alarms.get(i);
+            if (alarm.wakeup) {
+                hasWakeup = true;
+            }
+            alarm.count = 1;
+            // Recurring alarms may have passed several alarm intervals while the
+            // alarm was kept pending. Send the appropriate trigger count.
+            if (alarm.repeatInterval > 0) {
+                alarm.count += (nowELAPSED - alarm.whenElapsed) / alarm.repeatInterval;
+                // Also schedule its next recurrence
+                final long delta = alarm.count * alarm.repeatInterval;
+                final long nextElapsed = alarm.whenElapsed + delta;
+                setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
+                        maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
+                        alarm.repeatInterval, alarm.operation, null, null, alarm.flags, true,
+                        alarm.workSource, alarm.alarmClock, alarm.uid, alarm.packageName);
+                // Kernel alarms will be rescheduled as needed in setImplLocked
+            }
+        }
+        if (!hasWakeup && checkAllowNonWakeupDelayLocked(nowELAPSED)) {
+            // No need to wakeup for non wakeup alarms
+            if (mPendingNonWakeupAlarms.size() == 0) {
+                mStartCurrentDelayTime = nowELAPSED;
+                mNextNonWakeupDeliveryTime = nowELAPSED
+                        + ((currentNonWakeupFuzzLocked(nowELAPSED)*3)/2);
+            }
+            mPendingNonWakeupAlarms.addAll(alarms);
+            mNumDelayedAlarms += alarms.size();
+        } else {
+            if (DEBUG_BG_LIMIT) {
+                Slog.d(TAG, "Waking up to deliver pending blocked alarms");
+            }
+            // Since we are waking up, also deliver any pending non wakeup alarms we have.
+            if (mPendingNonWakeupAlarms.size() > 0) {
+                alarms.addAll(mPendingNonWakeupAlarms);
+                final long thisDelayTime = nowELAPSED - mStartCurrentDelayTime;
+                mTotalDelayTime += thisDelayTime;
+                if (mMaxDelayTime < thisDelayTime) {
+                    mMaxDelayTime = thisDelayTime;
+                }
+                mPendingNonWakeupAlarms.clear();
+            }
+            calculateDeliveryPriorities(alarms);
+            Collections.sort(alarms, mAlarmDispatchComparator);
+            deliverAlarmsLocked(alarms, nowELAPSED);
+        }
+    }
+
     void restorePendingWhileIdleAlarmsLocked() {
         if (RECORD_DEVICE_IDLE_ALARMS) {
             IdleDispatchEntry ent = new IdleDispatchEntry();
@@ -1032,11 +1179,14 @@ class AlarmManagerService extends SystemService {
 
         try {
             ActivityManager.getService().registerUidObserver(new UidObserver(),
-                    ActivityManager.UID_OBSERVER_IDLE, ActivityManager.PROCESS_STATE_UNKNOWN, null);
+                    ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
+                            | ActivityManager.UID_OBSERVER_ACTIVE,
+                    ActivityManager.PROCESS_STATE_UNKNOWN, null);
         } catch (RemoteException e) {
             // ignored; both services live in system_server
         }
-
+        mAppOpsService = IAppOpsService.Stub.asInterface(
+                ServiceManager.getService(Context.APP_OPS_SERVICE));
         publishBinderService(Context.ALARM_SERVICE, mService);
         publishLocalService(LocalService.class, new LocalService());
     }
@@ -1048,6 +1198,34 @@ class AlarmManagerService extends SystemService {
             mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
             mLocalDeviceIdleController
                     = LocalServices.getService(DeviceIdleController.LocalService.class);
+            try {
+                mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
+                        new AppOpsWatcher());
+            } catch (RemoteException rexc) {
+                // Shouldn't happen as they are in the same process.
+                Slog.e(TAG, "AppOps service not reachable", rexc);
+            }
+
+            final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+            if (pmi != null) {
+                pmi.registerLowPowerModeObserver(
+                        new PowerManagerInternal.LowPowerModeListener() {
+                            @Override
+                            public int getServiceType() {
+                                return ServiceType.FORCE_APPS_STANDBY;
+                            }
+
+                            @Override
+                            public void onLowPowerModeChanged(PowerSaveState result) {
+                                updateForceAppStandby(result.batterySaverEnabled);
+                            }
+                        });
+                updateForceAppStandby(
+                        pmi.getLowPowerState(ServiceType.FORCE_APPS_STANDBY).batterySaverEnabled);
+            } else {
+                Slog.wtf(TAG, "PowerManagerInternal not found.");
+            }
+            mChargingReceiver = new ChargingReceiver();
         }
     }
 
@@ -1060,6 +1238,44 @@ class AlarmManagerService extends SystemService {
         }
     }
 
+    void updateForceAppStandby(boolean enabled) {
+        synchronized (mLock) {
+            int status = ((BatteryManager) getContext().getSystemService(Context.BATTERY_SERVICE))
+                    .getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS);
+            boolean charging = status != BatteryManager.BATTERY_STATUS_DISCHARGING;
+            mForceAppStandby = mForegroundAppExperiment ? !charging : enabled;
+            Slog.d(TAG, "Force app standby is: " + mForceAppStandby + " charging: " + charging
+                    + " FAE: " + mForegroundAppExperiment + " status: " + status);
+            maybeRunPendingBackgroundAlarms();
+       }
+    }
+
+    void maybeRunPendingBackgroundAlarms() {
+        synchronized (mLock) {
+            if (!isBackgroundRestrictionEnabled()) {
+                if (DEBUG_BG_LIMIT) {
+                    Slog.d(TAG, "Running pending-background alarms.");
+                }
+
+                // TODO: remove this code and constant when feature is turned on
+                // deliver all blocked alarms
+                final ArrayList<Alarm> allBlockedAlarms = new ArrayList<>();
+                for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
+                    allBlockedAlarms.addAll(mPendingBackgroundAlarms.valueAt(i));
+                }
+                mPendingBackgroundAlarms = new SparseArray<>();
+                deliverPendingBackgroundAlarmsLocked(allBlockedAlarms,
+                        SystemClock.elapsedRealtime());
+            }
+        }
+    }
+
+    boolean isBackgroundRestrictionEnabled() {
+        synchronized (mLock) {
+            return mForceAppStandby || mConstants.BACKGROUND_ALARMS_BLOCKED;
+        }
+    }
+
     void setTimeZoneImpl(String tz) {
         if (TextUtils.isEmpty(tz)) {
             return;
@@ -1069,7 +1285,7 @@ class AlarmManagerService extends SystemService {
         // Prevent reentrant calls from stepping on each other when writing
         // the time zone property
         boolean timeZoneWasChanged = false;
-        synchronized (this) {
+        synchronized (mLock) {
             String current = SystemProperties.get(TIMEZONE_PROPERTY);
             if (current == null || !current.equals(zone.getID())) {
                 if (localLOGV) {
@@ -1376,7 +1592,7 @@ class AlarmManagerService extends SystemService {
             // timing restrictions.
             } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
                     || callingUid == mSystemUiUid
-                    || Arrays.binarySearch(mDeviceIdleUserWhitelist,
+                    || Arrays.binarySearch(mDeviceIdleSystemWhitelist,
                             UserHandle.getAppId(callingUid)) >= 0)) {
                 flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
                 flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
@@ -1450,17 +1666,25 @@ class AlarmManagerService extends SystemService {
     };
 
     public final class LocalService {
-        public void setDeviceIdleUserWhitelist(int[] appids) {
-            setDeviceIdleUserWhitelistImpl(appids);
+        public void setDeviceIdleUserWhitelist(int[] userAppids, int[] systemAppids) {
+            setDeviceIdleWhitelistImpl(systemAppids, mDeviceIdleSystemWhitelist);
+            mDeviceIdleSystemWhitelist = systemAppids;
         }
     }
 
     void dumpImpl(PrintWriter pw) {
         synchronized (mLock) {
             pw.println("Current Alarm Manager state:");
+            pw.println("  ForceAppStandby: " + mForceAppStandby);
             mConstants.dump(pw);
             pw.println();
 
+            pw.print("  Foreground uids: [");
+            for (int i = 0; i < mForegroundUids.size(); i++) {
+                if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
+            }
+            pw.println("]");
+            pw.println("  Forced app standby packages: " + mForcedAppStandbyPackages);
             final long nowRTC = System.currentTimeMillis();
             final long nowELAPSED = SystemClock.elapsedRealtime();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -1500,7 +1724,7 @@ class AlarmManagerService extends SystemService {
             pw.print(" set at "); TimeUtils.formatDuration(mLastWakeupSet, nowELAPSED, pw);
             pw.println();
             pw.print("  Num time change events: "); pw.println(mNumTimeChanged);
-            pw.println("  mDeviceIdleUserWhitelist=" + Arrays.toString(mDeviceIdleUserWhitelist));
+            pw.println("  mDeviceIdleSystemWhitelist=" + Arrays.toString(mDeviceIdleSystemWhitelist));
 
             pw.println();
             pw.println("  Next alarm clock information: ");
@@ -1533,6 +1757,19 @@ class AlarmManagerService extends SystemService {
                     dumpAlarmList(pw, b.alarms, "    ", nowELAPSED, nowRTC, sdf);
                 }
             }
+            pw.println();
+            pw.println("  Pending user blocked background alarms: ");
+            boolean blocked = false;
+            for (int i = 0; i < mPendingBackgroundAlarms.size(); i++) {
+                final ArrayList<Alarm> blockedAlarms = mPendingBackgroundAlarms.valueAt(i);
+                if (blockedAlarms != null && blockedAlarms.size() > 0) {
+                    blocked = true;
+                    dumpAlarmList(pw, blockedAlarms, "    ", nowELAPSED, nowRTC, sdf);
+                }
+            }
+            if (!blocked) {
+                pw.println("    none");
+            }
             if (mPendingIdleUntil != null || mPendingWhileIdleAlarms.size() > 0) {
                 pw.println();
                 pw.println("    Idle mode state:");
@@ -1797,9 +2034,24 @@ class AlarmManagerService extends SystemService {
         }
     }
 
-    void setDeviceIdleUserWhitelistImpl(int[] appids) {
+    void setDeviceIdleWhitelistImpl(int[] appids, int[] toArray) {
         synchronized (mLock) {
-            mDeviceIdleUserWhitelist = appids;
+            // appids are sorted, just send pending alarms for any new appids added to the whitelist
+            int i = 0, j = 0;
+            while (i < appids.length) {
+                while (j < toArray.length
+                        && toArray[j] < appids[i]) {
+                    j++;
+                }
+                if (j < toArray.length
+                        && appids[i] != toArray[j]) {
+                    if (DEBUG_BG_LIMIT) {
+                        Slog.d(TAG, "Sending blocked alarms for whitelisted appid " + appids[j]);
+                    }
+                    sendPendingBackgroundAlarmsForAppIdLocked(appids[j]);
+                }
+                i++;
+            }
         }
     }
 
@@ -1979,7 +2231,18 @@ class AlarmManagerService extends SystemService {
                 mPendingWhileIdleAlarms.remove(i);
             }
         }
-
+        for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
+            final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
+            for (int j = alarmsForUid.size() - 1; j >= 0; j--) {
+                if (alarmsForUid.get(j).matches(operation, directReceiver)) {
+                    // Don't set didRemove, since this doesn't impact the scheduled alarms.
+                    alarmsForUid.remove(j);
+                }
+            }
+            if (alarmsForUid.size() == 0) {
+                mPendingBackgroundAlarms.removeAt(i);
+            }
+        }
         if (didRemove) {
             if (DEBUG_BATCH) {
                 Slog.v(TAG, "remove(operation) changed bounds; rebatching");
@@ -2016,7 +2279,17 @@ class AlarmManagerService extends SystemService {
                 mPendingWhileIdleAlarms.remove(i);
             }
         }
-
+        for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i --) {
+            final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
+            for (int j = alarmsForUid.size() - 1; j >= 0; j--) {
+                if (alarmsForUid.get(j).matches(packageName)) {
+                    alarmsForUid.remove(j);
+                }
+            }
+            if (alarmsForUid.size() == 0) {
+                mPendingBackgroundAlarms.removeAt(i);
+            }
+        }
         if (didRemove) {
             if (DEBUG_BATCH) {
                 Slog.v(TAG, "remove(package) changed bounds; rebatching");
@@ -2043,7 +2316,11 @@ class AlarmManagerService extends SystemService {
                 mPendingWhileIdleAlarms.remove(i);
             }
         }
-
+        for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
+            if (mPendingBackgroundAlarms.keyAt(i) == uid) {
+                mPendingBackgroundAlarms.removeAt(i);
+            }
+        }
         if (didRemove) {
             if (DEBUG_BATCH) {
                 Slog.v(TAG, "remove(package) changed bounds; rebatching");
@@ -2070,6 +2347,11 @@ class AlarmManagerService extends SystemService {
                 mPendingWhileIdleAlarms.remove(i);
             }
         }
+        for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
+            if (UserHandle.getUserId(mPendingBackgroundAlarms.keyAt(i)) == userHandle) {
+                mPendingBackgroundAlarms.removeAt(i);
+            }
+        }
         for (int i = mLastAllowWhileIdleDispatch.size() - 1; i >= 0; i--) {
             if (UserHandle.getUserId(mLastAllowWhileIdleDispatch.keyAt(i)) == userHandle) {
                 mLastAllowWhileIdleDispatch.removeAt(i);
@@ -2184,6 +2466,42 @@ class AlarmManagerService extends SystemService {
         }
     }
 
+    private boolean isBackgroundRestrictedLocked(Alarm alarm) {
+        if (!isBackgroundRestrictionEnabled()) {
+            return false;
+        }
+        if (alarm.alarmClock != null) {
+            // Don't block alarm clocks
+            return false;
+        }
+        if (alarm.operation != null
+                && (alarm.operation.isActivity() || alarm.operation.isForegroundService())) {
+            // Don't block starting foreground components
+            return false;
+        }
+        final String sourcePackage =
+                (alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName;
+        final int sourceUid = alarm.creatorUid;
+        return shouldForceStandbyPackageLocked(sourcePackage, sourceUid);
+    }
+
+    private boolean shouldForceStandbyPackageLocked(String pkg, int uid) {
+        if (isUidForeground(uid)) {
+            return false;
+        }
+        if (Arrays.binarySearch(mDeviceIdleSystemWhitelist, UserHandle.getAppId(uid)) >= 0) {
+            return false;
+        }
+        if (mForceAppStandby) {
+            return true;
+        }
+        return mForcedAppStandbyPackages.contains(pkg);
+    }
+
+    private boolean isUidForeground(int uid) {
+        return (uid < Process.FIRST_APPLICATION_UID) || mForegroundUids.get(uid);
+    }
+
     private native long init();
     private native void close(long nativeData);
     private native void set(long nativeData, int type, long seconds, long nanoseconds);
@@ -2239,6 +2557,19 @@ class AlarmManagerService extends SystemService {
                         continue;
                     }
                 }
+                if (isBackgroundRestrictedLocked(alarm)) {
+                    // Alarms with FLAG_WAKE_FROM_IDLE or mPendingIdleUntil alarm are not deferred
+                    if (DEBUG_BG_LIMIT) {
+                        Slog.d(TAG, "Deferring alarm " + alarm + " due to user forced app standby");
+                    }
+                    ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.get(alarm.creatorUid);
+                    if (alarmsForUid == null) {
+                        alarmsForUid = new ArrayList<>();
+                        mPendingBackgroundAlarms.put(alarm.creatorUid, alarmsForUid);
+                    }
+                    alarmsForUid.add(alarm);
+                    continue;
+                }
 
                 alarm.count = 1;
                 triggerList.add(alarm);
@@ -2899,26 +3230,54 @@ class AlarmManagerService extends SystemService {
         }
     }
 
+    class ChargingReceiver extends BroadcastReceiver {
+
+        public ChargingReceiver() {
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_POWER_CONNECTED);
+            filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
+            getContext().registerReceiver(this, filter);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // false as we just want to recheck if FAE is on given battery state.
+            updateForceAppStandby(false);
+        }
+    }
+
+
     final class UidObserver extends IUidObserver.Stub {
         @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
         }
 
         @Override public void onUidGone(int uid, boolean disabled) {
-            if (disabled) {
-                synchronized (mLock) {
+            if (uid < Process.FIRST_APPLICATION_UID) return; // Ignore
+            synchronized (mLock) {
+                if (disabled) {
                     removeForStoppedLocked(uid);
                 }
+                mForegroundUids.delete(uid);
             }
         }
 
         @Override public void onUidActive(int uid) {
+            if (uid < Process.FIRST_APPLICATION_UID) return; // Ignore
+            synchronized (mLock) {
+                if (!mForegroundUids.get(uid)) {
+                    mForegroundUids.put(uid, true);
+                    sendPendingBackgroundAlarmsLocked(uid, null);
+                }
+            }
         }
 
         @Override public void onUidIdle(int uid, boolean disabled) {
-            if (disabled) {
-                synchronized (mLock) {
+            if (uid < Process.FIRST_APPLICATION_UID) return; // Ignore
+            synchronized (mLock) {
+                if (disabled) {
                     removeForStoppedLocked(uid);
                 }
+                mForegroundUids.delete(uid);
             }
         }
 
@@ -2926,6 +3285,28 @@ class AlarmManagerService extends SystemService {
         }
     };
 
+    private final class AppOpsWatcher extends IAppOpsCallback.Stub {
+        @Override
+        public void opChanged(int op, int uid, String packageName) throws RemoteException {
+            synchronized (mLock) {
+                final int mode = mAppOpsService.checkOperation(op, uid, packageName);
+                if (DEBUG_BG_LIMIT) {
+                    Slog.d(TAG,
+                            "Appop changed for " + uid + ", " + packageName + " to " + mode);
+                }
+                final boolean changed;
+                if (mode != AppOpsManager.MODE_ALLOWED) {
+                    changed = mForcedAppStandbyPackages.add(packageName);
+                } else {
+                    changed = mForcedAppStandbyPackages.remove(packageName);
+                }
+                if (changed && mode == AppOpsManager.MODE_ALLOWED) {
+                    sendPendingBackgroundAlarmsLocked(uid, packageName);
+                }
+            }
+        }
+    }
+
     private final BroadcastStats getStatsLocked(PendingIntent pi) {
         String pkg = pi.getCreatorPackage();
         int uid = pi.getCreatorUid();
index 29f8a11..50b8df2 100644 (file)
@@ -52,6 +52,7 @@ import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.Xml;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.os.Zygote;
@@ -87,6 +88,11 @@ public class AppOpsService extends IAppOpsService.Stub {
     static final String TAG = "AppOps";
     static final boolean DEBUG = false;
 
+    private static final int NO_VERSION = -1;
+    /** Increment by one every time and add the corresponding upgrade logic in
+     *  {@link #upgradeLocked(int)} below. The first version was 1 */
+    private static final int CURRENT_VERSION = 1;
+
     // Write at most every 30 minutes.
     static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
 
@@ -112,14 +118,16 @@ public class AppOpsService extends IAppOpsService.Stub {
         }
     };
 
-    private final SparseArray<UidState> mUidStates = new SparseArray<>();
+    @VisibleForTesting
+    final SparseArray<UidState> mUidStates = new SparseArray<>();
 
     /*
      * These are app op restrictions imposed per user from various parties.
      */
     private final ArrayMap<IBinder, ClientRestrictionState> mOpUserRestrictions = new ArrayMap<>();
 
-    private static final class UidState {
+    @VisibleForTesting
+    static final class UidState {
         public final int uid;
         public ArrayMap<String, Ops> pkgOps;
         public SparseIntArray opModes;
@@ -1398,6 +1406,7 @@ public class AppOpsService extends IAppOpsService.Stub {
     }
 
     void readState() {
+        int oldVersion = NO_VERSION;
         synchronized (mFile) {
             synchronized (this) {
                 FileInputStream stream;
@@ -1422,6 +1431,11 @@ public class AppOpsService extends IAppOpsService.Stub {
                         throw new IllegalStateException("no start tag found");
                     }
 
+                    final String versionString = parser.getAttributeValue(null, "v");
+                    if (versionString != null) {
+                        oldVersion = Integer.parseInt(versionString);
+                    }
+
                     int outerDepth = parser.getDepth();
                     while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                             && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
@@ -1464,6 +1478,55 @@ public class AppOpsService extends IAppOpsService.Stub {
                 }
             }
         }
+        synchronized (this) {
+            upgradeLocked(oldVersion);
+        }
+    }
+
+    private void upgradeRunAnyInBackgroundLocked() {
+        for (int i = 0; i < mUidStates.size(); i++) {
+            final UidState uidState = mUidStates.valueAt(i);
+            if (uidState == null) {
+                continue;
+            }
+            if (uidState.opModes != null) {
+                final int idx = uidState.opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
+                if (idx >= 0) {
+                    uidState.opModes.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
+                            uidState.opModes.valueAt(idx));
+                }
+            }
+            if (uidState.pkgOps == null) {
+                continue;
+            }
+            for (int j = 0; j < uidState.pkgOps.size(); j++) {
+                Ops ops = uidState.pkgOps.valueAt(j);
+                if (ops != null) {
+                    final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
+                    if (op != null && op.mode != AppOpsManager.opToDefaultMode(op.op)) {
+                        final Op copy = new Op(op.uid, op.packageName,
+                                AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
+                        copy.mode = op.mode;
+                        ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
+                    }
+                }
+            }
+        }
+    }
+
+    private void upgradeLocked(int oldVersion) {
+        if (oldVersion >= CURRENT_VERSION) {
+            return;
+        }
+        Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
+        switch (oldVersion) {
+            case NO_VERSION:
+                upgradeRunAnyInBackgroundLocked();
+                // fall through
+            case 1:
+                // for future upgrades
+        }
+        scheduleFastWriteLocked();
     }
 
     void readUidOps(XmlPullParser parser) throws NumberFormatException,
@@ -1613,6 +1676,7 @@ public class AppOpsService extends IAppOpsService.Stub {
                 out.setOutput(stream, StandardCharsets.UTF_8.name());
                 out.startDocument(null, true);
                 out.startTag(null, "app-ops");
+                out.attribute(null, "v", String.valueOf(CURRENT_VERSION));
 
                 final int uidStateCount = mUidStates.size();
                 for (int i = 0; i < uidStateCount; i++) {
index 05c7504..b79d0fc 100644 (file)
@@ -65,6 +65,7 @@ import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.KeyValueListParser;
@@ -544,6 +545,8 @@ public class DeviceIdleController extends SystemService
         private static final String KEY_NOTIFICATION_WHITELIST_DURATION =
                 "notification_whitelist_duration";
 
+        private static final String KEY_WHITELISTED_PACKAGES = "whitelist_pkgs";
+
         /**
          * This is the time, after becoming inactive, that we go in to the first
          * light-weight idle mode.
@@ -761,18 +764,20 @@ public class DeviceIdleController extends SystemService
          */
         public long NOTIFICATION_WHITELIST_DURATION;
 
+        public String[] WHITELISTED_PACKAGES = new String[0];
+
         private final ContentResolver mResolver;
         private final boolean mHasWatch;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
 
+        private static final String SETTING = Settings.Global.DEVICE_IDLE_CONSTANTS + "_om";
+
         public Constants(Handler handler, ContentResolver resolver) {
             super(handler);
             mResolver = resolver;
             mHasWatch = getContext().getPackageManager().hasSystemFeature(
                     PackageManager.FEATURE_WATCH);
-            mResolver.registerContentObserver(Settings.Global.getUriFor(
-                    mHasWatch ? Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH
-                              : Settings.Global.DEVICE_IDLE_CONSTANTS),
+            mResolver.registerContentObserver(Settings.Global.getUriFor(SETTING),
                     false, this);
             updateConstants();
         }
@@ -784,14 +789,13 @@ public class DeviceIdleController extends SystemService
 
         private void updateConstants() {
             synchronized (DeviceIdleController.this) {
+                final String config = Settings.Global.getString(mResolver, SETTING);
                 try {
-                    mParser.setString(Settings.Global.getString(mResolver,
-                            mHasWatch ? Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH
-                                      : Settings.Global.DEVICE_IDLE_CONSTANTS));
+                    mParser.setString(config);
                 } catch (IllegalArgumentException e) {
                     // Failed to parse the settings string, log this and move on
                     // with defaults.
-                    Slog.e(TAG, "Bad device idle settings", e);
+                    Slog.e(TAG, "Bad device idle settings: " + config, e);
                 }
 
                 LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getLong(
@@ -853,6 +857,32 @@ public class DeviceIdleController extends SystemService
                         KEY_SMS_TEMP_APP_WHITELIST_DURATION, 20 * 1000L);
                 NOTIFICATION_WHITELIST_DURATION = mParser.getLong(
                         KEY_NOTIFICATION_WHITELIST_DURATION, 30 * 1000L);
+
+
+                WHITELISTED_PACKAGES = mParser.getString(KEY_WHITELISTED_PACKAGES, "").split(",");
+
+                boolean changed = false;
+                for (String name : WHITELISTED_PACKAGES) {
+                    if (TextUtils.isEmpty(name)) {
+                        continue;
+                    }
+
+                    try {
+                        ApplicationInfo ai = getContext().getPackageManager()
+                                .getApplicationInfo(name, PackageManager.MATCH_ANY_USER);
+                        if (mPowerSaveWhitelistUserApps.put(name, UserHandle.getAppId(ai.uid))
+                                == null) {
+                            changed = true;
+                        }
+                    } catch (NameNotFoundException e) {
+                        // Ignore
+                    }
+                }
+                if (changed) {
+                    reportPowerSaveWhitelistChangedLocked();
+                    updateWhitelistAppIdsLocked();
+                    writeConfigFileLocked();
+                }
             }
         }
 
@@ -960,6 +990,9 @@ public class DeviceIdleController extends SystemService
             pw.print("    "); pw.print(KEY_NOTIFICATION_WHITELIST_DURATION); pw.print("=");
             TimeUtils.formatDuration(NOTIFICATION_WHITELIST_DURATION, pw);
             pw.println();
+
+            pw.print("    "); pw.print(KEY_WHITELISTED_PACKAGES); pw.print("=");
+            pw.println(Arrays.toString(WHITELISTED_PACKAGES));
         }
     }
 
@@ -1458,9 +1491,9 @@ public class DeviceIdleController extends SystemService
                 filter.addAction(Intent.ACTION_SCREEN_ON);
                 getContext().registerReceiver(mInteractivityReceiver, filter);
 
-                mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
+                mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistUserAppIdArray, mPowerSaveWhitelistAllAppIdArray);
                 mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
-                mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
+                mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray, mPowerSaveWhitelistAllAppIdArray);
 
                 updateInteractivityLocked();
             }
@@ -2389,7 +2422,7 @@ public class DeviceIdleController extends SystemService
                 Slog.d(TAG, "Setting activity manager whitelist to "
                         + Arrays.toString(mPowerSaveWhitelistAllAppIdArray));
             }
-            mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
+            mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistUserAppIdArray, mPowerSaveWhitelistAllAppIdArray);
         }
         if (mLocalPowerManager != null) {
             if (DEBUG) {
@@ -2403,7 +2436,7 @@ public class DeviceIdleController extends SystemService
                 Slog.d(TAG, "Setting alarm whitelist to "
                         + Arrays.toString(mPowerSaveWhitelistUserAppIdArray));
             }
-            mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
+            mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray, mPowerSaveWhitelistAllAppIdArray);
         }
     }
 
index 1520b96..6bfb060 100644 (file)
@@ -308,6 +308,7 @@ import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -407,6 +408,7 @@ import com.android.server.am.ActivityStack.ActivityState;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.power.BatterySaverPolicy.ServiceType;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.PinnedStackWindowController;
@@ -1210,6 +1212,8 @@ public class ActivityManagerService extends IActivityManager.Stub
      */
     int[] mDeviceIdleWhitelist = new int[0];
 
+    int[] mDeviceIdleUserWhitelist = new int[0];
+
     /**
      * Set of app ids that are temporarily allowed to escape bg check due to high-pri message
      */
@@ -7882,6 +7886,15 @@ public class ActivityManagerService extends IActivityManager.Stub
     }
 
     @Override
+    public boolean isIntentSenderAForegroundService(IIntentSender pendingResult) {
+        if (pendingResult instanceof PendingIntentRecord) {
+            final PendingIntentRecord res = (PendingIntentRecord) pendingResult;
+            return res.key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE;
+        }
+        return false;
+    }
+
+    @Override
     public Intent getIntentForIntentSender(IIntentSender pendingResult) {
         enforceCallingPermission(Manifest.permission.GET_INTENT_SENDER_INTENT,
                 "getIntentForIntentSender()");
@@ -8544,6 +8557,7 @@ public class ActivityManagerService extends IActivityManager.Stub
         }
     }
 
+//xxx
     // Unified app-op and target sdk check
     int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
         // Apps that target O+ are always subject to background check
@@ -8553,6 +8567,14 @@ public class ActivityManagerService extends IActivityManager.Stub
             }
             return ActivityManager.APP_START_MODE_DELAYED_RIGID;
         }
+        if (uid >= Process.FIRST_APPLICATION_UID && mForceAppStandby) {
+
+            // This is used for implicit broadcasts. User-whitelist should still affect it.
+            if (!isOnDeviceIdleUserWhitelistLocked(uid)) {
+                return ActivityManager.APP_START_MODE_DELAYED;
+            }
+        }
+
         // ...and legacy apps get an AppOp check
         int appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
                 uid, packageName);
@@ -8667,6 +8689,11 @@ public class ActivityManagerService extends IActivityManager.Stub
                 || mPendingTempWhitelist.indexOfKey(uid) >= 0;
     }
 
+    boolean isOnDeviceIdleUserWhitelistLocked(int uid) {
+        final int appId = UserHandle.getAppId(uid);
+        return Arrays.binarySearch(mDeviceIdleUserWhitelist, appId) >= 0;
+    }
+
     private ProviderInfo getProviderInfoLocked(String authority, int userHandle, int pmFlags) {
         ProviderInfo pi = null;
         ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userHandle);
@@ -14168,6 +14195,26 @@ public class ActivityManagerService extends IActivityManager.Stub
             mSystemReady = true;
         }
 
+        final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+        if (pmi != null) {
+            pmi.registerLowPowerModeObserver(
+                    new PowerManagerInternal.LowPowerModeListener() {
+                        @Override
+                        public int getServiceType() {
+                            return ServiceType.FORCE_APPS_STANDBY;
+                        }
+
+                        @Override
+                        public void onLowPowerModeChanged(PowerSaveState result) {
+                            updateForceAppStandby(result.batterySaverEnabled);
+                        }
+                    });
+            updateForceAppStandby(
+                    pmi.getLowPowerState(ServiceType.FORCE_APPS_STANDBY).batterySaverEnabled);
+        } else {
+            Slog.wtf(TAG, "PowerManagerInternal not found.");
+        }
+
         try {
             sTheRealBuildSerial = IDeviceIdentifiersPolicyService.Stub.asInterface(
                     ServiceManager.getService(Context.DEVICE_IDENTIFIERS_SERVICE))
@@ -14330,6 +14377,22 @@ public class ActivityManagerService extends IActivityManager.Stub
         }
     }
 
+    boolean mForceAppStandby;
+
+    void updateForceAppStandby(boolean enabled) {
+        synchronized (this) {
+            if (mForceAppStandby != enabled) {
+                mForceAppStandby = enabled;
+
+                if (mForceAppStandby) {
+                    Slog.w(TAG, "Forcing app standby.");
+
+                    doStopUidForIdleUidsLocked();
+                }
+            }
+        }
+    }
+
     void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) {
         synchronized (this) {
             mAppErrors.killAppAtUserRequestLocked(app, fromDialog);
@@ -15902,6 +15965,7 @@ public class ActivityManagerService extends IActivityManager.Stub
                 }
             }
             pw.println("  mDeviceIdleWhitelist=" + Arrays.toString(mDeviceIdleWhitelist));
+            pw.println("  mDeviceIdleUserWhitelist=" + Arrays.toString(mDeviceIdleUserWhitelist));
             pw.println("  mDeviceIdleTempWhitelist=" + Arrays.toString(mDeviceIdleTempWhitelist));
             if (mPendingTempWhitelist.size() > 0) {
                 pw.println("  mPendingTempWhitelist:");
@@ -23216,6 +23280,21 @@ public class ActivityManagerService extends IActivityManager.Stub
         }
     }
 
+    void doStopUidForIdleUidsLocked() {
+        final int size = mActiveUids.size();
+        for (int i = 0; i < size; i++) {
+            final int uid = mActiveUids.keyAt(i);
+            if (uid < Process.FIRST_APPLICATION_UID) {
+                continue;
+            }
+            final UidRecord uidRec = mActiveUids.valueAt(i);
+            if (!uidRec.idle) {
+                continue;
+            }
+            doStopUidLocked(uidRec.uid, uidRec);
+        }
+    }
+
     final void doStopUidLocked(int uid, final UidRecord uidRec) {
         mServices.stopInBackgroundLocked(uid);
         enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
@@ -24016,9 +24095,10 @@ public class ActivityManagerService extends IActivityManager.Stub
         }
 
         @Override
-        public void setDeviceIdleWhitelist(int[] appids) {
+        public void setDeviceIdleWhitelist(int[] userAppids, int[] allAppids) {
             synchronized (ActivityManagerService.this) {
-                mDeviceIdleWhitelist = appids;
+                mDeviceIdleUserWhitelist = userAppids;
+                mDeviceIdleWhitelist = allAppids;
             }
         }
 
index 4dab32c..c4197c9 100644 (file)
@@ -50,11 +50,14 @@ import android.content.pm.ServiceInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
 import android.os.Process;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -80,6 +83,7 @@ import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.job.JobStore.JobStatusFunctor;
 import com.android.server.job.controllers.AppIdleController;
+import com.android.server.job.controllers.BackgroundJobsController;
 import com.android.server.job.controllers.BatteryController;
 import com.android.server.job.controllers.ConnectivityController;
 import com.android.server.job.controllers.ContentObserverController;
@@ -89,6 +93,7 @@ import com.android.server.job.controllers.JobStatus;
 import com.android.server.job.controllers.StateController;
 import com.android.server.job.controllers.StorageController;
 import com.android.server.job.controllers.TimeController;
+import com.android.server.power.BatterySaverPolicy.ServiceType;
 
 import libcore.util.EmptyArray;
 
@@ -116,6 +121,9 @@ public final class JobSchedulerService extends com.android.server.SystemService
     /** The maximum number of jobs that we allow an unprivileged app to schedule */
     private static final int MAX_JOBS_PER_APP = 100;
 
+    private static final String JOB_SCHEDULER_EXPERIMENT_KEY =
+            Settings.Global.JOB_SCHEDULER_CONSTANTS + "_om";
+
 
     /** Global local for all job scheduler state. */
     final Object mLock = new Object();
@@ -140,6 +148,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
     BatteryController mBatteryController;
     /** Need direct access to this for testing. */
     StorageController mStorageController;
+    /** Need directly for sending uid state changes */
+    private BackgroundJobsController mBackgroundJobsController;
     /**
      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
      * when ready to execute them.
@@ -199,6 +209,10 @@ public final class JobSchedulerService extends com.android.server.SystemService
      */
     int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
 
+    boolean mForceAppStandby;
+    boolean mForegroundAppExperiment;
+    ChargingReceiver mChargingReceiver;
+
     /**
      * All times are in milliseconds. These constants are kept synchronized with the system
      * global Settings. Any access to this class or its fields should be done while
@@ -225,6 +239,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
         private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count";
         private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time";
         private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time";
+        private static final String KEY_BG_JOBS_RESTRICTED = "bg_jobs_restricted";
+        private static final String KEY_FOREGROUND_ONLY_EXPERIMENT = "foreground_only";
 
         private static final int DEFAULT_MIN_IDLE_COUNT = 1;
         private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
@@ -240,6 +256,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
         private static final int DEFAULT_BG_MODERATE_JOB_COUNT = 4;
         private static final int DEFAULT_BG_LOW_JOB_COUNT = 1;
         private static final int DEFAULT_BG_CRITICAL_JOB_COUNT = 1;
+        private static final boolean DEFAULT_BG_JOBS_RESTRICTED = false;
         private static final int DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT = Integer.MAX_VALUE;
         private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE;
         private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
@@ -333,6 +350,11 @@ public final class JobSchedulerService extends com.android.server.SystemService
          */
         long MIN_EXP_BACKOFF_TIME = DEFAULT_MIN_EXP_BACKOFF_TIME;
 
+        /**
+         * Runtime switch for throttling background jobs
+         */
+        boolean BACKGROUND_JOBS_RESTRICTED = DEFAULT_BG_JOBS_RESTRICTED;
+
         private ContentResolver mResolver;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
 
@@ -344,6 +366,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
             mResolver = resolver;
             mResolver.registerContentObserver(Settings.Global.getUriFor(
                     Settings.Global.JOB_SCHEDULER_CONSTANTS), false, this);
+            mResolver.registerContentObserver(Settings.Global.getUriFor(
+                    JOB_SCHEDULER_EXPERIMENT_KEY), false, this);
             updateConstants();
         }
 
@@ -411,6 +435,24 @@ public final class JobSchedulerService extends com.android.server.SystemService
                         DEFAULT_MIN_LINEAR_BACKOFF_TIME);
                 MIN_EXP_BACKOFF_TIME = mParser.getLong(KEY_MIN_EXP_BACKOFF_TIME,
                         DEFAULT_MIN_EXP_BACKOFF_TIME);
+                final boolean bgJobsRestricted = mParser.getBoolean(KEY_BG_JOBS_RESTRICTED,
+                        DEFAULT_BG_JOBS_RESTRICTED);
+                if (bgJobsRestricted != BACKGROUND_JOBS_RESTRICTED) {
+                    BACKGROUND_JOBS_RESTRICTED = bgJobsRestricted;
+                    maybeEnableBackgroundRestriction();
+                }
+
+                String expSettings =
+                        Settings.Global.getString(mResolver, JOB_SCHEDULER_EXPERIMENT_KEY);
+                try {
+                    Slog.d(TAG, "Parsing: " + expSettings);
+                    mParser.setString(expSettings);
+                } catch (IllegalArgumentException e) {
+                    Slog.e(TAG, "Bad job scheduler experiment settings.");
+                }
+                mForegroundAppExperiment =
+                        mParser.getBoolean(KEY_FOREGROUND_ONLY_EXPERIMENT, false);
+                updateForceAppStandby(mForegroundAppExperiment);
             }
         }
 
@@ -470,6 +512,9 @@ public final class JobSchedulerService extends com.android.server.SystemService
 
             pw.print("    "); pw.print(KEY_MIN_EXP_BACKOFF_TIME); pw.print("=");
             pw.print(MIN_EXP_BACKOFF_TIME); pw.println();
+
+            pw.print("    "); pw.print(KEY_BG_JOBS_RESTRICTED); pw.print("=");
+            pw.print(BACKGROUND_JOBS_RESTRICTED); pw.println();
         }
     }
 
@@ -611,15 +656,24 @@ public final class JobSchedulerService extends com.android.server.SystemService
             if (disabled) {
                 cancelJobsForUid(uid, "uid gone");
             }
+            synchronized (mLock) {
+                mBackgroundJobsController.setUidActiveLocked(uid, false);
+            }
         }
 
         @Override public void onUidActive(int uid) throws RemoteException {
+            synchronized (mLock) {
+                mBackgroundJobsController.setUidActiveLocked(uid, true);
+            }
         }
 
         @Override public void onUidIdle(int uid, boolean disabled) {
             if (disabled) {
                 cancelJobsForUid(uid, "app uid idle");
             }
+            synchronized (mLock) {
+                mBackgroundJobsController.setUidActiveLocked(uid, false);
+            }
         }
 
         @Override public void onUidCachedChanged(int uid, boolean cached) {
@@ -923,6 +977,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
         mControllers.add(mBatteryController);
         mStorageController = StorageController.get(this);
         mControllers.add(mStorageController);
+        mBackgroundJobsController = BackgroundJobsController.get(this);
+        mControllers.add(mBackgroundJobsController);
         mControllers.add(AppIdleController.get(this));
         mControllers.add(ContentObserverController.get(this));
         mControllers.add(DeviceIdleJobsController.get(this));
@@ -1003,13 +1059,35 @@ public final class JobSchedulerService extends com.android.server.SystemService
             try {
                 ActivityManager.getService().registerUidObserver(mUidObserver,
                         ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
-                        | ActivityManager.UID_OBSERVER_IDLE, ActivityManager.PROCESS_STATE_UNKNOWN,
-                        null);
+                        | ActivityManager.UID_OBSERVER_IDLE | ActivityManager.UID_OBSERVER_ACTIVE,
+                        ActivityManager.PROCESS_STATE_UNKNOWN, null);
             } catch (RemoteException e) {
                 // ignored; both services live in system_server
             }
+
+            final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+            if (pmi != null) {
+                pmi.registerLowPowerModeObserver(
+                        new PowerManagerInternal.LowPowerModeListener() {
+                            @Override
+                            public int getServiceType() {
+                                return ServiceType.FORCE_APPS_STANDBY;
+                            }
+
+                            @Override
+                            public void onLowPowerModeChanged(PowerSaveState result) {
+                                updateForceAppStandby(result.batterySaverEnabled);
+                            }
+                        });
+                updateForceAppStandby(
+                        pmi.getLowPowerState(ServiceType.FORCE_APPS_STANDBY).batterySaverEnabled);
+            } else {
+                Slog.wtf(TAG, "PowerManagerInternal not found.");
+            }
+
             // Remove any jobs that are not associated with any of the current users.
             cancelJobsForNonExistentUsers();
+            mChargingReceiver = new ChargingReceiver();
         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
             synchronized (mLock) {
                 // Let's go!
@@ -1040,6 +1118,23 @@ public final class JobSchedulerService extends com.android.server.SystemService
         }
     }
 
+    void updateForceAppStandby(boolean enabled) {
+        synchronized (this) {
+            int status = ((BatteryManager) getContext().getSystemService(Context.BATTERY_SERVICE))
+                    .getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS);
+            boolean charging = status != BatteryManager.BATTERY_STATUS_DISCHARGING;
+            mForceAppStandby = mForegroundAppExperiment ? !charging : enabled;
+            Slog.d(TAG, "Force app standby is: " + mForceAppStandby + " charging: " + charging
+                    + " FAE: " + mForegroundAppExperiment + " status: " + status);
+            maybeEnableBackgroundRestriction();
+        }
+    }
+
+    void maybeEnableBackgroundRestriction() {
+        mBackgroundJobsController.enableRestrictionsLocked(mConstants.BACKGROUND_JOBS_RESTRICTED,
+                mForceAppStandby);
+    }
+
     /**
      * Called when we have a job status object that we need to insert in our
      * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
@@ -2318,6 +2413,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
         final long nowElapsed = SystemClock.elapsedRealtime();
         final long nowUptime = SystemClock.uptimeMillis();
         synchronized (mLock) {
+            pw.println("ForceAppStandby: " + mForceAppStandby);
             mConstants.dump(pw);
             pw.println();
             pw.println("Started users: " + Arrays.toString(mStartedUsers));
@@ -2475,4 +2571,20 @@ public final class JobSchedulerService extends com.android.server.SystemService
         }
         pw.println();
     }
+
+    class ChargingReceiver extends BroadcastReceiver {
+
+        public ChargingReceiver() {
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_POWER_CONNECTED);
+            filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
+            getContext().registerReceiver(this, filter);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // false as we just want to recheck if FAE is on given battery state.
+            updateForceAppStandby(false);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
new file mode 100644 (file)
index 0000000..968fa95
--- /dev/null
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.job.controllers;
+
+import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.IDeviceIdleController;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobStore;
+
+import java.io.PrintWriter;
+
+public final class BackgroundJobsController extends StateController {
+
+    private static final String LOG_TAG = "BackgroundJobsController";
+    private static final boolean DEBUG = JobSchedulerService.DEBUG;
+
+    // Singleton factory
+    private static final Object sCreationLock = new Object();
+    private static volatile BackgroundJobsController sController;
+
+    /* Runtime switch to keep feature under wraps */
+    private boolean mEnableSwitch;
+
+    /** Enable regardless of what app ops says */
+    private boolean mForceEnableSwitch;
+    private final JobSchedulerService mJobSchedulerService;
+    private final IAppOpsService mAppOpsService;
+    private final IDeviceIdleController mDeviceIdleController;
+
+    private final SparseBooleanArray mForegroundUids;
+    private int[] mPowerWhitelistedAppIds;
+    private int[] mTempWhitelistedAppIds;
+    /**
+     * Only tracks jobs for which source package app op RUN_ANY_IN_BACKGROUND is not ALLOWED.
+     * Maps jobs to the sourceUid unlike the global {@link JobSchedulerService#mJobs JobStore}
+     * which uses callingUid.
+     */
+    private SparseArray<ArraySet<JobStatus>> mTrackedJobs;
+
+    public static BackgroundJobsController get(JobSchedulerService service) {
+        synchronized (sCreationLock) {
+            if (sController == null) {
+                sController = new BackgroundJobsController(service, service.getContext(),
+                        service.getLock());
+            }
+            return sController;
+        }
+    }
+
+    private BroadcastReceiver mDozeWhitelistReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (mLock) {
+                try {
+                    switch (intent.getAction()) {
+                        case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
+                            mPowerWhitelistedAppIds = mDeviceIdleController.getAppIdWhitelist();
+                            break;
+                        case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
+                            mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
+                            break;
+                    }
+                } catch (RemoteException rexc) {
+                    Slog.e(LOG_TAG, "Device idle controller not reachable");
+                }
+                if (checkAllTrackedJobsLocked()) {
+                    mStateChangedListener.onControllerStateChanged();
+                }
+            }
+        }
+    };
+
+    private BackgroundJobsController(JobSchedulerService service, Context context, Object lock) {
+        super(service, context, lock);
+        mJobSchedulerService = service;
+        mAppOpsService = IAppOpsService.Stub.asInterface(
+                ServiceManager.getService(Context.APP_OPS_SERVICE));
+        mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
+                ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+
+        mForegroundUids = new SparseBooleanArray();
+        mTrackedJobs = new SparseArray<>();
+        try {
+            mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
+                    new AppOpsWatcher());
+            mPowerWhitelistedAppIds = mDeviceIdleController.getAppIdWhitelist();
+            mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
+        } catch (RemoteException rexc) {
+            // Shouldn't happen as they are in the same process.
+            Slog.e(LOG_TAG, "AppOps or DeviceIdle service not reachable", rexc);
+        }
+        IntentFilter powerWhitelistFilter = new IntentFilter();
+        powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
+        powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
+        context.registerReceiverAsUser(mDozeWhitelistReceiver, UserHandle.ALL, powerWhitelistFilter,
+                null, null);
+
+        mForceEnableSwitch = false;
+        mEnableSwitch = false;
+    }
+
+    @Override
+    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
+        final int uid = jobStatus.getSourceUid();
+        final String packageName = jobStatus.getSourcePackageName();
+        try {
+            final boolean restrict = mForceEnableSwitch || (mAppOpsService.checkOperation(
+                    AppOpsManager.OP_RUN_IN_BACKGROUND,
+                    uid, packageName) != AppOpsManager.MODE_ALLOWED);
+            if (!restrict) {
+                jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true);
+                return;
+            }
+        } catch (RemoteException rexc) {
+            Slog.e(LOG_TAG, "Cannot reach app ops service", rexc);
+        }
+        jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRunJobLocked(uid));
+        startTrackingJobLocked(jobStatus);
+    }
+
+    @Override
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
+            boolean forUpdate) {
+        stopTrackingJobLocked(jobStatus);
+    }
+
+    /* Called by JobSchedulerService to report uid state changes between active and idle */
+    public void setUidActiveLocked(int uid, boolean active) {
+        if (uid < Process.FIRST_APPLICATION_UID) return; // Ignore
+
+        final boolean changed = (active != mForegroundUids.get(uid));
+        if (!changed) {
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(LOG_TAG, "uid " + uid + " going to " + (active ? "fg" : "bg"));
+        }
+        if (active) {
+            mForegroundUids.put(uid, true);
+        } else {
+            mForegroundUids.delete(uid);
+        }
+        if (checkTrackedJobsForUidLocked(uid)) {
+            mStateChangedListener.onControllerStateChanged();
+        }
+    }
+
+    @Override
+    public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
+        pw.println("Background restrictions: global switch = " + mEnableSwitch
+                + " force = " + mEnableSwitch);
+        pw.print("Foreground uids: [");
+        for (int i = 0; i < mForegroundUids.size(); i++) {
+            if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
+        }
+        pw.println("]");
+        mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
+            @Override
+            public void process(JobStatus jobStatus) {
+                if (!jobStatus.shouldDump(filterUid)) {
+                    return;
+                }
+                final int uid = jobStatus.getSourceUid();
+                pw.print("  #");
+                jobStatus.printUniqueId(pw);
+                pw.print(" from ");
+                UserHandle.formatUid(pw, uid);
+                pw.print(mForegroundUids.get(uid) ? " foreground" : " background");
+                if (isWhitelistedLocked(uid)) {
+                    pw.print(", whitelisted");
+                }
+                pw.print(": ");
+                pw.print(jobStatus.getSourcePackageName());
+                pw.print(" [background restrictions");
+                final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
+                pw.print(jobsForUid != null && jobsForUid.contains(jobStatus) ? " on]" : " off]");
+                if ((jobStatus.satisfiedConstraints
+                        & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
+                    pw.println(" RUNNABLE");
+                } else {
+                    pw.println(" WAITING");
+                }
+            }
+        });
+    }
+
+    public void enableRestrictionsLocked(boolean enable, boolean forceEnabled) {
+        mEnableSwitch = enable;
+        mForceEnableSwitch = forceEnabled;
+        Slog.d(LOG_TAG, "Background jobs restrictions switch changed to " + mEnableSwitch
+                 + " force " + mForceEnableSwitch);
+        if (checkAllTrackedJobsLocked()) {
+            mStateChangedListener.onControllerStateChanged();
+        }
+    }
+
+    void startTrackingJobLocked(JobStatus jobStatus) {
+        final int uid = jobStatus.getSourceUid();
+        ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
+        if (jobsForUid == null) {
+            jobsForUid = new ArraySet<>();
+            mTrackedJobs.put(uid, jobsForUid);
+        }
+        jobsForUid.add(jobStatus);
+    }
+
+    void stopTrackingJobLocked(JobStatus jobStatus) {
+        final int uid = jobStatus.getSourceUid();
+        ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
+        if (jobsForUid != null) {
+            jobsForUid.remove(jobStatus);
+        }
+    }
+
+    boolean checkAllTrackedJobsLocked() {
+        boolean changed = false;
+        for (int i = 0; i < mTrackedJobs.size(); i++) {
+            changed |= checkTrackedJobsForUidLocked(mTrackedJobs.keyAt(i));
+        }
+        return changed;
+    }
+
+    private boolean checkTrackedJobsForUidLocked(int uid) {
+        final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
+        boolean changed = false;
+        if (jobsForUid != null) {
+            for (int i = 0; i < jobsForUid.size(); i++) {
+                JobStatus jobStatus = jobsForUid.valueAt(i);
+                changed |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(
+                        canRunJobLocked(uid));
+            }
+        }
+        return changed;
+    }
+
+    boolean isWhitelistedLocked(int uid) {
+        int appId = UserHandle.getAppId(uid);
+        return ArrayUtils.contains(mTempWhitelistedAppIds, appId)
+                || ArrayUtils.contains(mPowerWhitelistedAppIds, appId);
+    }
+
+    boolean canRunJobLocked(int uid) {
+        return !(mEnableSwitch || mForceEnableSwitch)
+                || isUidForeground(uid) || isWhitelistedLocked(uid);
+    }
+
+    private boolean isUidForeground(int uid) {
+        return (uid < Process.FIRST_APPLICATION_UID) || mForegroundUids.get(uid);
+    }
+
+    private final class AppOpsWatcher extends IAppOpsCallback.Stub {
+        @Override
+        public void opChanged(int op, int uid, String packageName) throws RemoteException {
+            synchronized (mLock) {
+                final int mode = mAppOpsService.checkOperation(op, uid, packageName);
+                if (DEBUG) {
+                    Slog.d(LOG_TAG,
+                            "Appop changed for " + uid + ", " + packageName + " to " + mode);
+                }
+                final boolean shouldTrack = mForceEnableSwitch
+                        || (mode != AppOpsManager.MODE_ALLOWED);
+                UpdateTrackedJobsFunc updateTrackedJobs = new UpdateTrackedJobsFunc(uid,
+                        packageName, shouldTrack);
+                mJobSchedulerService.getJobStore().forEachJob(updateTrackedJobs);
+                if (updateTrackedJobs.mChanged) {
+                    mStateChangedListener.onControllerStateChanged();
+                }
+            }
+        }
+    }
+
+    private final class UpdateTrackedJobsFunc implements JobStore.JobStatusFunctor {
+        private final String mPackageName;
+        private final int mUid;
+        private final boolean mShouldTrack;
+        private boolean mChanged = false;
+
+        UpdateTrackedJobsFunc(int uid, String packageName, boolean shouldTrack) {
+            mUid = uid;
+            mPackageName = packageName;
+            mShouldTrack = shouldTrack;
+        }
+
+        @Override
+        public void process(JobStatus jobStatus) {
+            final String packageName = jobStatus.getSourcePackageName();
+            final int uid = jobStatus.getSourceUid();
+            if (mUid != uid || !mPackageName.equals(packageName)) {
+                return;
+            }
+            if (mShouldTrack) {
+                mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(
+                        canRunJobLocked(uid));
+                startTrackingJobLocked(jobStatus);
+            } else {
+                mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true);
+                stopTrackingJobLocked(jobStatus);
+            }
+        }
+    }
+}
index 303b000..23caa8c 100644 (file)
@@ -70,6 +70,7 @@ public final class JobStatus {
     static final int CONSTRAINT_DEVICE_NOT_DOZING = 1<<25;
     static final int CONSTRAINT_NOT_ROAMING = 1<<24;
     static final int CONSTRAINT_METERED = 1<<23;
+    static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1<<22;
 
     static final int CONNECTIVITY_MASK =
             CONSTRAINT_UNMETERED | CONSTRAINT_CONNECTIVITY |
@@ -707,6 +708,10 @@ public final class JobStatus {
         return setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state);
     }
 
+    boolean setBackgroundNotRestrictedConstraintSatisfied(boolean state) {
+        return setConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED, state);
+    }
+
     boolean setConstraintSatisfied(int constraint, boolean state) {
         boolean old = (satisfiedConstraints&constraint) != 0;
         if (old == state) {
@@ -758,12 +763,16 @@ public final class JobStatus {
         // satisfied).
         // AppNotIdle implicit constraint must be satisfied
         // DeviceNotDozing implicit constraint must be satisfied
+        // NotRestrictedInBackground implicit constraint must be satisfied
         final boolean deadlineSatisfied = (!job.isPeriodic() && hasDeadlineConstraint()
                 && (satisfiedConstraints & CONSTRAINT_DEADLINE) != 0);
         final boolean notIdle = (satisfiedConstraints & CONSTRAINT_APP_NOT_IDLE) != 0;
         final boolean notDozing = (satisfiedConstraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0
                 || (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
-        return (isConstraintsSatisfied() || deadlineSatisfied) && notIdle && notDozing;
+        final boolean notRestrictedInBg =
+                (satisfiedConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0;
+        return (isConstraintsSatisfied() || deadlineSatisfied) && notIdle && notDozing
+                && notRestrictedInBg;
     }
 
     static final int CONSTRAINTS_OF_INTEREST =
index 74ddfc5..ca7dad7 100644 (file)
@@ -63,6 +63,7 @@ import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.provider.Telephony.Carriers;
@@ -565,6 +566,7 @@ public class GnssLocationProvider implements LocationProviderInterface {
         final PowerSaveState result =
                 mPowerManager.getPowerSaveState(ServiceType.GPS);
         switch (result.gpsMode) {
+            case BatterySaverPolicy.GPS_MODE_REALLY_DISABLED_WHEN_SCREEN_OFF:
             case BatterySaverPolicy.GPS_MODE_DISABLED_WHEN_SCREEN_OFF:
                 // If we are in battery saver mode and the screen is off, disable GPS.
                 disableGps |= result.batterySaverEnabled && !mPowerManager.isInteractive();
@@ -573,6 +575,28 @@ public class GnssLocationProvider implements LocationProviderInterface {
         if (disableGps != mDisableGps) {
             mDisableGps = disableGps;
             updateRequirements();
+
+            updateLocationModeForReallyDisabledWhenScreenOff(result.gpsMode);
+        }
+    }
+
+    boolean mLocationDisabledForPowerSaving;
+
+    private void updateLocationModeForReallyDisabledWhenScreenOff(int gpsMode) {
+        final boolean disableLocation = mDisableGps
+                && (gpsMode == BatterySaverPolicy.GPS_MODE_REALLY_DISABLED_WHEN_SCREEN_OFF);
+
+// TODO Secondary user, intent sent in LocationSettingsBase
+        if (disableLocation != mLocationDisabledForPowerSaving) {
+Log.w("XXX:GnssLP", "mLocationDisabledForPowerSaving=" + mLocationDisabledForPowerSaving + " on u" + UserHandle.myUserId());
+            mLocationDisabledForPowerSaving = disableLocation;
+            if (disableLocation) {
+                Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.LOCATION_MODE,
+                        android.provider.Settings.Secure.LOCATION_MODE_OFF);
+            } else {
+                Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.LOCATION_MODE,
+                        android.provider.Settings.Secure.LOCATION_MODE_PREVIOUS);
+            }
         }
     }
 
@@ -2547,6 +2571,8 @@ public class GnssLocationProvider implements LocationProviderInterface {
         s.append("  mStarted=").append(mStarted).append('\n');
         s.append("  mFixInterval=").append(mFixInterval).append('\n');
         s.append("  mDisableGps (battery saver mode)=").append(mDisableGps).append('\n');
+        s.append("  mDisableLocation (battery saver mode)=").append(mLocationDisabledForPowerSaving)
+                .append('\n');
         s.append("  mEngineCapabilities=0x").append(Integer.toHexString(mEngineCapabilities));
         s.append(" ( ");
         if (hasCapability(GPS_CAPABILITY_SCHEDULING)) s.append("SCHEDULING ");
index 1781d8c..530f441 100644 (file)
 package com.android.server.power;
 
 import android.annotation.IntDef;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.PowerManager;
+import android.os.UserHandle;
 import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.text.TextUtils;
 import android.util.KeyValueListParser;
+import android.util.Pair;
 import android.util.Slog;
 import android.os.PowerSaveState;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.IoThread;
 
+import libcore.io.IoUtils;
+
+import java.io.FileWriter;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Class to decide whether to turn on battery saver mode for specific service
@@ -44,7 +63,11 @@ public class BatterySaverPolicy extends ContentObserver {
             ServiceType.SCREEN_BRIGHTNESS,
             ServiceType.SOUND,
             ServiceType.BATTERY_STATS,
-            ServiceType.DATA_SAVER})
+            ServiceType.DATA_SAVER,
+            ServiceType.FORCE_APPS_STANDBY,
+            ServiceType.LOWER_MAX_FREQUENCY,
+            ServiceType.AOD,
+    })
     public @interface ServiceType {
         int NULL = 0;
         int GPS = 1;
@@ -57,15 +80,23 @@ public class BatterySaverPolicy extends ContentObserver {
         int SOUND = 8;
         int BATTERY_STATS = 9;
         int DATA_SAVER = 10;
+
+        int FORCE_APPS_STANDBY = 11;
+        int LOWER_MAX_FREQUENCY = 13;
+        int AOD = 15;
     }
 
     private static final String TAG = "BatterySaverPolicy";
 
     // Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode.
     public static final int GPS_MODE_NO_CHANGE = 0;
+
     // Value of batterySaverGpsMode such that GPS is disabled when battery saver mode
     // is enabled and the screen is off.
     public static final int GPS_MODE_DISABLED_WHEN_SCREEN_OFF = 1;
+
+    public static final int GPS_MODE_REALLY_DISABLED_WHEN_SCREEN_OFF = 2;
+
     // Secure setting for GPS behavior when battery saver mode is on.
     public static final String SECURE_KEY_GPS_MODE = "batterySaverGpsMode";
 
@@ -79,6 +110,23 @@ public class BatterySaverPolicy extends ContentObserver {
     private static final String KEY_ADJUST_BRIGHTNESS_FACTOR = "adjust_brightness_factor";
     private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred";
     private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred";
+    private static final String KEY_FORCE_APPS_STANDBY_ENABLED = "force_apps_standby_enabled";
+    private static final String KEY_POWER_HINT_BLOCK = "power_hint_block";
+    private static final String KEY_RED_BAR_ENABLED = "red_bar_enabled";
+    private static final String KEY_MAX_FILE_WRITE_RETRIES = "max_file_write_retries";
+
+    private static final String KEY_FILE_OVERRIDE_PREFIX = "file:";
+    private static final String KEY_SECURE_SETTINGS_OVERRIDE_PREFIX = "secure:";
+    private static final String KEY_GLOBAL_SETTINGS_OVERRIDE_PREFIX = "global:";
+    private static final String KEY_SYSTEM_SETTINGS_OVERRIDE_PREFIX = "system:";
+
+    private static final String KEY_FILE_OVERRIDE_PREFIX_NS = "file-ns:";
+    private static final String KEY_SECURE_SETTINGS_OVERRIDE_PREFIX_NS = "secure-ns:";
+    private static final String KEY_GLOBAL_SETTINGS_OVERRIDE_PREFIX_NS = "global-ns:";
+    private static final String KEY_SYSTEM_SETTINGS_OVERRIDE_PREFIX_NS = "system-ns:";
+
+    private static final String KEY_ACTIVATE_BROADCASTS = "bc-act:";
+    private static final String KEY_DEACTIVATE_BROADCASTS = "bc-deact:";
 
     private final KeyValueListParser mParser = new KeyValueListParser(',');
 
@@ -164,34 +212,105 @@ public class BatterySaverPolicy extends ContentObserver {
      */
     private float mAdjustBrightnessFactor;
 
+    private boolean mForceAppsStandbyEnabled;
+    private int mPowerHintMask;
+    private boolean mRedBarEnabled;
+
+    /**
+     * scaling_max_freq wouldn't accept a value lower than the current frequency, so we need to
+     * retry, and this limits the max number of retries.
+     */
+    private int mMaxFileWriteRetries;
+
+    private final ArrayList<Pair<String, String>> mFileOverrides = new ArrayList<>();
+    private final ArrayList<Pair<String, String>> mFileOverridesNS = new ArrayList<>();
+    private final ArrayList<Pair<String, String>> mFileRestores = new ArrayList<>();
+
+    private final ArrayList<Pair<String, String>> mSecureOverrides = new ArrayList<>();
+    private final ArrayList<Pair<String, String>> mGlobalOverrides = new ArrayList<>();
+    private final ArrayList<Pair<String, String>> mSystemOverrides = new ArrayList<>();
+    private final ArrayList<Pair<String, String>> mSecureOverridesNS = new ArrayList<>();
+    private final ArrayList<Pair<String, String>> mGlobalOverridesNS = new ArrayList<>();
+    private final ArrayList<Pair<String, String>> mSystemOverridesNS = new ArrayList<>();
+
+    private final ArrayList<Intent> mActivateBroadcasts = new ArrayList<>();
+    private final ArrayList<Intent> mDeactivateBroadcasts = new ArrayList<>();
+
+    private static final String SETTING_ORIGINAL_SUFFIX = "_bs_orig";
+
     private ContentResolver mContentResolver;
 
-    public BatterySaverPolicy(Handler handler) {
+    private static final String BATTERY_SAVER_CONSTANTS_DEFAULT_KEY = Settings.Global.BATTERY_SAVER_CONSTANTS + "_om";
+    private static String BATTERY_SAVER_CONSTANTS_KEY = BATTERY_SAVER_CONSTANTS_DEFAULT_KEY;
+
+    private boolean mActivated = false;
+    private boolean mScreenOn = true;
+
+    private final Context mContext;
+    private final Handler mHandler;
+
+    public BatterySaverPolicy(Context context, Handler handler) {
         super(handler);
+        mContext = context;
+        mHandler = handler;
     }
 
     public void start(ContentResolver contentResolver) {
         mContentResolver = contentResolver;
 
+        final String key = Settings.Global.getString(mContentResolver, Settings.Global.BATTERY_SAVER_CONSTANTS + "_key");
+        if (!TextUtils.isEmpty(key)) {
+            BATTERY_SAVER_CONSTANTS_KEY = key;
+        }
+
+
         mContentResolver.registerContentObserver(Settings.Global.getUriFor(
-                Settings.Global.BATTERY_SAVER_CONSTANTS), false, this);
+                BATTERY_SAVER_CONSTANTS_KEY), false, this);
         onChange(true, null);
+
+        // In case the device rebooted while battery saver was enabled, restore all settings.
+        // restoreAllSettings relies on configuration read by onChange(), so it needs to follow it.
+        restoreAllSettings();
     }
 
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case Intent.ACTION_SCREEN_ON:
+                case Intent.ACTION_SCREEN_OFF:
+                    updateScreenState();
+                    break;
+            }
+        }
+    };
+
+
     @Override
     public void onChange(boolean selfChange, Uri uri) {
         final String value = Settings.Global.getString(mContentResolver,
-                Settings.Global.BATTERY_SAVER_CONSTANTS);
+                BATTERY_SAVER_CONSTANTS_KEY);
         updateConstants(value);
+
+        // Also propagate to BATTERY_SAVER_USE_RED_BAR
+        Settings.Global.putInt(mContentResolver, Global.BATTERY_SAVER_USE_RED_BAR,
+                mRedBarEnabled ? 1 : 0);
+
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+        mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, mHandler);
+
     }
 
     @VisibleForTesting
     void updateConstants(final String value) {
         synchronized (BatterySaverPolicy.this) {
+            Slog.d(TAG, "Updating battery saver settings: " + value);
+
             try {
                 mParser.setString(value);
             } catch (IllegalArgumentException e) {
-                Slog.e(TAG, "Bad battery saver constants");
+                Slog.e(TAG, "Bad battery saver constants: " + value);
             }
 
             mVibrationDisabled = mParser.getBoolean(KEY_VIBRATION_DISABLED, true);
@@ -204,10 +323,74 @@ public class BatterySaverPolicy extends ContentObserver {
             mAdjustBrightnessFactor = mParser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
             mDataSaverDisabled = mParser.getBoolean(KEY_DATASAVER_DISABLED, true);
 
+            mForceAppsStandbyEnabled = mParser.getBoolean(KEY_FORCE_APPS_STANDBY_ENABLED, false);
+            mPowerHintMask = ~mParser.getInt(KEY_POWER_HINT_BLOCK, 0);
+
+            mRedBarEnabled = mParser.getBoolean(KEY_RED_BAR_ENABLED, true);
+
+            mMaxFileWriteRetries = mParser.getInt(KEY_MAX_FILE_WRITE_RETRIES, 20);
+
             // Get default value from Settings.Secure
             final int defaultGpsMode = Settings.Secure.getInt(mContentResolver, SECURE_KEY_GPS_MODE,
                     GPS_MODE_DISABLED_WHEN_SCREEN_OFF);
             mGpsMode = mParser.getInt(KEY_GPS_MODE, defaultGpsMode);
+
+            parseOverrides(mFileOverrides, mParser, KEY_FILE_OVERRIDE_PREFIX);
+            parseOverrides(mSecureOverrides, mParser, KEY_SECURE_SETTINGS_OVERRIDE_PREFIX);
+            parseOverrides(mGlobalOverrides, mParser, KEY_GLOBAL_SETTINGS_OVERRIDE_PREFIX);
+            parseOverrides(mSystemOverrides, mParser, KEY_SYSTEM_SETTINGS_OVERRIDE_PREFIX);
+
+            parseOverrides(mFileOverridesNS, mParser, KEY_FILE_OVERRIDE_PREFIX_NS);
+            parseOverrides(mSecureOverridesNS, mParser, KEY_SECURE_SETTINGS_OVERRIDE_PREFIX_NS);
+            parseOverrides(mGlobalOverridesNS, mParser, KEY_GLOBAL_SETTINGS_OVERRIDE_PREFIX_NS);
+            parseOverrides(mSystemOverridesNS, mParser, KEY_SYSTEM_SETTINGS_OVERRIDE_PREFIX_NS);
+
+            parseIntents(mActivateBroadcasts, mParser, KEY_ACTIVATE_BROADCASTS);
+            parseIntents(mDeactivateBroadcasts, mParser, KEY_DEACTIVATE_BROADCASTS);
+
+            copyIfEmpty(mFileOverridesNS, mFileOverrides);
+            copyIfEmpty(mSecureOverridesNS, mSecureOverrides);
+            copyIfEmpty(mGlobalOverridesNS, mGlobalOverrides);
+            copyIfEmpty(mSystemOverridesNS, mSystemOverrides);
+        }
+    }
+
+    private static <T> void copyIfEmpty(ArrayList<T> dest, ArrayList<T> source) {
+        if (dest.isEmpty()) {
+            dest.addAll(source);
+        }
+    }
+
+    private static void parseOverrides(ArrayList<Pair<String, String>> target,
+            KeyValueListParser parser, String prefix) {
+        target.clear();
+
+        for (String origKey : parser.getKeys()) {
+            if (origKey.startsWith(prefix)) {
+                final String key = origKey.substring(prefix.length());
+
+                target.add(Pair.create(key, parser.getString(origKey, "")));
+            }
+        }
+    }
+
+    private void parseIntents(ArrayList<Intent> target, KeyValueListParser parser,
+            String prefix) {
+        target.clear();
+
+        for (String origKey : parser.getKeys()) {
+            if (origKey.startsWith(prefix)) {
+                final String key = origKey.substring(prefix.length());
+                final String value = parser.getString(origKey, "");
+                if (TextUtils.isEmpty(value)) {
+                    continue;
+                }
+                try {
+                    target.add(Intent.parseUri(value, /* flags =*/ 0));
+                } catch (URISyntaxException e) {
+                    Slog.e(TAG, "Error parsing intent: " + value);
+                }
+            }
         }
     }
 
@@ -258,6 +441,11 @@ public class BatterySaverPolicy extends ContentObserver {
                 case ServiceType.VIBRATION:
                     return builder.setBatterySaverEnabled(mVibrationDisabled)
                             .build();
+
+                case ServiceType.FORCE_APPS_STANDBY:
+                    return builder.setBatterySaverEnabled(mForceAppsStandbyEnabled)
+                            .build();
+
                 default:
                     return builder.setBatterySaverEnabled(realMode)
                             .build();
@@ -268,9 +456,9 @@ public class BatterySaverPolicy extends ContentObserver {
     public void dump(PrintWriter pw) {
         pw.println();
         pw.println("Battery saver policy");
-        pw.println("  Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS);
+        pw.println("  Settings key: " + BATTERY_SAVER_CONSTANTS_KEY);
         pw.println("  value: " + Settings.Global.getString(mContentResolver,
-                Settings.Global.BATTERY_SAVER_CONSTANTS));
+                BATTERY_SAVER_CONSTANTS_KEY));
 
         pw.println();
         pw.println("  " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled);
@@ -283,5 +471,247 @@ public class BatterySaverPolicy extends ContentObserver {
         pw.println("  " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
         pw.println("  " + KEY_GPS_MODE + "=" + mGpsMode);
 
+        pw.println("  " + KEY_FORCE_APPS_STANDBY_ENABLED + "=" + mForceAppsStandbyEnabled);
+        pw.println("  " + KEY_POWER_HINT_BLOCK + "=" + Integer.toHexString(~mPowerHintMask));
+        pw.println("  " + KEY_RED_BAR_ENABLED + "=" + mRedBarEnabled);
+        pw.println("  " + KEY_MAX_FILE_WRITE_RETRIES + "=" + mMaxFileWriteRetries);
+
+        pw.println("  Files overrides     =" + mFileOverrides);
+        pw.println("  Files overrides (NS)=" + mFileOverridesNS);
+        pw.println("  Files restores      =" + mFileRestores);
+        pw.println("  Secure overrides     =" + mSecureOverrides);
+        pw.println("  Secure overrides (NS)=" + mSecureOverridesNS);
+        pw.println("  Global overrides     =" + mGlobalOverrides);
+        pw.println("  Global overrides (NS)=" + mGlobalOverridesNS);
+        pw.println("  System overrides     =" + mSystemOverrides);
+        pw.println("  System overrides (NS)=" + mSystemOverridesNS);
+
+        pw.println("  Activate broadcasts  =" + mActivateBroadcasts);
+        pw.println("  Deactivate broadcasts=" + mDeactivateBroadcasts);
+    }
+
+    // TODO Move it somewhere else.
+    public void startSaver() {
+        updateActivateState(true);
+    }
+
+    private static void applySettings(ContentResolver cr, int userId,
+            ArrayList<Pair<String, String>> overrides,
+            SettingsIf settings) {
+        for (Pair<String, String> setting : overrides) {
+            final String name = setting.first;
+            final String value = setting.second;
+            final String name_orig = name + SETTING_ORIGINAL_SUFFIX;
+
+            // Keep the original unless we've already done so.
+            final String orig = settings.read(cr, name, userId);
+            if (settings.read(cr, name_orig, userId) == null) {
+                settings.write(cr, name_orig, orig, userId);
+            }
+            settings.write(cr, name, value, userId);
+        }
+    }
+
+    public void stopSaver() {
+        updateActivateState(false);
     }
-}
+
+    private void updateScreenState() {
+        updateActivateState(mActivated);
+    }
+
+    private void updateActivateState(boolean activate) {
+        final PowerManager pm = mContext.getSystemService(PowerManager.class);
+        final boolean newScreenOn = pm.isInteractive();
+
+        if ((activate == mActivated) && (newScreenOn == mScreenOn)) {
+            return; // no changes.
+        }
+
+        // When we're going to activate, save the original file values.
+        // Note we keep the original *settings* in a different way, so no need to save the original settings here.
+        if (!mActivated) {
+            mFileRestores.clear();
+
+            for (Pair<String, String> files : mFileOverrides) {
+                final String name = files.first;
+                final String value = files.second;
+                try {
+                    final String org = IoUtils.readFileAsString(name).trim();
+                    mFileRestores.add(Pair.create(name, org));
+                } catch (IOException e) {
+                    Slog.wtf(TAG, "Can't read from" + name, e);
+                }
+            }
+        }
+        boolean wasActivated = mActivated;
+        mActivated = activate;
+        mScreenOn = newScreenOn;
+
+        if (mActivated) {
+            Slog.d(TAG, "Starting battery saver...");
+
+            PowerManagerService.setPowerHintMask(mPowerHintMask);
+
+            writeToFiles(mScreenOn ? mFileOverrides : mFileOverridesNS);
+
+            // Update settings.
+            final ContentResolver cr = mContentResolver;
+            applySettings(cr, UserHandle.USER_SYSTEM, mScreenOn ? mSecureOverrides : mSecureOverridesNS, SecureSettingsIf.INSTANCE);
+            applySettings(cr, UserHandle.USER_SYSTEM, mScreenOn ? mGlobalOverrides : mGlobalOverridesNS, GlobalSettingsIf.INSTANCE);
+            applySettings(cr, UserHandle.USER_SYSTEM, mScreenOn ? mSystemOverrides : mSystemOverridesNS, SystemSettingsIf.INSTANCE);
+        } else {
+            Slog.d(TAG, "Stopping battery saver...");
+
+            PowerManagerService.setPowerHintMask(0xffffffff);
+
+            writeToFiles(mFileRestores);
+
+            restoreAllSettings();
+        }
+        if (wasActivated != mActivated) {
+            final ArrayList<Intent> intents = mActivated ? mActivateBroadcasts : mDeactivateBroadcasts;
+
+            mHandler.post(() -> {
+                for (Intent intent : intents) {
+                    Slog.i(TAG, "Broadcasting: " + intent);
+                    mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+                }
+            });
+        }
+    }
+
+    private void restoreAllSettings() {
+        Slog.d(TAG, "Restoring settings...");
+
+        // TODO Other users.
+        final ContentResolver cr = mContentResolver;
+        restoreSettings(cr, UserHandle.USER_SYSTEM, mScreenOn ? mSecureOverrides : mSecureOverridesNS, SecureSettingsIf.INSTANCE);
+        restoreSettings(cr, UserHandle.USER_SYSTEM, mScreenOn ? mGlobalOverrides : mGlobalOverridesNS, GlobalSettingsIf.INSTANCE);
+        restoreSettings(cr, UserHandle.USER_SYSTEM, mScreenOn ? mSystemOverrides : mSystemOverridesNS, SystemSettingsIf.INSTANCE);
+    }
+
+    private static void restoreSettings(ContentResolver cr, int userId,
+            ArrayList<Pair<String, String>> overrides,
+            SettingsIf settings) {
+        for (Pair<String, String> setting : overrides) {
+            final String name = setting.first;
+            final String name_orig = name + SETTING_ORIGINAL_SUFFIX;
+
+            final String current = settings.read(cr, name, userId);
+            final String value = setting.second;
+
+            if (TextUtils.equals(current, value)) {
+                // If the user hans't changed it, restore the original value.
+
+                final String orig = settings.read(cr, name_orig, userId);
+                if (orig != null) {
+                    settings.write(cr, name, orig, userId);
+                }
+            }
+
+            // Erase the original value.
+            settings.write(cr, name_orig, null, userId);
+        }
+    }
+
+    private static final int SAVE_RETRY_DELAY = 3000;
+
+    private final AtomicInteger mNumRetries = new AtomicInteger();
+    private volatile ArrayList<Pair<String, String>> pendingFilesAndValues;
+
+    private final Runnable mWritePendingFiles = () -> {
+        writePendingFiles(); // We need to extraact it to a separate method to avoid self-refing.
+    };
+
+    private void writeToFiles(ArrayList<Pair<String, String>> filesAndValues) {
+        pendingFilesAndValues = filesAndValues;
+
+        mNumRetries.set(0);
+
+        // Grr, we can't write a lower frequency than the current frequency to the max freq.
+        // We need a retry logic...
+        IoThread.getHandler().post(mWritePendingFiles);
+    }
+
+    private void writePendingFiles() {
+        final ArrayList<Pair<String, String>> files = pendingFilesAndValues;
+        if (files != null) {
+            for (Pair<String, String> pair : files) {
+                final String name = pair.first;
+                final String value = pair.second;
+                try {
+                    writeToFile(name, value);
+                } catch (IOException e) {
+                    if (mNumRetries.incrementAndGet() < mMaxFileWriteRetries) {
+                        Slog.w(TAG, "Failed to write " + value + " to " + name + ", retrying.");
+                        // Retry.
+                        IoThread.getHandler().postDelayed(mWritePendingFiles, SAVE_RETRY_DELAY);
+                    } else {
+                        Slog.wtf(TAG, "Failed to write " + value + " to " + name, e);
+                    }
+                    return;
+                }
+            }
+            pendingFilesAndValues = null;
+        }
+    }
+
+    private void writeToFile(String name, String value) throws IOException {
+        Slog.d(TAG, "Writing " + value + " to " + name);
+        try (FileWriter out = new FileWriter(name)) {
+            out.write(value);
+        }
+    }
+
+    interface SettingsIf {
+        void write(ContentResolver cr, String name, String value, int user);
+        String read(ContentResolver cr, String name, int user);
+    }
+
+    private static class SecureSettingsIf implements SettingsIf {
+        public static final SecureSettingsIf INSTANCE = new SecureSettingsIf();
+
+        @Override
+        public void write(ContentResolver cr, String name, String value, int user) {
+            Slog.d(TAG, "Writing " + value + " to secure." + name);
+            Settings.Secure.putStringForUser(cr, name, value, user);
+        }
+
+        @Override
+        public String read(ContentResolver cr, String name, int user) {
+            return Settings.Secure.getStringForUser(cr, name, user);
+        }
+    }
+
+    private static class GlobalSettingsIf implements SettingsIf {
+        public static final GlobalSettingsIf INSTANCE = new GlobalSettingsIf();
+
+        @Override
+        public void write(ContentResolver cr, String name, String value, int user) {
+            Slog.d(TAG, "Writing " + value + " to global." + name);
+            Settings.Global.putStringForUser(cr, name, value, user);
+        }
+
+        @Override
+        public String read(ContentResolver cr, String name, int user) {
+            return Settings.Global.getStringForUser(cr, name, user);
+        }
+    }
+
+    private static class SystemSettingsIf implements SettingsIf {
+        public static final SystemSettingsIf INSTANCE = new SystemSettingsIf();
+
+        @Override
+        public void write(ContentResolver cr, String name, String value, int user) {
+            Slog.d(TAG, "Writing " + value + " to system." + name);
+            Settings.System.putStringForUser(cr, name, value, user);
+        }
+
+        @Override
+        public String read(ContentResolver cr, String name, int user) {
+            return Settings.System.getStringForUser(cr, name, user);
+        }
+    }
+
+}
\ No newline at end of file
index f84b20c..e06c5a4 100644 (file)
@@ -636,6 +636,12 @@ public final class PowerManagerService extends SystemService
     private static native void nativeSendPowerHint(int hintId, int data);
     private static native void nativeSetFeature(int featureId, int data);
 
+    private static native void nativeSetPowerHintMask(int featureIdMask);
+
+    public static void setPowerHintMask(int featureIdMask) {
+        nativeSetPowerHintMask(featureIdMask);
+    }
+
     public PowerManagerService(Context context) {
         super(context);
         mContext = context;
@@ -645,7 +651,7 @@ public final class PowerManagerService extends SystemService
         mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
         mConstants = new Constants(mHandler);
         mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
-        mBatterySaverPolicy = new BatterySaverPolicy(mHandler);
+        mBatterySaverPolicy = new BatterySaverPolicy(mContext, mHandler);
 
         synchronized (mLock) {
             mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
@@ -1014,6 +1020,7 @@ public final class PowerManagerService extends SystemService
                         final PowerSaveState result =
                                 mBatterySaverPolicy.getBatterySaverPolicy(
                                         listener.getServiceType(), lowPowerModeEnabled);
+// XXX
                         listener.onLowPowerModeChanged(result);
                     }
                     intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
@@ -1024,6 +1031,12 @@ public final class PowerManagerService extends SystemService
                     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
                     mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
                             Manifest.permission.DEVICE_POWER);
+
+                    if (mLowPowerModeEnabled) {
+                        mBatterySaverPolicy.startSaver();
+                    } else {
+                        mBatterySaverPolicy.stopSaver();
+                    }
                 }
             });
         }
index 0abc847..dbf57bf 100644 (file)
@@ -398,7 +398,7 @@ public class WindowManagerService extends IWindowManager.Stub
 
     final boolean mHasPermanentDpad;
     final long mDrawLockTimeoutMillis;
-    final boolean mAllowAnimationsInLowPowerMode;
+    // final boolean mAllowAnimationsInLowPowerMode;
 
     final boolean mAllowBootMessages;
 
@@ -1039,8 +1039,8 @@ public class WindowManagerService extends IWindowManager.Stub
                 com.android.internal.R.bool.config_defaultInTouchMode);
         mDrawLockTimeoutMillis = context.getResources().getInteger(
                 com.android.internal.R.integer.config_drawLockTimeoutMillis);
-        mAllowAnimationsInLowPowerMode = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_allowAnimationsInLowPowerMode);
+//        mAllowAnimationsInLowPowerMode = context.getResources().getBoolean(
+//                com.android.internal.R.bool.config_allowAnimationsInLowPowerMode);
         mMaxUiWidth = context.getResources().getInteger(
                 com.android.internal.R.integer.config_maxUiWidth);
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
@@ -1086,7 +1086,7 @@ public class WindowManagerService extends IWindowManager.Stub
                 public void onLowPowerModeChanged(PowerSaveState result) {
                     synchronized (mWindowMap) {
                         final boolean enabled = result.batterySaverEnabled;
-                        if (mAnimationsDisabled != enabled && !mAllowAnimationsInLowPowerMode) {
+                        if (mAnimationsDisabled != enabled) {
                             mAnimationsDisabled = enabled;
                             dispatchNewAnimatorScaleLocked(null);
                         }
index 2db7dbe..f93321e 100644 (file)
@@ -66,8 +66,32 @@ static nsecs_t gLastEventTime[USER_ACTIVITY_EVENT_LAST + 1];
 // Throttling interval for user activity calls.
 static const nsecs_t MIN_TIME_BETWEEN_USERACTIVITIES = 100 * 1000000L; // 100ms
 
+static int gPowerHintMask = 0xffffffff;
+
 // ----------------------------------------------------------------------------
 
+static bool shouldSendPowerHint(uint featureId, int data) {
+    switch ((PowerHint) featureId) {
+    case PowerHint::VSYNC:
+    case PowerHint::SUSTAINED_PERFORMANCE:
+    case PowerHint::VR_MODE:
+    case PowerHint::LAUNCH:
+        if (data == 0) return true; // Always pass 0
+        break;
+    case PowerHint::LOW_POWER:           // TODO Use it?
+        if (data == 0) {
+            featureId = 16;
+        }
+    default:
+        break; // Just pass the other ones.
+    }
+    return (gPowerHintMask & (1 << (featureId - 1))) != 0;
+}
+
+static bool shouldSendPowerHint(PowerHint feature, int data) {
+    return shouldSendPowerHint((uint) feature, data);
+}
+
 static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
     if (env->ExceptionCheck()) {
         ALOGE("An exception was thrown by callback '%s'.", methodName);
@@ -122,7 +146,7 @@ void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t
 
             // Tell the power HAL when user activity occurs.
             gPowerHalMutex.lock();
-            if (getPowerHal()) {
+            if (shouldSendPowerHint(PowerHint::INTERACTION, 0) && getPowerHal()) {
               Return<void> ret;
               if (gPowerHalV1_1 != nullptr) {
                 ret = gPowerHalV1_1->powerHintAsync(PowerHint::INTERACTION, 0);
@@ -177,6 +201,13 @@ static void nativeSetInteractive(JNIEnv* /* env */, jclass /* clazz */, jboolean
     }
 }
 
+static void nativeSetPowerHintMask(JNIEnv* /* env */, jclass /* clazz */, jint mask) {
+    std::lock_guard<std::mutex> lock(gPowerHalMutex);
+    gPowerHintMask = mask;
+
+    ALOGD("nativeSetPowerHintMask: mask=%x", gPowerHintMask);
+}
+
 static void nativeSetAutoSuspend(JNIEnv* /* env */, jclass /* clazz */, jboolean enable) {
     if (enable) {
         android::base::Timer t;
@@ -195,7 +226,8 @@ static void nativeSetAutoSuspend(JNIEnv* /* env */, jclass /* clazz */, jboolean
 
 static void nativeSendPowerHint(JNIEnv *env, jclass clazz, jint hintId, jint data) {
     std::lock_guard<std::mutex> lock(gPowerHalMutex);
-    if (getPowerHal()) {
+
+    if (shouldSendPowerHint(hintId, data) && getPowerHal()) {
         Return<void> ret;
         if (gPowerHalV1_1 != nullptr) {
             ret =  gPowerHalV1_1->powerHintAsync((PowerHint)hintId, data);
@@ -232,6 +264,9 @@ static const JNINativeMethod gPowerManagerServiceMethods[] = {
             (void*) nativeSendPowerHint },
     { "nativeSetFeature", "(II)V",
             (void*) nativeSetFeature },
+
+    { "nativeSetPowerHintMask", "(I)V",
+            (void*) nativeSetPowerHintMask },
 };
 
 #define FIND_CLASS(var, className) \
index 19396d4..a6d6b02 100644 (file)
@@ -31,6 +31,7 @@ LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl
 
 LOCAL_SRC_FILES += aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl \
     aidl/com/android/servicestests/aidl/ICmdReceiverService.aidl
+LOCAL_SRC_FILES += $(call all-java-files-under, test-apps/JobTestApp/src)
 
 LOCAL_JAVA_LIBRARIES := android.test.mock legacy-android-test
 
@@ -63,4 +64,5 @@ LOCAL_STATIC_JAVA_LIBRARIES += ub-uiautomator
 
 include $(BUILD_PACKAGE)
 
-include $(call all-makefiles-under, $(LOCAL_PATH))
\ No newline at end of file
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
index 59d205e..4729d06 100644 (file)
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
     <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
     <uses-permission android:name="android.permission.DELETE_PACKAGES" />
+    <uses-permission android:name="android.permission.GET_APP_OPS_STATS" />
+    <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+    <uses-permission android:name="android.permission.DEVICE_POWER" />
+    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" />
+    <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
 
     <!-- Uses API introduced in O (26) -->
     <uses-sdk android:minSdkVersion="1"
index 6a9983e..c85dccd 100644 (file)
@@ -17,6 +17,7 @@
     <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
         <option name="test-file-name" value="FrameworksServicesTests.apk" />
         <option name="test-file-name" value="ConnTestApp.apk" />
+        <option name="test-file-name" value="JobTestApp.apk" />
     </target_preparer>
 
     <option name="test-suite-tag" value="apct" />
diff --git a/services/tests/servicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml b/services/tests/servicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml
new file mode 100644 (file)
index 0000000..a37d84f
--- /dev/null
@@ -0,0 +1,200 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<app-ops>
+<uid n="1001">
+<op n="15" m="0" />
+</uid>
+<uid n="10052">
+<op n="63" m="1" />
+</uid>
+<pkg n="com.quicinc.cne.CNEService">
+<uid n="1000" p="true">
+<op n="59" t="1501535978641" pu="0" />
+<op n="60" t="1501535978641" pu="0" />
+</uid>
+</pkg>
+<pkg n="android">
+<uid n="1000" p="true">
+<op n="0" />
+<op n="3" t="1501537828283" d="22" />
+<op n="8" t="1501535987988" pu="0" pp="com.android.providers.calendar" />
+<op n="23" r="1501535979451" />
+<op n="40" t="1501621469584" d="1" />
+<op n="41" t="1501535980608" d="85615033" />
+<op n="61" t="1501557904487" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.android.server.telecom">
+<uid n="1000" p="true">
+<op n="6" t="1501535984350" pu="0" pp="com.android.providers.contacts" />
+</uid>
+</pkg>
+<pkg n="com.android.settings">
+<uid n="1000" p="true">
+<op n="59" t="1501536001265" pu="0" />
+<op n="60" t="1501536001265" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.android.providers.telephony">
+<uid n="1001" p="true">
+<op n="15" m="0" />
+</uid>
+</pkg>
+<pkg n="com.qualcomm.qti.rcsbootstraputil">
+<uid n="1001" p="false">
+<op n="59" t="1501535981233" pu="0" />
+<op n="60" t="1501535981233" pu="0" />
+<op n="63" t="1501536015379" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.android.phone">
+<uid n="1001" p="true">
+<op n="14" t="1501547602479" />
+<op n="15" m="0" t="1501535981903" pu="0" />
+<op n="40" t="1501621220685" d="4" />
+<op n="59" t="1501535978675" pu="0" />
+<op n="60" t="1501535978675" pu="0" />
+<op n="63" m="1" t="1501277487395" pu="0" />
+</uid>
+</pkg>
+<pkg n="audioserver">
+<uid n="1041" p="false">
+<op n="40" t="1501542152888" d="4" />
+</uid>
+</pkg>
+<pkg n="com.android.shell">
+<uid n="2000" p="true">
+<op n="59" t="1501535997600" pu="0" />
+<op n="60" t="1501535997600" pu="0" />
+<op n="63" t="1501535997600" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.google.android.apps.turbo">
+<uid n="10024" p="true">
+<op n="59" t="1501621079685" pu="0" />
+<op n="60" t="1501621079685" pu="0" />
+<op n="63" t="1501621079682" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.android.providers.downloads">
+<uid n="10027" p="true">
+<op n="59" t="1501601386341" />
+<op n="60" t="1501601375992" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.google.android.carriersetup">
+<uid n="10029" p="true">
+<op n="59" t="1501536001405" pu="0" />
+<op n="60" t="1501536001405" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.android.systemui">
+<uid n="10031" p="true">
+<op n="3" t="1501537825972" d="21" />
+<op n="40" t="1501619729317" d="7297" />
+<op n="59" t="1501535979651" pu="0" />
+<op n="60" t="1501535978058" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.android.chrome">
+<uid n="10096" p="false">
+<op n="23" r="1501537723291" />
+<op n="59" t="1501537615416" pu="0" />
+<op n="60" t="1501537615416" pu="0" />
+<op n="63" m="1" />
+</uid>
+</pkg>
+<pkg n="com.google.android.apps.maps">
+<uid n="10102" p="false">
+<op n="0" />
+<op n="1" t="1501620392477" pu="0" />
+<op n="59" t="1501620392609" pu="0" />
+<op n="60" t="1501620392609" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.google.android.syncadapters.contacts">
+<uid n="10109" p="false">
+<op n="4" t="1501535997715" pu="0" pp="com.android.providers.contacts" />
+<op n="59" t="1501535997265" pu="0" />
+<op n="60" t="1501535997265" pu="0" />
+<op n="63" t="1501535997589" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.google.android.youtube">
+<uid n="10111" p="false">
+<op n="59" t="1501620380957" pu="0" />
+<op n="60" t="1501620380957" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.google.android.deskclock">
+<uid n="10114" p="false">
+<op n="40" t="1501537682746" d="379" />
+<op n="59" t="1501537682098" pu="0" />
+<op n="60" t="1501537682098" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.google.android.apps.internal.betterbug">
+<uid n="10117" p="false">
+<op n="59" t="1501535989133" pu="0" />
+<op n="60" t="1501535989133" pu="0" />
+<op n="63" t="1501535989132" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.google.android.tts">
+<uid n="10118" p="false">
+<op n="59" t="1501193966186" pu="0" />
+<op n="60" t="1501193966186" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.google.android.apps.enterprise.dmagent">
+<uid n="10119" p="false">
+<op n="59" t="1501193986104" pu="0" />
+<op n="60" t="1501193986104" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.qualcomm.embms">
+<uid n="10122" p="false">
+<op n="59" t="1501535999723" pu="0" />
+<op n="60" t="1501535999723" pu="0" />
+<op n="63" t="1501535999550" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.qualcomm.qti.telephonyservice">
+<uid n="10123" p="false">
+<op n="59" t="1501535978649" pu="0" />
+<op n="60" t="1501535978649" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.qualcomm.ltebc_vzw">
+<uid n="10124" p="false">
+<op n="59" t="1501536001390" pu="0" />
+<op n="60" t="1501536001390" pu="0" />
+<op n="63" t="1501536000356" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.android.ramdump">
+<uid n="10125" p="false">
+<op n="59" t="1501536047490" pu="0" />
+<op n="60" t="1501536047490" pu="0" />
+<op n="63" m="1" />
+</uid>
+</pkg>
+<pkg n="com.android.nexuslogger">
+<uid n="10127" p="false">
+<op n="59" t="1501535985248" pu="0" />
+<op n="60" t="1501535985248" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.google.android.apps.multidevice.client">
+<uid n="10131" p="false">
+<op n="59" t="1501535991782" pu="0" />
+<op n="60" t="1501535991782" pu="0" />
+<op n="63" t="1501535991781" pu="0" />
+</uid>
+</pkg>
+<pkg n="com.android.frameworks.servicestests">
+<uid n="10132" p="false">
+<op n="59" t="1501551739953" pu="0" />
+<op n="60" t="1501551739953" pu="0" />
+</uid>
+</pkg>
+</app-ops>
diff --git a/services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java b/services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java
new file mode 100644 (file)
index 0000000..cc89ae8
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Tests app ops version upgrades
+ */
+@RunWith(AndroidJUnit4.class)
+public class AppOpsUpgradeTest {
+    private static final String TAG = AppOpsUpgradeTest.class.getSimpleName();
+    private static final String APP_OPS_UNVERSIONED_ASSET_PATH =
+            "AppOpsUpgradeTest/appops-unversioned.xml";
+    private static final String APP_OPS_FILENAME = "appops-test.xml";
+    private static final int NON_DEFAULT_OPS_IN_FILE = 4;
+    private static final int CURRENT_VERSION = 1;
+
+    private File mAppOpsFile;
+    private Context mContext;
+    private Handler mHandler;
+
+    private void extractAppOpsFile() {
+        mAppOpsFile.getParentFile().mkdirs();
+        if (mAppOpsFile.exists()) {
+            mAppOpsFile.delete();
+        }
+        try (FileOutputStream out = new FileOutputStream(mAppOpsFile);
+             InputStream in = mContext.getAssets().open(APP_OPS_UNVERSIONED_ASSET_PATH,
+                     AssetManager.ACCESS_BUFFER)) {
+            byte[] buffer = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = in.read(buffer)) >= 0) {
+                out.write(buffer, 0, bytesRead);
+            }
+            out.flush();
+            Log.d(TAG, "Successfully copied xml to " + mAppOpsFile.getAbsolutePath());
+        } catch (IOException exc) {
+            Log.e(TAG, "Exception while copying appops xml", exc);
+            fail();
+        }
+    }
+
+    private void assertSameModes(SparseArray<AppOpsService.UidState> uidStates, int op1, int op2) {
+        int numberOfNonDefaultOps = 0;
+        final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1);
+        final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2);
+        for(int i = 0; i < uidStates.size(); i++) {
+            final AppOpsService.UidState uidState = uidStates.valueAt(i);
+            if (uidState.opModes != null) {
+                final int uidMode1 = uidState.opModes.get(op1, defaultModeOp1);
+                final int uidMode2 = uidState.opModes.get(op2, defaultModeOp2);
+                assertEquals(uidMode1, uidMode2);
+                if (uidMode1 != defaultModeOp1) {
+                    numberOfNonDefaultOps++;
+                }
+            }
+            if (uidState.pkgOps == null) {
+                continue;
+            }
+            for (int j = 0; j < uidState.pkgOps.size(); j++) {
+                final AppOpsService.Ops ops = uidState.pkgOps.valueAt(j);
+                if (ops == null) {
+                    continue;
+                }
+                final AppOpsService.Op _op1 = ops.get(op1);
+                final AppOpsService.Op _op2 = ops.get(op2);
+                final int mode1 = (_op1 == null) ? defaultModeOp1 : _op1.mode;
+                final int mode2 = (_op2 == null) ? defaultModeOp2 : _op2.mode;
+                assertEquals(mode1, mode2);
+                if (mode1 != defaultModeOp1) {
+                    numberOfNonDefaultOps++;
+                }
+            }
+        }
+        assertEquals(numberOfNonDefaultOps, NON_DEFAULT_OPS_IN_FILE);
+    }
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mAppOpsFile = new File(mContext.getFilesDir(), APP_OPS_FILENAME);
+        extractAppOpsFile();
+        HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        mHandler = new Handler(handlerThread.getLooper());
+    }
+
+    @Test
+    public void testUpgradeFromNoVersion() throws Exception {
+        AppOpsDataParser parser = new AppOpsDataParser(mAppOpsFile);
+        assertTrue(parser.parse());
+        assertEquals(AppOpsDataParser.NO_VERSION, parser.mVersion);
+        AppOpsService testService = new AppOpsService(mAppOpsFile, mHandler); // trigger upgrade
+        assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND,
+                AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
+        testService.mContext = mContext;
+        mHandler.removeCallbacks(testService.mWriteRunner);
+        testService.writeState();
+        assertTrue(parser.parse());
+        assertEquals(CURRENT_VERSION, parser.mVersion);
+    }
+
+    /**
+     * Class to parse data from the appops xml. Currently only parses and holds the version number.
+     * Other fields may be added as and when required for testing.
+     */
+    private static final class AppOpsDataParser {
+        static final int NO_VERSION = -1;
+        int mVersion;
+        private File mFile;
+
+        AppOpsDataParser(File file) {
+            mFile = file;
+            mVersion = NO_VERSION;
+        }
+
+        boolean parse() {
+            try (FileInputStream stream = new FileInputStream(mFile)) {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(stream, StandardCharsets.UTF_8.name());
+                int type;
+                while ((type = parser.next()) != XmlPullParser.START_TAG
+                        && type != XmlPullParser.END_DOCUMENT) {
+                    ;
+                }
+                if (type != XmlPullParser.START_TAG) {
+                    throw new IllegalStateException("no start tag found");
+                }
+                final String versionString = parser.getAttributeValue(null, "v");
+                if (versionString != null) {
+                    mVersion = Integer.parseInt(versionString);
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed while parsing test appops xml", e);
+                return false;
+            }
+            return true;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
new file mode 100644 (file)
index 0000000..70d2274
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.job;
+
+import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STARTED;
+import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STOPPED;
+import static com.android.servicestests.apps.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.app.job.JobParameters;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.IDeviceIdleController;
+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;
+import android.util.Log;
+
+import com.android.servicestests.apps.jobtestapp.TestJobActivity;
+
+import org.junit.After;
+import org.junit.Before;
+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:
+ * <pre>
+ *  mmm -j32 frameworks/base/services/tests/servicestests/
+ *  adb install out/target/product/marlin/data/app/JobTestApp/JobTestApp.apk
+ *  adb install out/target/product/marlin/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ *  adb  shell am instrument -e class 'com.android.server.job.BackgroundRestrictionsTest' -w \
+ *  'com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner'
+ * </pre>
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class BackgroundRestrictionsTest {
+    private static final String TAG = BackgroundRestrictionsTest.class.getSimpleName();
+    private static final String TEST_APP_PACKAGE = "com.android.servicestests.apps.jobtestapp";
+    private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestJobActivity";
+    private static final long POLL_INTERVAL = 2000;
+    private static final long DEFAULT_WAIT_TIMEOUT = 5000;
+
+    private Context mContext;
+    private AppOpsManager mAppOpsManager;
+    private IDeviceIdleController mDeviceIdleController;
+    private IActivityManager mIActivityManager;
+    private int mTestJobId;
+    private int mTestPackageUid;
+    /* accesses must be synchronized on itself */
+    private final TestJobStatus mTestJobStatus = new TestJobStatus();
+    private final BroadcastReceiver mJobStateChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY);
+            Log.d(TAG, "Received action " + intent.getAction());
+            synchronized (mTestJobStatus) {
+                switch (intent.getAction()) {
+                    case ACTION_JOB_STARTED:
+                        mTestJobStatus.running = true;
+                        mTestJobStatus.jobId = params.getJobId();
+                        mTestJobStatus.stopReason = JobParameters.REASON_CANCELED;
+                        break;
+                    case ACTION_JOB_STOPPED:
+                        mTestJobStatus.running = false;
+                        mTestJobStatus.jobId = params.getJobId();
+                        mTestJobStatus.stopReason = params.getStopReason();
+                        break;
+                }
+            }
+        }
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
+                ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+        mIActivityManager = ActivityManager.getService();
+        mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0);
+        mTestJobId = (int) (SystemClock.uptimeMillis() / 1000);
+        mTestJobStatus.reset();
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION_JOB_STARTED);
+        intentFilter.addAction(ACTION_JOB_STOPPED);
+        mContext.registerReceiver(mJobStateChangeReceiver, intentFilter);
+        setGlobalSwitch(true);
+        setAppOpsModeAllowed(true);
+        setPowerWhiteListed(false);
+    }
+
+    private void scheduleAndAssertJobStarted() throws Exception {
+        final Intent scheduleJobIntent = new Intent(TestJobActivity.ACTION_START_JOB);
+        scheduleJobIntent.putExtra(TestJobActivity.EXTRA_JOB_ID_KEY, mTestJobId);
+        scheduleJobIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
+        mContext.startActivity(scheduleJobIntent);
+        Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
+        assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @Test
+    public void testGlobalSwitch() throws Exception {
+        setGlobalSwitch(false); // Job should not stop now.
+        scheduleAndAssertJobStarted();
+        setAppOpsModeAllowed(false);
+        mIActivityManager.makePackageIdle(TEST_APP_PACKAGE, UserHandle.USER_CURRENT);
+        assertFalse("Job stopped even when feature switch is off",
+                awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @Test
+    public void testPowerWhiteList() throws Exception {
+        scheduleAndAssertJobStarted();
+        setAppOpsModeAllowed(false);
+        mIActivityManager.makePackageIdle(TEST_APP_PACKAGE, UserHandle.USER_CURRENT);
+        assertTrue("Job did not stop after making idle", awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        setPowerWhiteListed(true);
+        Thread.sleep(TestJobActivity.JOB_INITIAL_BACKOFF);
+        assertTrue("Job did not start after adding to power whitelist",
+                awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        setPowerWhiteListed(false);
+        assertTrue("Job did not stop after removing from power whitelist",
+                awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        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);
+        setGlobalSwitch(false);
+        setAppOpsModeAllowed(true);
+        setPowerWhiteListed(false);
+    }
+
+    private void setGlobalSwitch(boolean enabled) {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.JOB_SCHEDULER_CONSTANTS, "bg_jobs_restricted=" + enabled);
+    }
+
+    private void setPowerWhiteListed(boolean whitelist) throws RemoteException {
+        if (whitelist) {
+            mDeviceIdleController.addPowerSaveWhitelistApp(TEST_APP_PACKAGE);
+        } else {
+            mDeviceIdleController.removePowerSaveWhitelistApp(TEST_APP_PACKAGE);
+        }
+    }
+
+    private void setAppOpsModeAllowed(boolean allow) throws PackageManager.NameNotFoundException {
+        mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mTestPackageUid,
+                TEST_APP_PACKAGE, allow ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
+    }
+
+    private boolean awaitJobStart(long timeout) throws InterruptedException {
+        return waitUntilTrue(timeout, () -> {
+            synchronized (mTestJobStatus) {
+                return (mTestJobStatus.jobId == mTestJobId) && mTestJobStatus.running;
+            }
+        });
+    }
+
+    private boolean awaitJobStop(long timeout) throws InterruptedException {
+        return waitUntilTrue(timeout, () -> {
+            synchronized (mTestJobStatus) {
+                return (mTestJobStatus.jobId == mTestJobId) && !mTestJobStatus.running &&
+                        mTestJobStatus.stopReason == JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED;
+            }
+        });
+    }
+
+    private boolean waitUntilTrue(long timeout, Condition condition) throws InterruptedException {
+        final long deadLine = SystemClock.uptimeMillis() + timeout;
+        do {
+            Thread.sleep(POLL_INTERVAL);
+        } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine);
+        return condition.isTrue();
+    }
+
+    private static final class TestJobStatus {
+        int jobId;
+        int stopReason;
+        boolean running;
+        private void reset() {
+            running = false;
+            stopReason = jobId = 0;
+        }
+    }
+
+    private interface Condition {
+        boolean isTrue();
+    }
+}
index 69589e7..08df717 100644 (file)
@@ -57,7 +57,7 @@ public class BatterySaverPolicyTest extends AndroidTestCase {
     public void setUp() throws Exception {
         super.setUp();
         MockitoAnnotations.initMocks(this);
-        mBatterySaverPolicy = new BatterySaverPolicy(mHandler);
+        mBatterySaverPolicy = new BatterySaverPolicy(getContext(), mHandler);
         mBatterySaverPolicy.start(getContext().getContentResolver());
     }
 
diff --git a/services/tests/servicestests/test-apps/JobTestApp/Android.mk b/services/tests/servicestests/test-apps/JobTestApp/Android.mk
new file mode 100644 (file)
index 0000000..7893c91
--- /dev/null
@@ -0,0 +1,30 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := JobTestApp
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..ac35805
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.servicestests.apps.jobtestapp">
+
+    <application>
+        <service android:name=".TestJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" />
+        <activity android:name=".TestJobActivity"
+                  android:exported="true" />
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
new file mode 100644 (file)
index 0000000..94a85ee
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.servicestests.apps.jobtestapp;
+
+import android.app.Activity;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TestJobActivity extends Activity {
+    private static final String TAG = TestJobActivity.class.getSimpleName();
+    public static final String EXTRA_JOB_ID_KEY =
+            "com.android.servicestests.apps.jobtestapp.extra.JOB_ID";
+    public static final String ACTION_START_JOB =
+            "com.android.servicestests.apps.jobtestapp.extra.START_JOB";
+    public static final String ACTION_CANCEL_JOBS =
+            "com.android.servicestests.apps.jobtestapp.extra.CANCEL_JOBS";
+    public static final int JOB_INITIAL_BACKOFF = 10_000;
+    public static final int JOB_MINIMUM_LATENCY = 5_000;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        ComponentName jobServiceComponent = new ComponentName(this, TestJobService.class);
+        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
+        final Intent intent = getIntent();
+        switch (intent.getAction()) {
+            case ACTION_CANCEL_JOBS:
+                jobScheduler.cancelAll();
+                Log.d(TAG, "Cancelled all jobs for " + getPackageName());
+                break;
+            case ACTION_START_JOB:
+                final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode());
+                JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent)
+                        .setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR)
+                        .setMinimumLatency(JOB_MINIMUM_LATENCY);
+                final int result = jobScheduler.schedule(jobBuilder.build());
+                if (result != JobScheduler.RESULT_SUCCESS) {
+                    Log.e(TAG, "Could not schedule job " + jobId);
+                } else {
+                    Log.d(TAG, "Successfully scheduled job with id " + jobId);
+                }
+                break;
+        }
+        finish();
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
new file mode 100644 (file)
index 0000000..6bebb32
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.servicestests.apps.jobtestapp;
+
+import android.annotation.TargetApi;
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.content.Intent;
+import android.util.Log;
+
+@TargetApi(24)
+public class TestJobService extends JobService {
+    private static final String TAG = TestJobService.class.getSimpleName();
+    private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
+    public static final String ACTION_JOB_STARTED = PACKAGE_NAME + ".action.JOB_STARTED";
+    public static final String ACTION_JOB_STOPPED = PACKAGE_NAME + ".action.JOB_STOPPED";
+    public static final String JOB_PARAMS_EXTRA_KEY = PACKAGE_NAME + ".extra.JOB_PARAMETERS";
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        Log.i(TAG, "Test job executing: " + params.getJobId());
+        Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED);
+        reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params);
+        sendBroadcast(reportJobStartIntent);
+        return true;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        Log.i(TAG, "Test job stopped executing: " + params.getJobId());
+        Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED);
+        reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params);
+        sendBroadcast(reportJobStopIntent);
+        return true;
+    }
+}