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 
     51     private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
     52     private ChargingTracker mChargeTracker;
     53 
     54     public static BatteryController get(JobSchedulerService taskManagerService) {
     55         synchronized (sCreationLock) {
     56             if (sController == null) {
     57                 sController = new BatteryController(taskManagerService,
     58                         taskManagerService.getContext());
     59             }
     60         }
     61         return sController;
     62     }
     63 
     64     @VisibleForTesting
     65     public ChargingTracker getTracker() {
     66         return mChargeTracker;
     67     }
     68 
     69     @VisibleForTesting
     70     public static BatteryController getForTesting(StateChangedListener stateChangedListener,
     71                                            Context context) {
     72         return new BatteryController(stateChangedListener, context);
     73     }
     74 
     75     private BatteryController(StateChangedListener stateChangedListener, Context context) {
     76         super(stateChangedListener, context);
     77         mChargeTracker = new ChargingTracker();
     78         mChargeTracker.startTracking();
     79     }
     80 
     81     @Override
     82     public void maybeStartTrackingJob(JobStatus taskStatus) {
     83         final boolean isOnStablePower = mChargeTracker.isOnStablePower();
     84         if (taskStatus.hasChargingConstraint()) {
     85             synchronized (mTrackedTasks) {
     86                 mTrackedTasks.add(taskStatus);
     87                 taskStatus.chargingConstraintSatisfied.set(isOnStablePower);
     88             }
     89         }
     90     }
     91 
     92     @Override
     93     public void maybeStopTrackingJob(JobStatus taskStatus) {
     94         if (taskStatus.hasChargingConstraint()) {
     95             synchronized (mTrackedTasks) {
     96                 mTrackedTasks.remove(taskStatus);
     97             }
     98         }
     99     }
    100 
    101     private void maybeReportNewChargingState() {
    102         final boolean stablePower = mChargeTracker.isOnStablePower();
    103         if (DEBUG) {
    104             Slog.d(TAG, "maybeReportNewChargingState: " + stablePower);
    105         }
    106         boolean reportChange = false;
    107         synchronized (mTrackedTasks) {
    108             for (JobStatus ts : mTrackedTasks) {
    109                 boolean previous = ts.chargingConstraintSatisfied.getAndSet(stablePower);
    110                 if (previous != stablePower) {
    111                     reportChange = true;
    112                 }
    113             }
    114         }
    115         // Let the scheduler know that state has changed. This may or may not result in an
    116         // execution.
    117         if (reportChange) {
    118             mStateChangedListener.onControllerStateChanged();
    119         }
    120         // Also tell the scheduler that any ready jobs should be flushed.
    121         if (stablePower) {
    122             mStateChangedListener.onRunJobNow(null);
    123         }
    124     }
    125 
    126     public class ChargingTracker extends BroadcastReceiver {
    127         /**
    128          * Track whether we're "charging", where charging means that we're ready to commit to
    129          * doing work.
    130          */
    131         private boolean mCharging;
    132         /** Keep track of whether the battery is charged enough that we want to do work. */
    133         private boolean mBatteryHealthy;
    134 
    135         public ChargingTracker() {
    136         }
    137 
    138         public void startTracking() {
    139             IntentFilter filter = new IntentFilter();
    140 
    141             // Battery health.
    142             filter.addAction(Intent.ACTION_BATTERY_LOW);
    143             filter.addAction(Intent.ACTION_BATTERY_OKAY);
    144             // Charging/not charging.
    145             filter.addAction(BatteryManager.ACTION_CHARGING);
    146             filter.addAction(BatteryManager.ACTION_DISCHARGING);
    147             mContext.registerReceiver(this, filter);
    148 
    149             // Initialise tracker state.
    150             BatteryManagerInternal batteryManagerInternal =
    151                     LocalServices.getService(BatteryManagerInternal.class);
    152             mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow();
    153             mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
    154         }
    155 
    156         boolean isOnStablePower() {
    157             return mCharging && mBatteryHealthy;
    158         }
    159 
    160         @Override
    161         public void onReceive(Context context, Intent intent) {
    162             onReceiveInternal(intent);
    163         }
    164 
    165         @VisibleForTesting
    166         public void onReceiveInternal(Intent intent) {
    167             final String action = intent.getAction();
    168             if (Intent.ACTION_BATTERY_LOW.equals(action)) {
    169                 if (DEBUG) {
    170                     Slog.d(TAG, "Battery life too low to do work. @ "
    171                             + SystemClock.elapsedRealtime());
    172                 }
    173                 // If we get this action, the battery is discharging => it isn't plugged in so
    174                 // there's no work to cancel. We track this variable for the case where it is
    175                 // charging, but hasn't been for long enough to be healthy.
    176                 mBatteryHealthy = false;
    177             } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
    178                 if (DEBUG) {
    179                     Slog.d(TAG, "Battery life healthy enough to do work. @ "
    180                             + SystemClock.elapsedRealtime());
    181                 }
    182                 mBatteryHealthy = true;
    183                 maybeReportNewChargingState();
    184             } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
    185                 if (DEBUG) {
    186                     Slog.d(TAG, "Received charging intent, fired @ "
    187                             + SystemClock.elapsedRealtime());
    188                 }
    189                 mCharging = true;
    190                 maybeReportNewChargingState();
    191             } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
    192                 if (DEBUG) {
    193                     Slog.d(TAG, "Disconnected from power.");
    194                 }
    195                 mCharging = false;
    196                 maybeReportNewChargingState();
    197             }
    198         }
    199     }
    200 
    201     @Override
    202     public void dumpControllerState(PrintWriter pw) {
    203         pw.println("Batt.");
    204         pw.println("Stable power: " + mChargeTracker.isOnStablePower());
    205         synchronized (mTrackedTasks) {
    206             Iterator<JobStatus> it = mTrackedTasks.iterator();
    207             if (it.hasNext()) {
    208                 pw.print(String.valueOf(it.next().hashCode()));
    209             }
    210             while (it.hasNext()) {
    211                 pw.print("," + String.valueOf(it.next().hashCode()));
    212             }
    213             pw.println();
    214         }
    215     }
    216 }
    217