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 android.app.AlarmManager;
     20 import android.app.PendingIntent;
     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.SystemClock;
     28 import android.util.Slog;
     29 
     30 import com.android.internal.annotations.VisibleForTesting;
     31 import com.android.server.LocalServices;
     32 import com.android.server.job.JobSchedulerService;
     33 import com.android.server.job.StateChangedListener;
     34 
     35 import java.io.PrintWriter;
     36 import java.util.ArrayList;
     37 import java.util.Iterator;
     38 import java.util.List;
     39 
     40 /**
     41  * Simple controller that tracks whether the phone is charging or not. The phone is considered to
     42  * be charging when it's been plugged in for more than two minutes, and the system has broadcast
     43  * ACTION_BATTERY_OK.
     44  */
     45 public class BatteryController extends StateController {
     46     private static final String TAG = "JobScheduler.Batt";
     47 
     48     private static final Object sCreationLock = new Object();
     49     private static volatile BatteryController sController;
     50     private static final String ACTION_CHARGING_STABLE =
     51             "com.android.server.task.controllers.BatteryController.ACTION_CHARGING_STABLE";
     52     /** Wait this long after phone is plugged in before doing any work. */
     53     private static final long STABLE_CHARGING_THRESHOLD_MILLIS = 2 * 60 * 1000; // 2 minutes.
     54 
     55     private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
     56     private ChargingTracker mChargeTracker;
     57 
     58     public static BatteryController get(JobSchedulerService taskManagerService) {
     59         synchronized (sCreationLock) {
     60             if (sController == null) {
     61                 sController = new BatteryController(taskManagerService,
     62                         taskManagerService.getContext());
     63             }
     64         }
     65         return sController;
     66     }
     67 
     68     @VisibleForTesting
     69     public ChargingTracker getTracker() {
     70         return mChargeTracker;
     71     }
     72 
     73     @VisibleForTesting
     74     public static BatteryController getForTesting(StateChangedListener stateChangedListener,
     75                                            Context context) {
     76         return new BatteryController(stateChangedListener, context);
     77     }
     78 
     79     private BatteryController(StateChangedListener stateChangedListener, Context context) {
     80         super(stateChangedListener, context);
     81         mChargeTracker = new ChargingTracker();
     82         mChargeTracker.startTracking();
     83     }
     84 
     85     @Override
     86     public void maybeStartTrackingJob(JobStatus taskStatus) {
     87         final boolean isOnStablePower = mChargeTracker.isOnStablePower();
     88         if (taskStatus.hasChargingConstraint()) {
     89             synchronized (mTrackedTasks) {
     90                 mTrackedTasks.add(taskStatus);
     91                 taskStatus.chargingConstraintSatisfied.set(isOnStablePower);
     92             }
     93         }
     94         if (isOnStablePower) {
     95             mChargeTracker.setStableChargingAlarm();
     96         }
     97     }
     98 
     99     @Override
    100     public void maybeStopTrackingJob(JobStatus taskStatus) {
    101         if (taskStatus.hasChargingConstraint()) {
    102             synchronized (mTrackedTasks) {
    103                 mTrackedTasks.remove(taskStatus);
    104             }
    105         }
    106     }
    107 
    108     private void maybeReportNewChargingState() {
    109         final boolean stablePower = mChargeTracker.isOnStablePower();
    110         if (DEBUG) {
    111             Slog.d(TAG, "maybeReportNewChargingState: " + stablePower);
    112         }
    113         boolean reportChange = false;
    114         synchronized (mTrackedTasks) {
    115             for (JobStatus ts : mTrackedTasks) {
    116                 boolean previous = ts.chargingConstraintSatisfied.getAndSet(stablePower);
    117                 if (previous != stablePower) {
    118                     reportChange = true;
    119                 }
    120             }
    121         }
    122         // Let the scheduler know that state has changed. This may or may not result in an
    123         // execution.
    124         if (reportChange) {
    125             mStateChangedListener.onControllerStateChanged();
    126         }
    127         // Also tell the scheduler that any ready jobs should be flushed.
    128         if (stablePower) {
    129             mStateChangedListener.onRunJobNow(null);
    130         }
    131     }
    132 
    133     public class ChargingTracker extends BroadcastReceiver {
    134         private final AlarmManager mAlarm;
    135         private final PendingIntent mStableChargingTriggerIntent;
    136         /**
    137          * Track whether we're "charging", where charging means that we're ready to commit to
    138          * doing work.
    139          */
    140         private boolean mCharging;
    141         /** Keep track of whether the battery is charged enough that we want to do work. */
    142         private boolean mBatteryHealthy;
    143 
    144         public ChargingTracker() {
    145             mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
    146             Intent intent = new Intent(ACTION_CHARGING_STABLE);
    147             mStableChargingTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
    148         }
    149 
    150         public void startTracking() {
    151             IntentFilter filter = new IntentFilter();
    152 
    153             // Battery health.
    154             filter.addAction(Intent.ACTION_BATTERY_LOW);
    155             filter.addAction(Intent.ACTION_BATTERY_OKAY);
    156             // Charging/not charging.
    157             filter.addAction(Intent.ACTION_POWER_CONNECTED);
    158             filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
    159             // Charging stable.
    160             filter.addAction(ACTION_CHARGING_STABLE);
    161             mContext.registerReceiver(this, filter);
    162 
    163             // Initialise tracker state.
    164             BatteryManagerInternal batteryManagerInternal =
    165                     LocalServices.getService(BatteryManagerInternal.class);
    166             mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow();
    167             mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
    168         }
    169 
    170         boolean isOnStablePower() {
    171             return mCharging && mBatteryHealthy;
    172         }
    173 
    174         @Override
    175         public void onReceive(Context context, Intent intent) {
    176             onReceiveInternal(intent);
    177         }
    178 
    179         @VisibleForTesting
    180         public void onReceiveInternal(Intent intent) {
    181             final String action = intent.getAction();
    182             if (Intent.ACTION_BATTERY_LOW.equals(action)) {
    183                 if (DEBUG) {
    184                     Slog.d(TAG, "Battery life too low to do work. @ "
    185                             + SystemClock.elapsedRealtime());
    186                 }
    187                 // If we get this action, the battery is discharging => it isn't plugged in so
    188                 // there's no work to cancel. We track this variable for the case where it is
    189                 // charging, but hasn't been for long enough to be healthy.
    190                 mBatteryHealthy = false;
    191             } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
    192                 if (DEBUG) {
    193                     Slog.d(TAG, "Battery life healthy enough to do work. @ "
    194                             + SystemClock.elapsedRealtime());
    195                 }
    196                 mBatteryHealthy = true;
    197                 maybeReportNewChargingState();
    198             } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
    199                 if (DEBUG) {
    200                     Slog.d(TAG, "Received charging intent, setting alarm for "
    201                             + STABLE_CHARGING_THRESHOLD_MILLIS);
    202                 }
    203                 // Set up an alarm for ACTION_CHARGING_STABLE - we don't want to kick off tasks
    204                 // here if the user unplugs the phone immediately.
    205                 setStableChargingAlarm();
    206                 mCharging = true;
    207             } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
    208                 if (DEBUG) {
    209                     Slog.d(TAG, "Disconnected from power, cancelling any set alarms.");
    210                 }
    211                 // If an alarm is set, breathe a sigh of relief and cancel it - crisis averted.
    212                 mAlarm.cancel(mStableChargingTriggerIntent);
    213                 mCharging = false;
    214                 maybeReportNewChargingState();
    215             }else if (ACTION_CHARGING_STABLE.equals(action)) {
    216                 // Here's where we actually do the notify for a task being ready.
    217                 if (DEBUG) {
    218                     Slog.d(TAG, "Stable charging fired @ " + SystemClock.elapsedRealtime()
    219                             + " charging: " + mCharging);
    220                 }
    221                 if (mCharging) {  // Should never receive this intent if mCharging is false.
    222                     maybeReportNewChargingState();
    223                 }
    224             }
    225         }
    226 
    227         void setStableChargingAlarm() {
    228             final long alarmTriggerElapsed =
    229                     SystemClock.elapsedRealtime() + STABLE_CHARGING_THRESHOLD_MILLIS;
    230             if (DEBUG) {
    231                 Slog.d(TAG, "Setting stable alarm to go off in " +
    232                         (STABLE_CHARGING_THRESHOLD_MILLIS / 1000) + "s");
    233             }
    234             mAlarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTriggerElapsed,
    235                     mStableChargingTriggerIntent);
    236         }
    237     }
    238 
    239     @Override
    240     public void dumpControllerState(PrintWriter pw) {
    241         pw.println("Batt.");
    242         pw.println("Stable power: " + mChargeTracker.isOnStablePower());
    243         synchronized (mTrackedTasks) {
    244             Iterator<JobStatus> it = mTrackedTasks.iterator();
    245             if (it.hasNext()) {
    246                 pw.print(String.valueOf(it.next().hashCode()));
    247             }
    248             while (it.hasNext()) {
    249                 pw.print("," + String.valueOf(it.next().hashCode()));
    250             }
    251             pw.println();
    252         }
    253     }
    254 }
    255