From a95d31e3b2a3c9e696dadabcfddc64a78c535280 Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Wed, 6 Sep 2017 12:16:56 -0700 Subject: [PATCH 01/48] DO NOT MERGE Extreme battery saver experiment 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 afdfd2961ab24a6c8ea13c75d80d5241ead0da91) --- .../android/app/ActivityManagerInternal.java | 2 +- core/java/android/app/AppOpsManager.java | 14 +- core/java/android/app/IActivityManager.aidl | 1 + core/java/android/app/PendingIntent.java | 20 +- core/java/android/provider/Settings.java | 3 + .../java/android/util/KeyValueListParser.java | 9 + .../providers/settings/SettingsService.java | 9 + .../systemui/statusbar/phone/StatusBar.java | 4 +- .../android/server/AlarmManagerService.java | 381 +++++++++++++++++- .../com/android/server/AppOpsService.java | 68 +++- .../android/server/DeviceIdleController.java | 8 +- .../server/am/ActivityManagerService.java | 84 +++- .../server/job/JobSchedulerService.java | 75 +++- .../controllers/BackgroundJobsController.java | 332 +++++++++++++++ .../server/job/controllers/JobStatus.java | 11 +- .../server/location/GnssLocationProvider.java | 26 ++ .../server/power/BatterySaverPolicy.java | 308 +++++++++++++- .../server/power/PowerManagerService.java | 13 + .../server/wm/WindowManagerService.java | 8 +- ...droid_server_power_PowerManagerService.cpp | 39 +- services/tests/servicestests/Android.mk | 4 +- .../tests/servicestests/AndroidManifest.xml | 5 + services/tests/servicestests/AndroidTest.xml | 1 + .../AppOpsUpgradeTest/appops-unversioned.xml | 200 +++++++++ .../com/android/server/AppOpsUpgradeTest.java | 180 +++++++++ .../job/BackgroundRestrictionsTest.java | 228 +++++++++++ .../test-apps/JobTestApp/Android.mk | 30 ++ .../test-apps/JobTestApp/AndroidManifest.xml | 27 ++ .../apps/jobtestapp/TestJobActivity.java | 65 +++ .../apps/jobtestapp/TestJobService.java | 50 +++ 30 files changed, 2149 insertions(+), 56 deletions(-) create mode 100644 services/core/java/com/android/server/job/controllers/BackgroundJobsController.java create mode 100644 services/tests/servicestests/assets/AppOpsUpgradeTest/appops-unversioned.xml create mode 100644 services/tests/servicestests/src/com/android/server/AppOpsUpgradeTest.java create mode 100644 services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java create mode 100644 services/tests/servicestests/test-apps/JobTestApp/Android.mk create mode 100644 services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml create mode 100644 services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java create mode 100644 services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index c8d983933fc..68891b2f56d 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -177,7 +177,7 @@ public abstract void setPendingIntentWhitelistDuration(IIntentSender target, /** * 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. diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index b331d84010d..4bd85ae9ee5 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -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 }; /** diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 897e42bc2b2..f36c95eb38b 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -315,6 +315,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, diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index b7d3f578df2..a25c2267214 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -20,18 +20,17 @@ 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; @@ -1010,6 +1009,19 @@ public boolean isActivity() { } } + /** + * @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. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 586631e4b6e..0c3f457c888 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9314,6 +9314,9 @@ public static final class Global extends NameValueTable { */ 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. Ex: diff --git a/core/java/android/util/KeyValueListParser.java b/core/java/android/util/KeyValueListParser.java index be531ff3599..d11c79d79b2 100644 --- a/core/java/android/util/KeyValueListParser.java +++ b/core/java/android/util/KeyValueListParser.java @@ -17,6 +17,8 @@ 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 boolean getBoolean(String key, boolean def) { } return def; } + + /** + * @return All the keys. + */ + public Collection getKeys() { + return mValues.keySet(); + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java index a6fadf967f9..aecaf41df1e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java @@ -109,6 +109,7 @@ enum CommandVerb { 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 @@ public int onCommand(String cmd) { 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 @@ public int onCommand(String cmd) { 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 @@ static void dumpHelp(PrintWriter pw, boolean dumping) { 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 | 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 | current] NAMESPACE {PACKAGE_NAME | RESET_MODE}"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 7c6e886ad0a..98e78597da6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -3370,7 +3370,9 @@ void checkBarMode(int mode, int windowState, BarTransitions transitions) { 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); diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index d1cf0255b18..90c1763f399 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -44,8 +44,11 @@ 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 +57,7 @@ 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 +89,11 @@ 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 +113,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 +122,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 +133,15 @@ class AlarmManagerService extends SystemService { final LocalLog mLog = new LocalLog(TAG); AppOpsManager mAppOps; + IAppOpsService mAppOpsService; DeviceIdleController.LocalService mLocalDeviceIdleController; final Object mLock = new Object(); + ArraySet mForcedAppStandbyPackages = new ArraySet<>(); + SparseBooleanArray mForegroundUids = new SparseBooleanArray(); + // List of alarms per uid deferred due to user applied background restrictions on the source app + SparseArray> mPendingBackgroundAlarms = new SparseArray<>(); long mNativeData; private long mNextWakeup; private long mNextNonWakeup; @@ -158,6 +171,8 @@ class AlarmManagerService extends SystemService { long mAllowWhileIdleMinTime; int mNumTimeChanged; + boolean mForceAppStandby; + // Bookkeeping about the identity of the "System UI" package, determined at runtime. /** @@ -172,11 +187,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, @@ -225,6 +236,7 @@ private final class Constants extends ContentObserver { 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 long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; @@ -234,6 +246,8 @@ private final class Constants extends ContentObserver { 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 +267,8 @@ private final class Constants extends ContentObserver { // 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; @@ -311,6 +327,12 @@ private void updateConstants() { 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"); + } updateAllowWhileIdleMinTimeLocked(); updateAllowWhileIdleWhitelistDurationLocked(); @@ -705,7 +727,7 @@ void calculateDeliveryPriorities(ArrayList alarms) { static final BatchTimeOrder sBatchOrder = new BatchTimeOrder(); final ArrayList 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 +836,108 @@ void reAddAlarmLocked(Alarm a, long nowElapsed, boolean doValidate) { 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 alarmsForUid = mPendingBackgroundAlarms.get(uid); + if (alarmsForUid == null || alarmsForUid.size() == 0) { + return; + } + final ArrayList 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 alarmsToDeliver = new ArrayList<>(); + for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) { + final int uid = mPendingBackgroundAlarms.keyAt(i); + final ArrayList alarmsForUid = mPendingBackgroundAlarms.valueAt(i); + if (UserHandle.getAppId(uid) == appId) { + alarmsToDeliver.addAll(alarmsForUid); + mPendingBackgroundAlarms.removeAt(i); + } + } + deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime()); + } + + private void deliverPendingBackgroundAlarmsLocked(ArrayList 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 +1156,14 @@ public void onStart() { 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 +1175,33 @@ public void onBootPhase(int phase) { 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."); + } } } @@ -1060,6 +1214,41 @@ protected void finalize() throws Throwable { } } + void updateForceAppStandby(boolean enabled) { + synchronized (this) { + if (mForceAppStandby != enabled) { + mForceAppStandby = enabled; + maybeRunPendingBackgroundAlarms(); + } + } + } + + void maybeRunPendingBackgroundAlarms() { + synchronized (this) { + 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 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 (this) { + return mForceAppStandby || mConstants.BACKGROUND_ALARMS_BLOCKED; + } + } + void setTimeZoneImpl(String tz) { if (TextUtils.isEmpty(tz)) { return; @@ -1376,7 +1565,7 @@ public void set(String callingPackage, // 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 +1639,25 @@ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { }; 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 +1697,7 @@ void dumpImpl(PrintWriter pw) { 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 +1730,19 @@ void dumpImpl(PrintWriter pw) { 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 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 +2007,24 @@ long getNextWakeFromIdleTimeImpl() { } } - 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 +2204,18 @@ private void removeLocked(PendingIntent operation, IAlarmListener directReceiver mPendingWhileIdleAlarms.remove(i); } } - + for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) { + final ArrayList 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 +2252,17 @@ void removeLocked(String packageName) { mPendingWhileIdleAlarms.remove(i); } } - + for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i --) { + final ArrayList 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 +2289,11 @@ void removeForStoppedLocked(int uid) { 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 +2320,11 @@ void removeUserLocked(int userHandle) { 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 +2439,42 @@ private static final void dumpAlarmList(PrintWriter pw, ArrayList list, } } + 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 +2530,19 @@ boolean triggerAlarmsLocked(ArrayList triggerList, final long nowELAPSED, 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 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); @@ -2904,21 +3208,32 @@ final class UidObserver extends IUidObserver.Stub { } @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 +3241,28 @@ final class UidObserver extends IUidObserver.Stub { } }; + 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(); diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index 29f8a113079..50b8df2ae20 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -52,6 +52,7 @@ 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 void run() { } }; - private final SparseArray mUidStates = new SparseArray<>(); + @VisibleForTesting + final SparseArray mUidStates = new SparseArray<>(); /* * These are app op restrictions imposed per user from various parties. */ private final ArrayMap mOpUserRestrictions = new ArrayMap<>(); - private static final class UidState { + @VisibleForTesting + static final class UidState { public final int uid; public ArrayMap pkgOps; public SparseIntArray opModes; @@ -1398,6 +1406,7 @@ private boolean isOpRestrictedLocked(int uid, int code, String packageName) { } void readState() { + int oldVersion = NO_VERSION; synchronized (mFile) { synchronized (this) { FileInputStream stream; @@ -1422,6 +1431,11 @@ void readState() { 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 @@ void readState() { } } } + 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 @@ void writeState() { 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++) { diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 05c75042f0f..b3202bc5f08 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -1458,9 +1458,9 @@ public void onBootPhase(int phase) { 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 +2389,7 @@ private void updateWhitelistAppIdsLocked() { 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 +2403,7 @@ private void updateWhitelistAppIdsLocked() { Slog.d(TAG, "Setting alarm whitelist to " + Arrays.toString(mPowerSaveWhitelistUserAppIdArray)); } - mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray); + mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray, mPowerSaveWhitelistAllAppIdArray); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 85fa7a1b4c3..c0fb66a2a6c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -308,6 +308,7 @@ 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.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 @@ private class Identity { */ 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 */ @@ -7864,6 +7868,15 @@ public boolean isIntentSenderAnActivity(IIntentSender pendingResult) { return false; } + @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, @@ -8527,6 +8540,7 @@ public boolean isAppStartModeDisabled(int uid, String packageName) { } } +//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 @@ -8536,6 +8550,14 @@ int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTarg } 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); @@ -8650,6 +8672,11 @@ boolean isOnDeviceIdleWhitelistLocked(int uid) { || 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); @@ -14153,6 +14180,26 @@ public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) 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)) @@ -14315,6 +14362,22 @@ public void performReceive(Intent intent, int resultCode, String data, } } + 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); @@ -15884,6 +15947,7 @@ void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args, } } 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:"); @@ -23198,6 +23262,21 @@ final void runInBackgroundDisabled(int uid) { } } + 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); @@ -23998,9 +24077,10 @@ public void setPendingIntentWhitelistDuration(IIntentSender target, IBinder whit } @Override - public void setDeviceIdleWhitelist(int[] appids) { + public void setDeviceIdleWhitelist(int[] userAppids, int[] allAppids) { synchronized (ActivityManagerService.this) { - mDeviceIdleWhitelist = appids; + mDeviceIdleUserWhitelist = userAppids; + mDeviceIdleWhitelist = allAppids; } } diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 4dab32c03a7..a2899fcd156 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -55,6 +55,8 @@ 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 +82,7 @@ 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 +92,7 @@ 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; @@ -140,6 +144,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 +205,8 @@ public final class JobSchedulerService extends com.android.server.SystemService */ int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT]; + boolean mForceAppStandby; + /** * 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 +233,7 @@ private final class Constants extends ContentObserver { 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 int DEFAULT_MIN_IDLE_COUNT = 1; private static final int DEFAULT_MIN_CHARGING_COUNT = 1; @@ -240,6 +249,7 @@ private final class Constants extends ContentObserver { 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 +343,11 @@ private final class Constants extends ContentObserver { */ 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(','); @@ -411,6 +426,12 @@ private void updateConstants() { 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(); + } } } @@ -470,6 +491,9 @@ void dump(PrintWriter pw) { 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 +635,24 @@ private String getPackageName(Intent intent) { 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 +956,8 @@ public JobSchedulerService(Context context) { 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,11 +1038,32 @@ public void onBootPhase(int phase) { 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(); } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { @@ -1040,6 +1096,20 @@ public void process(JobStatus job) { } } + void updateForceAppStandby(boolean enabled) { + synchronized (this) { + if (mForceAppStandby != enabled) { + mForceAppStandby = enabled; + 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 +2388,7 @@ void dumpInternal(final PrintWriter pw, String[] args) { 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)); 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 index 00000000000..968fa956502 --- /dev/null +++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java @@ -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> 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 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 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 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 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); + } + } + } +} diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java index 303b0007e1f..23caa8cfa70 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -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 @@ boolean setDeviceNotDozingConstraintSatisfied(boolean state, boolean whitelisted 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 boolean isReady() { // 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 = diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 74ddfc5edc3..ca7dad7d503 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -63,6 +63,7 @@ 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 @@ private void updateLowPowerMode() { 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 @@ private void updateLowPowerMode() { 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 void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 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 "); diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java index 1781d8c657c..d4a0d447d3a 100644 --- a/services/core/java/com/android/server/power/BatterySaverPolicy.java +++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java @@ -20,15 +20,26 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; +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.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; /** * Class to decide whether to turn on battery saver mode for specific service @@ -44,7 +55,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 +72,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 +102,15 @@ 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 final KeyValueListParser mParser = new KeyValueListParser(','); @@ -164,34 +196,65 @@ 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 ArrayList> mFileOverrides = new ArrayList<>(); + private ArrayList> mFileRestores = new ArrayList<>(); + + private ArrayList> mSecureOverrides = new ArrayList<>(); + private ArrayList> mGlobalOverrides = new ArrayList<>(); + private ArrayList> mSystemOverrides = new ArrayList<>(); + + private static final String SETTING_ORIGINAL_SUFFIX = "_bs_orig"; + private ContentResolver mContentResolver; + private static final String BATTERY_SAVER_CONSTANTS2 = + Settings.Global.BATTERY_SAVER_CONSTANTS + "_om"; + public BatterySaverPolicy(Handler handler) { super(handler); } public void start(ContentResolver contentResolver) { mContentResolver = contentResolver; - mContentResolver.registerContentObserver(Settings.Global.getUriFor( - Settings.Global.BATTERY_SAVER_CONSTANTS), false, this); + BATTERY_SAVER_CONSTANTS2), 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(); } @Override public void onChange(boolean selfChange, Uri uri) { final String value = Settings.Global.getString(mContentResolver, - Settings.Global.BATTERY_SAVER_CONSTANTS); + BATTERY_SAVER_CONSTANTS2); updateConstants(value); + + // Also propagate to BATTERY_SAVER_USE_RED_BAR + Settings.Global.putInt(mContentResolver, Global.BATTERY_SAVER_USE_RED_BAR, + mRedBarEnabled ? 1 : 0); } @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 +267,35 @@ void updateConstants(final String value) { 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); + } + } + + private static void parseOverrides(ArrayList> 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, ""))); + } } } @@ -258,6 +346,11 @@ public PowerSaveState getBatterySaverPolicy(@ServiceType int type, boolean realM 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 +361,9 @@ public PowerSaveState getBatterySaverPolicy(@ServiceType int type, boolean realM public void dump(PrintWriter pw) { pw.println(); pw.println("Battery saver policy"); - pw.println(" Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS); + pw.println(" Settings " + BATTERY_SAVER_CONSTANTS2); pw.println(" value: " + Settings.Global.getString(mContentResolver, - Settings.Global.BATTERY_SAVER_CONSTANTS)); + BATTERY_SAVER_CONSTANTS2)); pw.println(); pw.println(" " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled); @@ -283,5 +376,204 @@ public void dump(PrintWriter pw) { 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 restores=" + mFileRestores); + pw.println(" Secure overrides=" + mSecureOverrides); + pw.println(" Global overrides=" + mGlobalOverrides); + pw.println(" System overrides=" + mSystemOverrides); + } + + // TODO Move it somewhere else. + public void startSaver() { + mFileRestores = new ArrayList<>(); + + Slog.d(TAG, "Starting battery saver..."); + + PowerManagerService.setPowerHintMask(mPowerHintMask); + + // Save current values from the file. + for (Pair 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); + } + } + + writeToFiles(mFileOverrides); + + // Update settings. + final ContentResolver cr = mContentResolver; + applySettings(cr, UserHandle.USER_SYSTEM, mSecureOverrides, SecureSettingsIf.INSTANCE); + applySettings(cr, UserHandle.USER_SYSTEM, mGlobalOverrides, GlobalSettingsIf.INSTANCE); + applySettings(cr, UserHandle.USER_SYSTEM, mSystemOverrides, SystemSettingsIf.INSTANCE); + } + + private static void applySettings(ContentResolver cr, int userId, + ArrayList> overrides, + SettingsIf settings) { + for (Pair setting : overrides) { + final String name = setting.first; + final String value = setting.second; + final String name_orig = name + SETTING_ORIGINAL_SUFFIX; + + 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() { + Slog.d(TAG, "Stopping battery saver..."); + + PowerManagerService.setPowerHintMask(0xffffffff); + + writeToFiles(mFileRestores); + + restoreAllSettings(); + } + + private void restoreAllSettings() { + Slog.d(TAG, "Restoring settings..."); + + // TODO Other users. + final ContentResolver cr = mContentResolver; + restoreSettings(cr, UserHandle.USER_SYSTEM, mSecureOverrides, SecureSettingsIf.INSTANCE); + restoreSettings(cr, UserHandle.USER_SYSTEM, mGlobalOverrides, GlobalSettingsIf.INSTANCE); + restoreSettings(cr, UserHandle.USER_SYSTEM, mSystemOverrides, SystemSettingsIf.INSTANCE); + } + + private static void restoreSettings(ContentResolver cr, int userId, + ArrayList> overrides, + SettingsIf settings) { + for (Pair 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> pendingFilesAndValues; + + private final Runnable mWritePendingFiles = () -> { + writePendingFiles(); // We need to extraact it to a separate method to avoid self-refing. + }; + + private void writeToFiles(ArrayList> 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> files = pendingFilesAndValues; + if (files != null) { + for (Pair 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 diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 0c72326095a..9c462f356ed 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -636,6 +636,12 @@ void dumpProto(ProtoOutputStream proto) { 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; @@ -1014,6 +1020,7 @@ public void run() { 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 void run() { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcastAsUser(intent, UserHandle.ALL, Manifest.permission.DEVICE_POWER); + + if (mLowPowerModeEnabled) { + mBatterySaverPolicy.startSaver(); + } else { + mBatterySaverPolicy.stopSaver(); + } } }); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 1ac0019f1ba..ed442926c88 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -398,7 +398,7 @@ public void onReceive(Context context, Intent intent) { final boolean mHasPermanentDpad; final long mDrawLockTimeoutMillis; - final boolean mAllowAnimationsInLowPowerMode; + // final boolean mAllowAnimationsInLowPowerMode; final boolean mAllowBootMessages; @@ -1039,8 +1039,8 @@ private WindowManagerService(Context context, InputManagerService inputManager, 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 int getServiceType() { public void onLowPowerModeChanged(PowerSaveState result) { synchronized (mWindowMap) { final boolean enabled = result.batterySaverEnabled; - if (mAnimationsDisabled != enabled && !mAllowAnimationsInLowPowerMode) { + if (mAnimationsDisabled != enabled) { mAnimationsDisabled = enabled; dispatchNewAnimatorScaleLocked(null); } diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp index 2db7dbee647..f93321e326f 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.cpp +++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp @@ -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 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 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 lock(gPowerHalMutex); - if (getPowerHal()) { + + if (shouldSendPowerHint(hintId, data) && getPowerHal()) { Return 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) \ diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index 19396d43e15..a6d6b026812 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -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)) diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 59d205ecab7..4729d06c61f 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -50,6 +50,11 @@ + + + + +