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) {