Home | History | Annotate | Download | only in controllers
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.server.job.controllers;
     18 
     19 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
     20 
     21 import android.content.BroadcastReceiver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.os.BatteryManager;
     26 import android.os.BatteryManagerInternal;
     27 import android.os.UserHandle;
     28 import android.util.ArraySet;
     29 import android.util.Log;
     30 import android.util.Slog;
     31 import android.util.proto.ProtoOutputStream;
     32 
     33 import com.android.internal.annotations.VisibleForTesting;
     34 import com.android.internal.util.IndentingPrintWriter;
     35 import com.android.server.LocalServices;
     36 import com.android.server.job.JobSchedulerService;
     37 import com.android.server.job.StateControllerProto;
     38 
     39 import java.util.function.Predicate;
     40 
     41 /**
     42  * Simple controller that tracks whether the phone is charging or not. The phone is considered to
     43  * be charging when it's been plugged in for more than two minutes, and the system has broadcast
     44  * ACTION_BATTERY_OK.
     45  */
     46 public final class BatteryController extends StateController {
     47     private static final String TAG = "JobScheduler.Battery";
     48     private static final boolean DEBUG = JobSchedulerService.DEBUG
     49             || Log.isLoggable(TAG, Log.DEBUG);
     50 
     51     private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
     52     private ChargingTracker mChargeTracker;
     53 
     54     @VisibleForTesting
     55     public ChargingTracker getTracker() {
     56         return mChargeTracker;
     57     }
     58 
     59     public BatteryController(JobSchedulerService service) {
     60         super(service);
     61         mChargeTracker = new ChargingTracker();
     62         mChargeTracker.startTracking();
     63     }
     64 
     65     @Override
     66     public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
     67         if (taskStatus.hasPowerConstraint()) {
     68             mTrackedTasks.add(taskStatus);
     69             taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY);
     70             taskStatus.setChargingConstraintSatisfied(mChargeTracker.isOnStablePower());
     71             taskStatus.setBatteryNotLowConstraintSatisfied(mChargeTracker.isBatteryNotLow());
     72         }
     73     }
     74 
     75     @Override
     76     public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
     77         if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) {
     78             mTrackedTasks.remove(taskStatus);
     79         }
     80     }
     81 
     82     private void maybeReportNewChargingStateLocked() {
     83         final boolean stablePower = mChargeTracker.isOnStablePower();
     84         final boolean batteryNotLow = mChargeTracker.isBatteryNotLow();
     85         if (DEBUG) {
     86             Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower);
     87         }
     88         boolean reportChange = false;
     89         for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
     90             final JobStatus ts = mTrackedTasks.valueAt(i);
     91             boolean previous = ts.setChargingConstraintSatisfied(stablePower);
     92             if (previous != stablePower) {
     93                 reportChange = true;
     94             }
     95             previous = ts.setBatteryNotLowConstraintSatisfied(batteryNotLow);
     96             if (previous != batteryNotLow) {
     97                 reportChange = true;
     98             }
     99         }
    100         if (stablePower || batteryNotLow) {
    101             // If one of our conditions has been satisfied, always schedule any newly ready jobs.
    102             mStateChangedListener.onRunJobNow(null);
    103         } else if (reportChange) {
    104             // Otherwise, just let the job scheduler know the state has changed and take care of it
    105             // as it thinks is best.
    106             mStateChangedListener.onControllerStateChanged();
    107         }
    108     }
    109 
    110     public final class ChargingTracker extends BroadcastReceiver {
    111         /**
    112          * Track whether we're "charging", where charging means that we're ready to commit to
    113          * doing work.
    114          */
    115         private boolean mCharging;
    116         /** Keep track of whether the battery is charged enough that we want to do work. */
    117         private boolean mBatteryHealthy;
    118         /** Sequence number of last broadcast. */
    119         private int mLastBatterySeq = -1;
    120 
    121         private BroadcastReceiver mMonitor;
    122 
    123         public ChargingTracker() {
    124         }
    125 
    126         public void startTracking() {
    127             IntentFilter filter = new IntentFilter();
    128 
    129             // Battery health.
    130             filter.addAction(Intent.ACTION_BATTERY_LOW);
    131             filter.addAction(Intent.ACTION_BATTERY_OKAY);
    132             // Charging/not charging.
    133             filter.addAction(BatteryManager.ACTION_CHARGING);
    134             filter.addAction(BatteryManager.ACTION_DISCHARGING);
    135             mContext.registerReceiver(this, filter);
    136 
    137             // Initialise tracker state.
    138             BatteryManagerInternal batteryManagerInternal =
    139                     LocalServices.getService(BatteryManagerInternal.class);
    140             mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow();
    141             mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
    142         }
    143 
    144         public void setMonitorBatteryLocked(boolean enabled) {
    145             if (enabled) {
    146                 if (mMonitor == null) {
    147                     mMonitor = new BroadcastReceiver() {
    148                         @Override public void onReceive(Context context, Intent intent) {
    149                             ChargingTracker.this.onReceive(context, intent);
    150                         }
    151                     };
    152                     IntentFilter filter = new IntentFilter();
    153                     filter.addAction(Intent.ACTION_BATTERY_CHANGED);
    154                     mContext.registerReceiver(mMonitor, filter);
    155                 }
    156             } else {
    157                 if (mMonitor != null) {
    158                     mContext.unregisterReceiver(mMonitor);
    159                     mMonitor = null;
    160                 }
    161             }
    162         }
    163 
    164         public boolean isOnStablePower() {
    165             return mCharging && mBatteryHealthy;
    166         }
    167 
    168         public boolean isBatteryNotLow() {
    169             return mBatteryHealthy;
    170         }
    171 
    172         public boolean isMonitoring() {
    173             return mMonitor != null;
    174         }
    175 
    176         public int getSeq() {
    177             return mLastBatterySeq;
    178         }
    179 
    180         @Override
    181         public void onReceive(Context context, Intent intent) {
    182             onReceiveInternal(intent);
    183         }
    184 
    185         @VisibleForTesting
    186         public void onReceiveInternal(Intent intent) {
    187             synchronized (mLock) {
    188                 final String action = intent.getAction();
    189                 if (Intent.ACTION_BATTERY_LOW.equals(action)) {
    190                     if (DEBUG) {
    191                         Slog.d(TAG, "Battery life too low to do work. @ "
    192                                 + sElapsedRealtimeClock.millis());
    193                     }
    194                     // If we get this action, the battery is discharging => it isn't plugged in so
    195                     // there's no work to cancel. We track this variable for the case where it is
    196                     // charging, but hasn't been for long enough to be healthy.
    197                     mBatteryHealthy = false;
    198                     maybeReportNewChargingStateLocked();
    199                 } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
    200                     if (DEBUG) {
    201                         Slog.d(TAG, "Battery life healthy enough to do work. @ "
    202                                 + sElapsedRealtimeClock.millis());
    203                     }
    204                     mBatteryHealthy = true;
    205                     maybeReportNewChargingStateLocked();
    206                 } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
    207                     if (DEBUG) {
    208                         Slog.d(TAG, "Received charging intent, fired @ "
    209                                 + sElapsedRealtimeClock.millis());
    210                     }
    211                     mCharging = true;
    212                     maybeReportNewChargingStateLocked();
    213                 } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
    214                     if (DEBUG) {
    215                         Slog.d(TAG, "Disconnected from power.");
    216                     }
    217                     mCharging = false;
    218                     maybeReportNewChargingStateLocked();
    219                 }
    220                 mLastBatterySeq = intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE,
    221                         mLastBatterySeq);
    222             }
    223         }
    224     }
    225 
    226     @Override
    227     public void dumpControllerStateLocked(IndentingPrintWriter pw,
    228             Predicate<JobStatus> predicate) {
    229         pw.println("Stable power: " + mChargeTracker.isOnStablePower());
    230         pw.println("Not low: " + mChargeTracker.isBatteryNotLow());
    231 
    232         if (mChargeTracker.isMonitoring()) {
    233             pw.print("MONITORING: seq=");
    234             pw.println(mChargeTracker.getSeq());
    235         }
    236         pw.println();
    237 
    238         for (int i = 0; i < mTrackedTasks.size(); i++) {
    239             final JobStatus js = mTrackedTasks.valueAt(i);
    240             if (!predicate.test(js)) {
    241                 continue;
    242             }
    243             pw.print("#");
    244             js.printUniqueId(pw);
    245             pw.print(" from ");
    246             UserHandle.formatUid(pw, js.getSourceUid());
    247             pw.println();
    248         }
    249     }
    250 
    251     @Override
    252     public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
    253             Predicate<JobStatus> predicate) {
    254         final long token = proto.start(fieldId);
    255         final long mToken = proto.start(StateControllerProto.BATTERY);
    256 
    257         proto.write(StateControllerProto.BatteryController.IS_ON_STABLE_POWER,
    258                 mChargeTracker.isOnStablePower());
    259         proto.write(StateControllerProto.BatteryController.IS_BATTERY_NOT_LOW,
    260                 mChargeTracker.isBatteryNotLow());
    261 
    262         proto.write(StateControllerProto.BatteryController.IS_MONITORING,
    263                 mChargeTracker.isMonitoring());
    264         proto.write(StateControllerProto.BatteryController.LAST_BROADCAST_SEQUENCE_NUMBER,
    265                 mChargeTracker.getSeq());
    266 
    267         for (int i = 0; i < mTrackedTasks.size(); i++) {
    268             final JobStatus js = mTrackedTasks.valueAt(i);
    269             if (!predicate.test(js)) {
    270                 continue;
    271             }
    272             final long jsToken = proto.start(StateControllerProto.BatteryController.TRACKED_JOBS);
    273             js.writeToShortProto(proto, StateControllerProto.BatteryController.TrackedJob.INFO);
    274             proto.write(StateControllerProto.BatteryController.TrackedJob.SOURCE_UID,
    275                     js.getSourceUid());
    276             proto.end(jsToken);
    277         }
    278 
    279         proto.end(mToken);
    280         proto.end(token);
    281     }
    282 }
    283