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.tradefed.config.Option; 21 import com.android.tradefed.config.OptionClass; 22 import com.android.tradefed.device.DeviceNotAvailableException; 23 import com.android.tradefed.device.DeviceSelectionOptions; 24 import com.android.tradefed.device.ITestDevice; 25 import com.android.tradefed.invoker.TestInformation; 26 import com.android.tradefed.log.LogUtil.CLog; 27 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 28 import com.android.tradefed.result.FailureDescription; 29 import com.android.tradefed.result.ITestInvocationListener; 30 import com.android.tradefed.result.ITestLifeCycleReceiver; 31 import com.android.tradefed.result.TestDescription; 32 import com.android.tradefed.result.proto.TestRecordProto.FailureStatus; 33 import com.android.tradefed.util.IRunUtil; 34 import com.android.tradefed.util.RunUtil; 35 import com.android.tradefed.util.TimeUtil; 36 37 import org.junit.Assert; 38 39 import java.util.HashMap; 40 41 /** 42 * An {@link IRemoteTest} that checks for a minimum battery charge, and waits for the battery to 43 * reach a second charging threshold if the minimum charge isn't present. 44 */ 45 @OptionClass(alias = "battery-checker") 46 public class DeviceBatteryLevelChecker implements IRemoteTest { 47 private static final Integer IGNORE_CHARGE = -101; 48 49 private ITestDevice mTestDevice = null; 50 private TestDescription mTestDescription = new TestDescription("BatteryCharging", "charge"); 51 private TestDescription mChargingSpeed = new TestDescription("BatteryCharging", "speed"); 52 53 /** 54 * We use max-battery here to coincide with a {@link DeviceSelectionOptions} option of the same 55 * name. Thus, DeviceBatteryLevelChecker 56 */ 57 @Option(name = "max-battery", description = "Charge level below which we force the device to " + 58 "sit and charge. Range: 0-100.") 59 private Integer mMaxBattery = 20; 60 61 @Option(name = "resume-level", description = "Charge level at which we release the device to " + 62 "begin testing again. Range: 0-100.") 63 private int mResumeLevel = 80; 64 65 /** 66 * This is decoupled from the log poll time below specifically to allow this invocation to be 67 * killed without having to wait for the full log period to lapse. 68 */ 69 @Option(name = "poll-time", description = "Time in minutes to wait between battery level " + 70 "polls. Decimal times accepted.") 71 private double mChargingPollTime = 1.0; 72 73 @Option(name = "batt-log-period", description = "Min time in minutes to wait between " + 74 "printing current battery level to log. Decimal times accepted.") 75 private double mLoggingPollTime = 10.0; 76 77 @Option(name = "reboot-charging-devices", description = "Whether to reboot a device when we " + 78 "detect that it should be held for charging. This would hopefully kill any battery-" + 79 "draining processes and allow the device to charge at its fastest rate.") 80 private boolean mRebootChargeDevices = false; 81 82 @Option(name = "stop-runtime", description = "Whether to stop runtime.") 83 private boolean mStopRuntime = false; 84 85 @Option(name = "stop-logcat", description = "Whether to stop logcat during the recharge. " 86 + "this option is enabled by default.") 87 private boolean mStopLogcat = true; 88 89 @Option( 90 name = "max-run-time", 91 description = "The max run time the battery level checker can run before stopping.", 92 isTimeVal = true 93 ) 94 private long mMaxRunTime = 30 * 60 * 1000L; 95 96 @Option( 97 name = "reference-charging-speed", 98 description = "The expected charging speed in % per hours.") 99 private Integer mChargingSpeedCheck = 15; 100 checkBatteryLevel(ITestDevice device)101 Integer checkBatteryLevel(ITestDevice device) { 102 return device.getBattery(); 103 } 104 stopDeviceRuntime()105 private void stopDeviceRuntime() throws DeviceNotAvailableException { 106 mTestDevice.executeShellCommand("stop"); 107 } 108 startDeviceRuntime()109 private void startDeviceRuntime() throws DeviceNotAvailableException { 110 mTestDevice.executeShellCommand("start"); 111 mTestDevice.waitForDeviceAvailable(); 112 } 113 114 /** {@inheritDoc} */ 115 @Override run(TestInformation testInfo, ITestInvocationListener listener)116 public void run(TestInformation testInfo, ITestInvocationListener listener) 117 throws DeviceNotAvailableException { 118 mTestDevice = testInfo.getDevice(); 119 Assert.assertNotNull(mTestDevice); 120 121 long startTime = System.currentTimeMillis(); 122 int testCount = 1; 123 boolean chargeCheck = false; 124 if (mChargingSpeedCheck != null && mChargingSpeedCheck > 0) { 125 testCount++; 126 chargeCheck = true; 127 } 128 listener.testRunStarted("BatteryCharging", testCount); 129 listener.testStarted(mTestDescription); 130 try { 131 Integer charge = null; 132 long elapsedTimeMs = getCurrentTimeMs(); 133 try { 134 charge = runTest(testInfo, listener); 135 elapsedTimeMs = getCurrentTimeMs() - elapsedTimeMs; 136 } catch (DeviceNotAvailableException e) { 137 FailureDescription failure = 138 FailureDescription.create(e.getMessage()) 139 .setCause(e) 140 .setErrorIdentifier(e.getErrorId()) 141 .setOrigin(e.getOrigin()); 142 if (e.getErrorId() != null) { 143 failure.setFailureStatus(e.getErrorId().status()); 144 } 145 listener.testRunFailed(failure); 146 throw e; 147 } finally { 148 listener.testEnded(mTestDescription, new HashMap<String, Metric>()); 149 } 150 if (chargeCheck) { 151 listener.testStarted(mChargingSpeed); 152 if (charge == null) { 153 FailureDescription failure = 154 FailureDescription.create("No battery charge information"); 155 failure.setFailureStatus(FailureStatus.NOT_EXECUTED); 156 listener.testFailed(mChargingSpeed, failure); 157 } else if (IGNORE_CHARGE.equals(charge)) { 158 listener.testIgnored(mChargingSpeed); 159 } else { 160 checkChargingSpeed(listener, charge, elapsedTimeMs); 161 } 162 listener.testEnded(mChargingSpeed, new HashMap<String, Metric>()); 163 } 164 } finally { 165 listener.testRunEnded( 166 System.currentTimeMillis() - startTime, new HashMap<String, Metric>()); 167 } 168 } 169 runTest(TestInformation testInfo, ITestInvocationListener listener)170 private Integer runTest(TestInformation testInfo, ITestInvocationListener listener) 171 throws DeviceNotAvailableException { 172 mTestDevice = testInfo.getDevice(); 173 Assert.assertNotNull(mTestDevice); 174 175 Integer batteryLevel = checkBatteryLevel(mTestDevice); 176 177 if (batteryLevel == null) { 178 CLog.w("Failed to determine battery level for device %s.", 179 mTestDevice.getSerialNumber()); 180 listener.testFailed( 181 mTestDescription, 182 FailureDescription.create("Failed to determine battery level")); 183 return null; 184 } else if (batteryLevel < mMaxBattery) { 185 // Time-out. Send the device to the corner 186 CLog.w("Battery level %d is below the min level %d; holding for device %s to charge " + 187 "to level %d", batteryLevel, mMaxBattery, mTestDevice.getSerialNumber(), 188 mResumeLevel); 189 } else { 190 // Good to go 191 CLog.d("Battery level %d is above the minimum of %d; %s is good to go.", batteryLevel, 192 mMaxBattery, mTestDevice.getSerialNumber()); 193 return IGNORE_CHARGE; 194 } 195 196 if (mRebootChargeDevices) { 197 // reboot the device, in an attempt to kill any battery-draining processes 198 CLog.d("Rebooting device %s prior to holding", mTestDevice.getSerialNumber()); 199 mTestDevice.reboot(); 200 } 201 202 // turn screen off 203 turnScreenOff(mTestDevice); 204 205 Integer finalBattery = null; 206 try { 207 if (mStopRuntime) { 208 stopDeviceRuntime(); 209 } 210 // Stop our logcat receiver 211 if (mStopLogcat) { 212 mTestDevice.stopLogcat(); 213 } 214 215 finalBattery = runBatteryCharging(listener, mTestDescription); 216 } finally { 217 if (mStopRuntime) { 218 // Restart runtime if it was stopped 219 startDeviceRuntime(); 220 } 221 } 222 CLog.i( 223 "Device %s is now charged to battery level %d; releasing.", 224 mTestDevice.getSerialNumber(), batteryLevel); 225 if (finalBattery != null) { 226 return finalBattery - batteryLevel; 227 } 228 return null; 229 } 230 turnScreenOff(ITestDevice device)231 private void turnScreenOff(ITestDevice device) throws DeviceNotAvailableException { 232 // TODO: Handle the case where framework is not working, both command below require it. 233 // disable always on 234 device.executeShellCommand("svc power stayon false"); 235 // set screen timeout to 1s 236 device.executeShellCommand("settings put system screen_off_timeout 1000"); 237 // pause for 5s to ensure that screen would be off 238 getRunUtil().sleep(5000); 239 } 240 runBatteryCharging(ITestLifeCycleReceiver listener, TestDescription test)241 private Integer runBatteryCharging(ITestLifeCycleReceiver listener, TestDescription test) { 242 // If we're down here, it's time to hold the device until it reaches mResumeLevel 243 Long lastReportTime = System.currentTimeMillis(); 244 Integer batteryLevel = checkBatteryLevel(mTestDevice); 245 246 long startTime = System.currentTimeMillis(); 247 while (batteryLevel != null && batteryLevel < mResumeLevel) { 248 if (System.currentTimeMillis() - lastReportTime > mLoggingPollTime * 60 * 1000) { 249 // Log the battery level status every mLoggingPollTime minutes 250 CLog.i( 251 "Battery level for device %s is currently %d", 252 mTestDevice.getSerialNumber(), batteryLevel); 253 lastReportTime = System.currentTimeMillis(); 254 } 255 if (System.currentTimeMillis() - startTime > mMaxRunTime) { 256 CLog.i( 257 "DeviceBatteryLevelChecker has been running for %s. terminating.", 258 TimeUtil.formatElapsedTime(mMaxRunTime)); 259 break; 260 } 261 262 getRunUtil().sleep((long) (mChargingPollTime * 60 * 1000)); 263 Integer newLevel = checkBatteryLevel(mTestDevice); 264 if (newLevel == null) { 265 // weird 266 CLog.w("Breaking out of wait loop because battery level read failed for device %s", 267 mTestDevice.getSerialNumber()); 268 listener.testFailed( 269 test, FailureDescription.create("Failed to read battery level")); 270 return null; 271 } else if (newLevel < batteryLevel) { 272 // also weird 273 CLog.w("Warning: battery discharged from %d to %d on device %s during the last " + 274 "%.02f minutes.", batteryLevel, newLevel, mTestDevice.getSerialNumber(), 275 mChargingPollTime); 276 } else { 277 CLog.v("Battery level for device %s is currently %d", mTestDevice.getSerialNumber(), 278 newLevel); 279 } 280 batteryLevel = newLevel; 281 } 282 return batteryLevel; 283 } 284 checkChargingSpeed( ITestInvocationListener listener, Integer charge, long chargingTime)285 private void checkChargingSpeed( 286 ITestInvocationListener listener, Integer charge, long chargingTime) { 287 double speedPerHours = (charge / ((double) chargingTime / 3600)); 288 if (speedPerHours < mChargingSpeedCheck) { 289 listener.testFailed( 290 mChargingSpeed, 291 FailureDescription.create( 292 String.format( 293 "Device charged %s%% in %s = %s%%/hours. This is below %s", 294 charge, 295 TimeUtil.formatElapsedTime(chargingTime), 296 speedPerHours, 297 mChargingSpeedCheck))); 298 mTestDevice.logBugreport("low-charging-speed-bugreport", listener); 299 } 300 CLog.d("Device charged %s%% in %s", charge, TimeUtil.formatElapsedTime(chargingTime)); 301 } 302 303 /** 304 * Get a RunUtil instance 305 * 306 * <p>Exposed for unit testing 307 */ 308 @VisibleForTesting getRunUtil()309 IRunUtil getRunUtil() { 310 return RunUtil.getDefault(); 311 } 312 setResumeLevel(int level)313 protected void setResumeLevel(int level) { 314 mResumeLevel = level; 315 } 316 317 @VisibleForTesting getCurrentTimeMs()318 long getCurrentTimeMs() { 319 return System.currentTimeMillis(); 320 } 321 } 322 323