1 /*
2  * Copyright (C) 2011 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.tradefed.testtype;
18 
19 import com.android.annotations.VisibleForTesting;
20 import com.android.ddmlib.IDevice;
21 import com.android.tradefed.config.Option;
22 import com.android.tradefed.config.OptionClass;
23 import com.android.tradefed.device.DeviceNotAvailableException;
24 import com.android.tradefed.device.DeviceSelectionOptions;
25 import com.android.tradefed.device.ITestDevice;
26 import com.android.tradefed.log.LogUtil.CLog;
27 import com.android.tradefed.result.ITestInvocationListener;
28 import com.android.tradefed.targetprep.ITargetPreparer;
29 import com.android.tradefed.util.IRunUtil;
30 import com.android.tradefed.util.RunUtil;
31 import com.android.tradefed.util.TimeUtil;
32 
33 import org.junit.Assert;
34 
35 import java.util.concurrent.ExecutionException;
36 import java.util.concurrent.TimeUnit;
37 
38 /**
39  * An {@link ITargetPreparer} that checks for a minimum battery charge, and waits for the battery
40  * to reach a second charging threshold if the minimum charge isn't present.
41  */
42 @OptionClass(alias = "battery-checker")
43 public class DeviceBatteryLevelChecker implements IDeviceTest, IRemoteTest {
44 
45     private ITestDevice mTestDevice = null;
46 
47     /**
48      * We use max-battery here to coincide with a {@link DeviceSelectionOptions} option of the same
49      * name.  Thus, DeviceBatteryLevelChecker
50      */
51     @Option(name = "max-battery", description = "Charge level below which we force the device to " +
52             "sit and charge.  Range: 0-100.")
53     private Integer mMaxBattery = 20;
54 
55     @Option(name = "resume-level", description = "Charge level at which we release the device to " +
56             "begin testing again. Range: 0-100.")
57     private int mResumeLevel = 80;
58 
59     /**
60      * This is decoupled from the log poll time below specifically to allow this invocation to be
61      * killed without having to wait for the full log period to lapse.
62      */
63     @Option(name = "poll-time", description = "Time in minutes to wait between battery level " +
64             "polls. Decimal times accepted.")
65     private double mChargingPollTime = 1.0;
66 
67     @Option(name = "batt-log-period", description = "Min time in minutes to wait between " +
68             "printing current battery level to log.  Decimal times accepted.")
69     private double mLoggingPollTime = 10.0;
70 
71     @Option(name = "reboot-charging-devices", description = "Whether to reboot a device when we " +
72             "detect that it should be held for charging.  This would hopefully kill any battery-" +
73             "draining processes and allow the device to charge at its fastest rate.")
74     private boolean mRebootChargeDevices = false;
75 
76     @Option(name = "stop-runtime", description = "Whether to stop runtime.")
77     private boolean mStopRuntime = false;
78 
79     @Option(name = "stop-logcat", description = "Whether to stop logcat during the recharge. "
80             + "this option is enabled by default.")
81     private boolean mStopLogcat = true;
82 
83     @Option(
84         name = "max-run-time",
85         description = "The max run time the battery level checker can run before stopping.",
86         isTimeVal = true
87     )
88     private long mMaxRunTime = 30 * 60 * 1000L;
89 
checkBatteryLevel(ITestDevice device)90     Integer checkBatteryLevel(ITestDevice device) {
91         try {
92             IDevice idevice = device.getIDevice();
93             // Force a synchronous check, which will also tell us if the device is still alive
94             return idevice.getBattery(500, TimeUnit.MILLISECONDS).get();
95         } catch (InterruptedException | ExecutionException e) {
96             return null;
97         }
98     }
99 
stopDeviceRuntime()100     private void stopDeviceRuntime() throws DeviceNotAvailableException {
101         getDevice().executeShellCommand("stop");
102     }
103 
104     /**
105      * {@inheritDoc}
106      */
107     @Override
run(ITestInvocationListener listener)108     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
109         Assert.assertNotNull(mTestDevice);
110 
111         Integer batteryLevel = checkBatteryLevel(mTestDevice);
112 
113         if (batteryLevel == null) {
114             CLog.w("Failed to determine battery level for device %s.",
115                     mTestDevice.getSerialNumber());
116             return;
117         } else if (batteryLevel < mMaxBattery) {
118             // Time-out.  Send the device to the corner
119             CLog.w("Battery level %d is below the min level %d; holding for device %s to charge " +
120                     "to level %d", batteryLevel, mMaxBattery, mTestDevice.getSerialNumber(),
121                     mResumeLevel);
122         } else {
123             // Good to go
124             CLog.d("Battery level %d is above the minimum of %d; %s is good to go.", batteryLevel,
125                     mMaxBattery, mTestDevice.getSerialNumber());
126             return;
127         }
128 
129         if (mRebootChargeDevices) {
130             // reboot the device, in an attempt to kill any battery-draining processes
131             CLog.d("Rebooting device %s prior to holding", mTestDevice.getSerialNumber());
132             mTestDevice.reboot();
133         }
134 
135         // turn screen off
136         turnScreenOff(mTestDevice);
137 
138         if (mStopRuntime) {
139             stopDeviceRuntime();
140         }
141         // Stop our logcat receiver
142         if (mStopLogcat) {
143             getDevice().stopLogcat();
144         }
145 
146         // If we're down here, it's time to hold the device until it reaches mResumeLevel
147         Long lastReportTime = System.currentTimeMillis();
148         Integer newLevel = checkBatteryLevel(mTestDevice);
149 
150         long startTime = System.currentTimeMillis();
151         while (batteryLevel != null && batteryLevel < mResumeLevel) {
152             if (System.currentTimeMillis() - lastReportTime > mLoggingPollTime * 60 * 1000) {
153                 // Log the battery level status every mLoggingPollTime minutes
154                 CLog.w("Battery level for device %s is currently %d", mTestDevice.getSerialNumber(),
155                         newLevel);
156                 lastReportTime = System.currentTimeMillis();
157             }
158             if (System.currentTimeMillis() - startTime > mMaxRunTime) {
159                 CLog.w(
160                         "DeviceBatteryLevelChecker has been running for %s. terminating.",
161                         TimeUtil.formatElapsedTime(mMaxRunTime));
162                 break;
163             }
164 
165             getRunUtil().sleep((long) (mChargingPollTime * 60 * 1000));
166             newLevel = checkBatteryLevel(mTestDevice);
167             if (newLevel == null) {
168                 // weird
169                 CLog.w("Breaking out of wait loop because battery level read failed for device %s",
170                         mTestDevice.getSerialNumber());
171                 break;
172             } else if (newLevel < batteryLevel) {
173                 // also weird
174                 CLog.w("Warning: battery discharged from %d to %d on device %s during the last " +
175                         "%.02f minutes.", batteryLevel, newLevel, mTestDevice.getSerialNumber(),
176                         mChargingPollTime);
177             } else {
178                 CLog.v("Battery level for device %s is currently %d", mTestDevice.getSerialNumber(),
179                         newLevel);
180             }
181             batteryLevel = newLevel;
182         }
183         CLog.w("Device %s is now charged to battery level %d; releasing.",
184                 mTestDevice.getSerialNumber(), batteryLevel);
185     }
186 
turnScreenOff(ITestDevice device)187     private void turnScreenOff(ITestDevice device) throws DeviceNotAvailableException {
188         // disable always on
189         device.executeShellCommand("svc power stayon false");
190         // set screen timeout to 1s
191         device.executeShellCommand("settings put system screen_off_timeout 1000");
192         // pause for 5s to ensure that screen would be off
193         getRunUtil().sleep(5000);
194     }
195 
196     /**
197      * Get a RunUtil instance
198      *
199      * <p>Exposed for unit testing
200      */
201     @VisibleForTesting
getRunUtil()202     IRunUtil getRunUtil() {
203         return RunUtil.getDefault();
204     }
205 
206     @Override
setDevice(ITestDevice device)207     public void setDevice(ITestDevice device) {
208         mTestDevice = device;
209     }
210 
211     @Override
getDevice()212     public ITestDevice getDevice() {
213         return mTestDevice;
214     }
215 
setResumeLevel(int level)216     protected void setResumeLevel(int level) {
217         mResumeLevel = level;
218     }
219 }
220 
221