Merge "Account for different charging policies." into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index cea16d6..e6ee975 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -318,7 +318,8 @@
private final List<JobRestriction> mJobRestrictions;
@GuardedBy("mLock")
- private final BatteryStateTracker mBatteryStateTracker;
+ @VisibleForTesting
+ final BatteryStateTracker mBatteryStateTracker;
@GuardedBy("mLock")
private final SparseArray<String> mCloudMediaProviderPackages = new SparseArray<>();
@@ -4261,20 +4262,34 @@
.sendToTarget();
}
- private final class BatteryStateTracker extends BroadcastReceiver {
- /**
- * Track whether we're "charging", where charging means that we're ready to commit to
- * doing work.
- */
- private boolean mCharging;
+ @VisibleForTesting
+ final class BatteryStateTracker extends BroadcastReceiver
+ implements BatteryManagerInternal.ChargingPolicyChangeListener {
+ private final BatteryManagerInternal mBatteryManagerInternal;
+
+ /** Last reported battery level. */
+ private int mBatteryLevel;
/** Keep track of whether the battery is charged enough that we want to do work. */
private boolean mBatteryNotLow;
+ /**
+ * Charging status based on {@link BatteryManager#ACTION_CHARGING} and
+ * {@link BatteryManager#ACTION_DISCHARGING}.
+ */
+ private boolean mCharging;
+ /**
+ * The most recently acquired value of
+ * {@link BatteryManager#BATTERY_PROPERTY_CHARGING_POLICY}.
+ */
+ private int mChargingPolicy;
+ /** Track whether there is power connected. It doesn't mean the device is charging. */
+ private boolean mPowerConnected;
/** Sequence number of last broadcast. */
private int mLastBatterySeq = -1;
private BroadcastReceiver mMonitor;
BatteryStateTracker() {
+ mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
}
public void startTracking() {
@@ -4286,13 +4301,18 @@
// Charging/not charging.
filter.addAction(BatteryManager.ACTION_CHARGING);
filter.addAction(BatteryManager.ACTION_DISCHARGING);
+ filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED);
+ filter.addAction(Intent.ACTION_POWER_CONNECTED);
+ filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
getTestableContext().registerReceiver(this, filter);
+ mBatteryManagerInternal.registerChargingPolicyChangeListener(this);
+
// Initialise tracker state.
- BatteryManagerInternal batteryManagerInternal =
- LocalServices.getService(BatteryManagerInternal.class);
- mBatteryNotLow = !batteryManagerInternal.getBatteryLevelLow();
- mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+ mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
+ mBatteryNotLow = !mBatteryManagerInternal.getBatteryLevelLow();
+ mCharging = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+ mChargingPolicy = mBatteryManagerInternal.getChargingPolicy();
}
public void setMonitorBatteryLocked(boolean enabled) {
@@ -4315,7 +4335,7 @@
}
public boolean isCharging() {
- return mCharging;
+ return isConsideredCharging();
}
public boolean isBatteryNotLow() {
@@ -4326,17 +4346,42 @@
return mMonitor != null;
}
+ public boolean isPowerConnected() {
+ return mPowerConnected;
+ }
+
public int getSeq() {
return mLastBatterySeq;
}
@Override
+ public void onChargingPolicyChanged(int newPolicy) {
+ synchronized (mLock) {
+ if (mChargingPolicy == newPolicy) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Charging policy changed from " + mChargingPolicy + " to " + newPolicy);
+ }
+
+ final boolean wasConsideredCharging = isConsideredCharging();
+ mChargingPolicy = newPolicy;
+
+ if (isConsideredCharging() != wasConsideredCharging) {
+ for (int c = mControllers.size() - 1; c >= 0; --c) {
+ mControllers.get(c).onBatteryStateChangedLocked();
+ }
+ }
+ }
+ }
+
+ @Override
public void onReceive(Context context, Intent intent) {
onReceiveInternal(intent);
}
- @VisibleForTesting
- public void onReceiveInternal(Intent intent) {
+ private void onReceiveInternal(Intent intent) {
synchronized (mLock) {
final String action = intent.getAction();
boolean changed = false;
@@ -4356,21 +4401,49 @@
mBatteryNotLow = true;
changed = true;
}
+ } else if (Intent.ACTION_BATTERY_LEVEL_CHANGED.equals(action)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Battery level changed @ "
+ + sElapsedRealtimeClock.millis());
+ }
+ final boolean wasConsideredCharging = isConsideredCharging();
+ mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
+ changed = isConsideredCharging() != wasConsideredCharging;
+ } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Power connected @ " + sElapsedRealtimeClock.millis());
+ }
+ if (mPowerConnected) {
+ return;
+ }
+ mPowerConnected = true;
+ changed = true;
+ } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Power disconnected @ " + sElapsedRealtimeClock.millis());
+ }
+ if (!mPowerConnected) {
+ return;
+ }
+ mPowerConnected = false;
+ changed = true;
} else if (BatteryManager.ACTION_CHARGING.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Battery charging @ " + sElapsedRealtimeClock.millis());
}
if (!mCharging) {
+ final boolean wasConsideredCharging = isConsideredCharging();
mCharging = true;
- changed = true;
+ changed = isConsideredCharging() != wasConsideredCharging;
}
} else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Battery discharging @ " + sElapsedRealtimeClock.millis());
}
if (mCharging) {
+ final boolean wasConsideredCharging = isConsideredCharging();
mCharging = false;
- changed = true;
+ changed = isConsideredCharging() != wasConsideredCharging;
}
}
mLastBatterySeq =
@@ -4382,6 +4455,30 @@
}
}
}
+
+ private boolean isConsideredCharging() {
+ if (mCharging) {
+ return true;
+ }
+ // BatteryService (or Health HAL or whatever central location makes sense)
+ // should ideally hold this logic so that everyone has a consistent
+ // idea of when the device is charging (or an otherwise stable charging/plugged state).
+ // TODO(304512874): move this determination to BatteryService
+ if (!mPowerConnected) {
+ return false;
+ }
+
+ if (mChargingPolicy == Integer.MIN_VALUE) {
+ // Property not supported on this device.
+ return false;
+ }
+ // Adaptive charging policies don't expose their target battery level, but 80% is a
+ // commonly used threshold for battery health, so assume that's what's being used by
+ // the policies and use 70%+ as the threshold here for charging in case some
+ // implementations choose to discharge the device slightly before recharging back up
+ // to the target level.
+ return mBatteryLevel >= 70 && BatteryManager.isAdaptiveChargingPolicy(mChargingPolicy);
+ }
}
final class LocalService implements JobSchedulerInternal {
@@ -5450,6 +5547,13 @@
}
}
+ /** Return {@code true} if the device is connected to power. */
+ public boolean isPowerConnected() {
+ synchronized (mLock) {
+ return mBatteryStateTracker.isPowerConnected();
+ }
+ }
+
int getStorageSeq() {
synchronized (mLock) {
return mStorageController.getTracker().getSeq();
@@ -5778,8 +5882,14 @@
mQuotaTracker.dump(pw);
pw.println();
+ pw.print("Power connected: ");
+ pw.println(mBatteryStateTracker.isPowerConnected());
pw.print("Battery charging: ");
- pw.println(mBatteryStateTracker.isCharging());
+ pw.println(mBatteryStateTracker.mCharging);
+ pw.print("Considered charging: ");
+ pw.println(mBatteryStateTracker.isConsideredCharging());
+ pw.print("Battery level: ");
+ pw.println(mBatteryStateTracker.mBatteryLevel);
pw.print("Battery not low: ");
pw.println(mBatteryStateTracker.isBatteryNotLow());
if (mBatteryStateTracker.isMonitoring()) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index ddbc2ec..e9f9b14 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -20,12 +20,6 @@
import android.annotation.NonNull;
import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.BatteryManager;
-import android.os.BatteryManagerInternal;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
@@ -36,7 +30,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.AppSchedulingModuleThread;
-import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
@@ -60,8 +53,6 @@
@GuardedBy("mLock")
private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
- private final PowerTracker mPowerTracker;
-
private final FlexibilityController mFlexibilityController;
/**
* Helper set to avoid too much GC churn from frequent calls to
@@ -77,16 +68,10 @@
public BatteryController(JobSchedulerService service,
FlexibilityController flexibilityController) {
super(service);
- mPowerTracker = new PowerTracker();
mFlexibilityController = flexibilityController;
}
@Override
- public void startTrackingLocked() {
- mPowerTracker.startTracking();
- }
-
- @Override
public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
if (taskStatus.hasPowerConstraint()) {
final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -95,7 +80,7 @@
if (taskStatus.hasChargingConstraint()) {
if (hasTopExemptionLocked(taskStatus)) {
taskStatus.setChargingConstraintSatisfied(nowElapsed,
- mPowerTracker.isPowerConnected());
+ mService.isPowerConnected());
} else {
taskStatus.setChargingConstraintSatisfied(nowElapsed,
mService.isBatteryCharging() && mService.isBatteryNotLow());
@@ -178,7 +163,7 @@
@GuardedBy("mLock")
private void maybeReportNewChargingStateLocked() {
- final boolean powerConnected = mPowerTracker.isPowerConnected();
+ final boolean powerConnected = mService.isPowerConnected();
final boolean stablePower = mService.isBatteryCharging() && mService.isBatteryNotLow();
final boolean batteryNotLow = mService.isBatteryNotLow();
if (DEBUG) {
@@ -239,62 +224,6 @@
mChangedJobs.clear();
}
- private final class PowerTracker extends BroadcastReceiver {
- /**
- * Track whether there is power connected. It doesn't mean the device is charging.
- * Use {@link JobSchedulerService#isBatteryCharging()} to determine if the device is
- * charging.
- */
- private boolean mPowerConnected;
-
- PowerTracker() {
- }
-
- void startTracking() {
- IntentFilter filter = new IntentFilter();
-
- filter.addAction(Intent.ACTION_POWER_CONNECTED);
- filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
- mContext.registerReceiver(this, filter);
-
- // Initialize tracker state.
- BatteryManagerInternal batteryManagerInternal =
- LocalServices.getService(BatteryManagerInternal.class);
- mPowerConnected = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
- }
-
- boolean isPowerConnected() {
- return mPowerConnected;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized (mLock) {
- final String action = intent.getAction();
-
- if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
- if (DEBUG) {
- Slog.d(TAG, "Power connected @ " + sElapsedRealtimeClock.millis());
- }
- if (mPowerConnected) {
- return;
- }
- mPowerConnected = true;
- } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
- if (DEBUG) {
- Slog.d(TAG, "Power disconnected @ " + sElapsedRealtimeClock.millis());
- }
- if (!mPowerConnected) {
- return;
- }
- mPowerConnected = false;
- }
-
- maybeReportNewChargingStateLocked();
- }
- }
- }
-
@VisibleForTesting
ArraySet<JobStatus> getTrackedJobs() {
return mTrackedTasks;
@@ -308,7 +237,6 @@
@Override
public void dumpControllerStateLocked(IndentingPrintWriter pw,
Predicate<JobStatus> predicate) {
- pw.println("Power connected: " + mPowerTracker.isPowerConnected());
pw.println("Stable power: " + (mService.isBatteryCharging() && mService.isBatteryNotLow()));
pw.println("Not low: " + mService.isBatteryNotLow());
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index b9bb059..f3efd89 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -238,6 +238,16 @@
public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE =
OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
+ /**
+ * Returns true if the policy is some type of adaptive charging policy.
+ * @hide
+ */
+ public static boolean isAdaptiveChargingPolicy(int policy) {
+ return policy == CHARGING_POLICY_ADAPTIVE_AC
+ || policy == CHARGING_POLICY_ADAPTIVE_AON
+ || policy == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+ }
+
// values for "battery part status" property
/**
* Battery part status is not supported.
diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java
index 9bad0de..0ec8729 100644
--- a/core/java/android/os/BatteryManagerInternal.java
+++ b/core/java/android/os/BatteryManagerInternal.java
@@ -16,6 +16,8 @@
package android.os;
+import android.annotation.NonNull;
+
/**
* Battery manager local system service interface.
*
@@ -84,6 +86,26 @@
*/
public abstract boolean getBatteryLevelLow();
+ public interface ChargingPolicyChangeListener {
+ void onChargingPolicyChanged(int newPolicy);
+ }
+
+ /**
+ * Register a listener for changes to {@link BatteryManager#BATTERY_PROPERTY_CHARGING_POLICY}.
+ * The charging policy can't be added to the BATTERY_CHANGED intent because it requires
+ * the BATTERY_STATS permission.
+ */
+ public abstract void registerChargingPolicyChangeListener(
+ @NonNull ChargingPolicyChangeListener chargingPolicyChangeListener);
+
+ /**
+ * Returns the value of {@link BatteryManager#BATTERY_PROPERTY_CHARGING_POLICY}.
+ * This will return {@link Integer#MIN_VALUE} if the device does not support the property.
+ *
+ * @see BatteryManager#getIntProperty(int)
+ */
+ public abstract int getChargingPolicy();
+
/**
* Returns a non-zero value if an unsupported charger is attached.
*
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 9d9e7c9..7979936 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -16,8 +16,8 @@
package com.android.server;
-import static android.os.Flags.stateOfHealthPublic;
import static android.os.Flags.batteryServiceSupportCurrentAdbCommand;
+import static android.os.Flags.stateOfHealthPublic;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import static com.android.server.health.Utils.copyV1Battery;
@@ -81,6 +81,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.NoSuchElementException;
+import java.util.concurrent.CopyOnWriteArraySet;
/**
* <p>BatteryService monitors the charging status, and charge level of the device
@@ -157,6 +158,12 @@
private int mLastChargeCounter;
private int mLastBatteryCycleCount;
private int mLastCharingState;
+ /**
+ * The last seen charging policy. This requires the
+ * {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be
+ * included in the ACTION_BATTERY_CHANGED intent extras.
+ */
+ private int mLastChargingPolicy;
private int mSequence = 1;
@@ -197,6 +204,9 @@
private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
private long mLastBatteryLevelChangedSentMs;
+ private final CopyOnWriteArraySet<BatteryManagerInternal.ChargingPolicyChangeListener>
+ mChargingPolicyChangeListeners = new CopyOnWriteArraySet<>();
+
private Bundle mBatteryChangedOptions = BroadcastOptions.makeBasic()
.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
@@ -527,6 +537,11 @@
shutdownIfNoPowerLocked();
shutdownIfOverTempLocked();
+ if (force || mHealthInfo.chargingPolicy != mLastChargingPolicy) {
+ mLastChargingPolicy = mHealthInfo.chargingPolicy;
+ mHandler.post(this::notifyChargingPolicyChanged);
+ }
+
if (force
|| (mHealthInfo.batteryStatus != mLastBatteryStatus
|| mHealthInfo.batteryHealth != mLastBatteryHealth
@@ -827,6 +842,17 @@
mLastBatteryLevelChangedSentMs = SystemClock.elapsedRealtime();
}
+ private void notifyChargingPolicyChanged() {
+ final int newPolicy;
+ synchronized (mLock) {
+ newPolicy = mLastChargingPolicy;
+ }
+ for (BatteryManagerInternal.ChargingPolicyChangeListener listener
+ : mChargingPolicyChangeListeners) {
+ listener.onChargingPolicyChanged(newPolicy);
+ }
+ }
+
// TODO: Current code doesn't work since "--unplugged" flag in BSS was purposefully removed.
private void logBatteryStatsLocked() {
IBinder batteryInfoService = ServiceManager.getService(BatteryStats.SERVICE_NAME);
@@ -1220,6 +1246,8 @@
pw.println(" voltage: " + mHealthInfo.batteryVoltageMillivolts);
pw.println(" temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius);
pw.println(" technology: " + mHealthInfo.batteryTechnology);
+ pw.println(" Charging state: " + mHealthInfo.chargingState);
+ pw.println(" Charging policy: " + mHealthInfo.chargingPolicy);
} else {
Shell shell = new Shell();
shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
@@ -1452,6 +1480,19 @@
}
@Override
+ public void registerChargingPolicyChangeListener(
+ BatteryManagerInternal.ChargingPolicyChangeListener listener) {
+ mChargingPolicyChangeListeners.add(listener);
+ }
+
+ @Override
+ public int getChargingPolicy() {
+ synchronized (mLock) {
+ return mLastChargingPolicy;
+ }
+ }
+
+ @Override
public int getInvalidCharger() {
synchronized (mLock) {
return mInvalidCharger;
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 3355a6c..fab7610 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -44,6 +44,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -57,6 +58,7 @@
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -65,7 +67,9 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
+import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
+import android.os.BatteryManagerInternal.ChargingPolicyChangeListener;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -89,6 +93,8 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -107,6 +113,8 @@
@Mock
private ActivityManagerInternal mActivityMangerInternal;
@Mock
+ private BatteryManagerInternal mBatteryManagerInternal;
+ @Mock
private Context mContext;
@Mock
private PackageManagerInternal mPackageManagerInternal;
@@ -114,6 +122,8 @@
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ private ChargingPolicyChangeListener mChargingPolicyChangeListener;
+
private class TestJobSchedulerService extends JobSchedulerService {
TestJobSchedulerService(Context context) {
super(context);
@@ -137,7 +147,7 @@
.when(() -> LocalServices.getService(ActivityManagerInternal.class));
doReturn(mock(AppStandbyInternal.class))
.when(() -> LocalServices.getService(AppStandbyInternal.class));
- doReturn(mock(BatteryManagerInternal.class))
+ doReturn(mBatteryManagerInternal)
.when(() -> LocalServices.getService(BatteryManagerInternal.class));
doReturn(mPackageManagerInternal)
.when(() -> LocalServices.getService(PackageManagerInternal.class));
@@ -186,8 +196,17 @@
// Called by DeviceIdlenessTracker
when(mContext.getSystemService(UiModeManager.class)).thenReturn(mock(UiModeManager.class));
+ setChargingPolicy(Integer.MIN_VALUE);
+
+ ArgumentCaptor<ChargingPolicyChangeListener> chargingPolicyChangeListenerCaptor =
+ ArgumentCaptor.forClass(ChargingPolicyChangeListener.class);
+
mService = new TestJobSchedulerService(mContext);
mService.waitOnAsyncLoadingForTesting();
+
+ verify(mBatteryManagerInternal).registerChargingPolicyChangeListener(
+ chargingPolicyChangeListenerCaptor.capture());
+ mChargingPolicyChangeListener = chargingPolicyChangeListenerCaptor.getValue();
}
@After
@@ -1718,6 +1737,127 @@
assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
}
+ @Test
+ public void testBatteryStateTrackerRegistersForImportantIntents() {
+ verify(mContext).registerReceiver(any(), ArgumentMatchers.argThat(filter -> true
+ && filter.hasAction(BatteryManager.ACTION_CHARGING)
+ && filter.hasAction(BatteryManager.ACTION_DISCHARGING)
+ && filter.hasAction(Intent.ACTION_BATTERY_LEVEL_CHANGED)
+ && filter.hasAction(Intent.ACTION_BATTERY_LOW)
+ && filter.hasAction(Intent.ACTION_BATTERY_OKAY)
+ && filter.hasAction(Intent.ACTION_POWER_CONNECTED)
+ && filter.hasAction(Intent.ACTION_POWER_DISCONNECTED)));
+ }
+
+ @Test
+ public void testIsCharging_standardChargingIntent() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ Intent chargingIntent = new Intent(BatteryManager.ACTION_CHARGING);
+ Intent dischargingIntent = new Intent(BatteryManager.ACTION_DISCHARGING);
+ tracker.onReceive(mContext, dischargingIntent);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ tracker.onReceive(mContext, chargingIntent);
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+
+ tracker.onReceive(mContext, dischargingIntent);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+ }
+
+ @Test
+ public void testIsCharging_adaptiveCharging_batteryTooLow() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(15);
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
+
+ setBatteryLevel(70);
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+ }
+
+ @Test
+ public void testIsCharging_adaptiveCharging_chargeBelowThreshold() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
+ setBatteryLevel(5);
+
+ tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_CHARGING));
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+
+ for (int level = 5; level < 80; ++level) {
+ setBatteryLevel(level);
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+ }
+ }
+
+ @Test
+ public void testIsCharging_adaptiveCharging_dischargeAboveThreshold() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
+ setBatteryLevel(80);
+
+ tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
+ assertTrue(tracker.isCharging());
+ assertTrue(mService.isBatteryCharging());
+
+ for (int level = 80; level > 60; --level) {
+ setBatteryLevel(level);
+ assertEquals(level >= 70, tracker.isCharging());
+ assertEquals(level >= 70, mService.isBatteryCharging());
+ }
+ }
+
+ @Test
+ public void testIsCharging_adaptiveCharging_notPluggedIn() {
+ JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+ tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_DISCONNECTED));
+ tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(15);
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(50);
+ setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(70);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(95);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+
+ setBatteryLevel(100);
+ assertFalse(tracker.isCharging());
+ assertFalse(mService.isBatteryCharging());
+ }
+
/** Tests that rare job batching works as expected. */
@Test
public void testConnectivityJobBatching() {
@@ -2257,4 +2397,17 @@
assertFalse(mService.getPendingJobQueue().contains(job2b));
assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b));
}
+
+ private void setBatteryLevel(int level) {
+ doReturn(level).when(mBatteryManagerInternal).getBatteryLevel();
+ mService.mBatteryStateTracker
+ .onReceive(mContext, new Intent(Intent.ACTION_BATTERY_LEVEL_CHANGED));
+ }
+
+ private void setChargingPolicy(int policy) {
+ doReturn(policy).when(mBatteryManagerInternal).getChargingPolicy();
+ if (mChargingPolicyChangeListener != null) {
+ mChargingPolicyChangeListener.onChargingPolicyChanged(policy);
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index d4ef647..ea937de 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -25,14 +25,11 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.Mockito.verify;
import android.app.AppGlobals;
import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.BatteryManagerInternal;
@@ -49,8 +46,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -63,7 +58,6 @@
private BatteryController mBatteryController;
private FlexibilityController mFlexibilityController;
- private BroadcastReceiver mPowerReceiver;
private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
private int mSourceUid;
@@ -99,10 +93,6 @@
.when(() -> LocalServices.getService(PackageManagerInternal.class));
// Initialize real objects.
- // Capture the listeners.
- ArgumentCaptor<BroadcastReceiver> receiverCaptor =
- ArgumentCaptor.forClass(BroadcastReceiver.class);
-
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.hasSystemFeature(
PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
@@ -111,11 +101,6 @@
mBatteryController = new BatteryController(mJobSchedulerService, mFlexibilityController);
mBatteryController.startTrackingLocked();
- verify(mContext).registerReceiver(receiverCaptor.capture(),
- ArgumentMatchers.argThat(filter ->
- filter.hasAction(Intent.ACTION_POWER_CONNECTED)
- && filter.hasAction(Intent.ACTION_POWER_DISCONNECTED)));
- mPowerReceiver = receiverCaptor.getValue();
try {
mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
// Need to do this since we're using a mock JS and not a real object.
@@ -159,9 +144,11 @@
}
private void setPowerConnected(boolean connected) {
- Intent intent = new Intent(
- connected ? Intent.ACTION_POWER_CONNECTED : Intent.ACTION_POWER_DISCONNECTED);
- mPowerReceiver.onReceive(mContext, intent);
+ doReturn(connected).when(mJobSchedulerService).isPowerConnected();
+ synchronized (mBatteryController.mLock) {
+ mBatteryController.onBatteryStateChangedLocked();
+ }
+ waitForNonDelayedMessagesProcessed();
}
private void setUidBias(int uid, int bias) {