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.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.os.BatteryManager;
     24 import android.os.BatteryManagerInternal;
     25 import android.os.SystemClock;
     26 import android.os.UserHandle;
     27 import android.util.Slog;
     28 
     29 import com.android.internal.annotations.VisibleForTesting;
     30 import com.android.server.LocalServices;
     31 import com.android.server.job.JobSchedulerService;
     32 import com.android.server.job.StateChangedListener;
     33 
     34 import java.io.PrintWriter;
     35 import java.util.ArrayList;
     36 import java.util.Iterator;
     37 import java.util.List;
     38 
     39 /**
     40  * Simple controller that tracks whether the phone is charging or not. The phone is considered to
     41  * be charging when it's been plugged in for more than two minutes, and the system has broadcast
     42  * ACTION_BATTERY_OK.
     43  */
     44 public class BatteryController extends StateController {
     45     private static final String TAG = "JobScheduler.Batt";
     46 
     47     private static final Object sCreationLock = new Object();
     48     private static volatile BatteryController sController;
     49 
     50     private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
     51     private ChargingTracker mChargeTracker;
     52 
     53     public static BatteryController get(JobSchedulerService taskManagerService) {
     54         synchronized (sCreationLock) {
     55             if (sController == null) {
     56                 sController = new BatteryController(taskManagerService,
     57                         taskManagerService.getContext(), taskManagerService.getLock());
     58             }
     59         }
     60         return sController;
     61     }
     62 
     63     @VisibleForTesting
     64     public ChargingTracker getTracker() {
     65         return mChargeTracker;
     66     }
     67 
     68     @VisibleForTesting
     69     public static BatteryController getForTesting(StateChangedListener stateChangedListener,
     70                                            Context context) {
     71         return new BatteryController(stateChangedListener, context, new Object());
     72     }
     73 
     74     private BatteryController(StateChangedListener stateChangedListener, Context context,
     75             Object lock) {
     76         super(stateChangedListener, context, lock);
     77         mChargeTracker = new ChargingTracker();
     78         mChargeTracker.startTracking();
     79     }
     80 
     81     @Override
     82     public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
     83         final boolean isOnStablePower = mChargeTracker.isOnStablePower();
     84         if (taskStatus.hasChargingConstraint()) {
     85             mTrackedTasks.add(taskStatus);
     86             taskStatus.setChargingConstraintSatisfied(isOnStablePower);
     87         }
     88     }
     89 
     90     @Override
     91     public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
     92         if (taskStatus.hasChargingConstraint()) {
     93             mTrackedTasks.remove(taskStatus);
     94         }
     95     }
     96 
     97     private void maybeReportNewChargingState() {
     98         final boolean stablePower = mChargeTracker.isOnStablePower();
     99         if (DEBUG) {
    100             Slog.d(TAG, "maybeReportNewChargingState: " + stablePower);
    101         }
    102         boolean reportChange = false;
    103         synchronized (mLock) {
    104             for (JobStatus ts : mTrackedTasks) {
    105                 boolean previous = ts.setChargingConstraintSatisfied(stablePower);
    106                 if (previous != stablePower) {
    107                     reportChange = true;
    108                 }
    109             }
    110         }
    111         // Let the scheduler know that state has changed. This may or may not result in an
    112         // execution.
    113         if (reportChange) {
    114             mStateChangedListener.onControllerStateChanged();
    115         }
    116         // Also tell the scheduler that any ready jobs should be flushed.
    117         if (stablePower) {
    118             mStateChangedListener.onRunJobNow(null);
    119         }
    120     }
    121 
    122     public class ChargingTracker extends BroadcastReceiver {
    123         /**
    124          * Track whether we're "charging", where charging means that we're ready to commit to
    125          * doing work.
    126          */
    127         private boolean mCharging;
    128         /** Keep track of whether the battery is charged enough that we want to do work. */
    129         private boolean mBatteryHealthy;
    130 
    131         public ChargingTracker() {
    132         }
    133 
    134         public void startTracking() {
    135             IntentFilter filter = new IntentFilter();
    136 
    137             // Battery health.
    138             filter.addAction(Intent.ACTION_BATTERY_LOW);
    139             filter.addAction(Intent.ACTION_BATTERY_OKAY);
    140             // Charging/not charging.
    141             filter.addAction(BatteryManager.ACTION_CHARGING);
    142             filter.addAction(BatteryManager.ACTION_DISCHARGING);
    143             mContext.registerReceiver(this, filter);
    144 
    145             // Initialise tracker state.
    146             BatteryManagerInternal batteryManagerInternal =
    147                     LocalServices.getService(BatteryManagerInternal.class);
    148             mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow();
    149             mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
    150         }
    151 
    152         boolean isOnStablePower() {
    153             return mCharging && mBatteryHealthy;
    154         }
    155 
    156         @Override
    157         public void onReceive(Context context, Intent intent) {
    158             onReceiveInternal(intent);
    159         }
    160 
    161         @VisibleForTesting
    162         public void onReceiveInternal(Intent intent) {
    163             final String action = intent.getAction();
    164             if (Intent.ACTION_BATTERY_LOW.equals(action)) {
    165                 if (DEBUG) {
    166                     Slog.d(TAG, "Battery life too low to do work. @ "
    167                             + SystemClock.elapsedRealtime());
    168                 }
    169                 // If we get this action, the battery is discharging => it isn't plugged in so
    170                 // there's no work to cancel. We track this variable for the case where it is
    171                 // charging, but hasn't been for long enough to be healthy.
    172                 mBatteryHealthy = false;
    173             } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
    174                 if (DEBUG) {
    175                     Slog.d(TAG, "Battery life healthy enough to do work. @ "
    176                             + SystemClock.elapsedRealtime());
    177                 }
    178                 mBatteryHealthy = true;
    179                 maybeReportNewChargingState();
    180             } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
    181                 if (DEBUG) {
    182                     Slog.d(TAG, "Received charging intent, fired @ "
    183                             + SystemClock.elapsedRealtime());
    184                 }
    185                 mCharging = true;
    186                 maybeReportNewChargingState();
    187             } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
    188                 if (DEBUG) {
    189                     Slog.d(TAG, "Disconnected from power.");
    190                 }
    191                 mCharging = false;
    192                 maybeReportNewChargingState();
    193             }
    194         }
    195     }
    196 
    197     @Override
    198     public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
    199         pw.print("Battery: stable power = ");
    200         pw.println(mChargeTracker.isOnStablePower());
    201         pw.print("Tracking ");
    202         pw.print(mTrackedTasks.size());
    203         pw.println(":");
    204         for (int i = 0; i < mTrackedTasks.size(); i++) {
    205             final JobStatus js = mTrackedTasks.get(i);
    206             if (!js.shouldDump(filterUid)) {
    207                 continue;
    208             }
    209             pw.print("  #");
    210             js.printUniqueId(pw);
    211             pw.print(" from ");
    212             UserHandle.formatUid(pw, js.getSourceUid());
    213             pw.println();
    214         }
    215     }
    216 }
    217