1 /* 2 * Copyright (C) 2017 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.cts.device.batterystats; 18 19 import android.accounts.Account; 20 import android.app.Activity; 21 import android.app.ActivityManager; 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.le.BluetoothLeScanner; 24 import android.bluetooth.le.ScanCallback; 25 import android.bluetooth.le.ScanResult; 26 import android.bluetooth.le.ScanSettings; 27 import android.content.BroadcastReceiver; 28 import android.app.job.JobInfo; 29 import android.app.job.JobScheduler; 30 import android.content.ComponentName; 31 import android.content.ContentResolver; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.IntentFilter; 35 import android.graphics.Color; 36 import android.graphics.Point; 37 import android.location.Location; 38 import android.location.LocationListener; 39 import android.location.LocationManager; 40 import android.os.AsyncTask; 41 import android.os.Bundle; 42 import android.os.Handler; 43 import android.os.HandlerThread; 44 import android.os.Looper; 45 import android.util.Log; 46 import android.view.Gravity; 47 import android.view.View; 48 import android.view.ViewGroup; 49 import android.view.WindowManager; 50 51 52 import org.junit.Assert; 53 54 import java.util.List; 55 import java.util.concurrent.CountDownLatch; 56 import java.util.concurrent.TimeUnit; 57 58 public class BatteryStatsBgVsFgActions { 59 private static final String TAG = BatteryStatsBgVsFgActions.class.getSimpleName(); 60 61 private static final int DO_NOTHING_TIMEOUT = 2000; 62 63 public static final String KEY_ACTION = "action"; 64 public static final String ACTION_BLE_SCAN_OPTIMIZED = "action.ble_scan_optimized"; 65 public static final String ACTION_BLE_SCAN_UNOPTIMIZED = "action.ble_scan_unoptimized"; 66 public static final String ACTION_JOB_SCHEDULE = "action.jobs"; 67 public static final String ACTION_SYNC = "action.sync"; 68 public static final String ACTION_SLEEP_WHILE_BACKGROUND = "action.sleep_background"; 69 public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top"; 70 public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay"; 71 72 public static final String KEY_REQUEST_CODE = "request_code"; 73 74 /** Number of times to check that app is in correct state before giving up. */ 75 public static final int PROC_STATE_CHECK_ATTEMPTS = 10; 76 77 /** Number of times to check that Bluetooth is enabled before giving up. */ 78 public static final int BT_ENABLE_ATTEMPTS = 8; 79 80 /** Perform the action specified by the given action code (see constants above). */ doAction(Context ctx, String actionCode, String requestCode)81 public static void doAction(Context ctx, String actionCode, String requestCode) { 82 if (actionCode == null) { 83 Log.e(TAG, "Intent was missing action."); 84 return; 85 } 86 sleep(100); 87 switch (actionCode) { 88 case ACTION_BLE_SCAN_OPTIMIZED: 89 doOptimizedBleScan(ctx, requestCode); 90 break; 91 case ACTION_BLE_SCAN_UNOPTIMIZED: 92 doUnoptimizedBleScan(ctx, requestCode); 93 break; 94 case ACTION_JOB_SCHEDULE: 95 doScheduleJob(ctx, requestCode); 96 break; 97 case ACTION_SYNC: 98 doSync(ctx, requestCode); 99 break; 100 case ACTION_SLEEP_WHILE_BACKGROUND: 101 sleep(DO_NOTHING_TIMEOUT); 102 tellHostActionFinished(ACTION_SLEEP_WHILE_BACKGROUND, requestCode); 103 break; 104 case ACTION_SLEEP_WHILE_TOP: 105 doNothingAsync(ctx, ACTION_SLEEP_WHILE_TOP, requestCode); 106 break; 107 case ACTION_SHOW_APPLICATION_OVERLAY: 108 showApplicationOverlay(ctx, requestCode); 109 break; 110 default: 111 Log.e(TAG, "Intent had invalid action"); 112 } 113 sleep(100); 114 } 115 showApplicationOverlay(Context ctx, String requestCode)116 private static void showApplicationOverlay(Context ctx, String requestCode) { 117 final WindowManager wm = ctx.getSystemService(WindowManager.class); 118 Point size = new Point(); 119 wm.getDefaultDisplay().getSize(size); 120 121 WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams( 122 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, 123 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 124 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 125 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); 126 wmlp.width = size.x / 4; 127 wmlp.height = size.y / 4; 128 wmlp.gravity = Gravity.CENTER | Gravity.LEFT; 129 wmlp.setTitle(ctx.getPackageName()); 130 131 ViewGroup.LayoutParams vglp = new ViewGroup.LayoutParams( 132 ViewGroup.LayoutParams.MATCH_PARENT, 133 ViewGroup.LayoutParams.MATCH_PARENT); 134 135 View v = new View(ctx); 136 v.setBackgroundColor(Color.GREEN); 137 v.setLayoutParams(vglp); 138 wm.addView(v, wmlp); 139 140 tellHostActionFinished(ACTION_SHOW_APPLICATION_OVERLAY, requestCode); 141 } 142 doOptimizedBleScan(Context ctx, String requestCode)143 private static void doOptimizedBleScan(Context ctx, String requestCode) { 144 ScanSettings scanSettings = new ScanSettings.Builder() 145 .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC).build(); 146 performBleScan(scanSettings); 147 tellHostActionFinished(ACTION_BLE_SCAN_OPTIMIZED, requestCode); 148 } 149 doUnoptimizedBleScan(Context ctx, String requestCode)150 private static void doUnoptimizedBleScan(Context ctx, String requestCode) { 151 ScanSettings scanSettings = new ScanSettings.Builder() 152 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(); 153 performBleScan(scanSettings); 154 tellHostActionFinished(ACTION_BLE_SCAN_UNOPTIMIZED, requestCode); 155 } 156 performBleScan(ScanSettings scanSettings)157 private static void performBleScan(ScanSettings scanSettings) { 158 BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 159 if (bluetoothAdapter == null) { 160 Log.e(TAG, "Device does not support Bluetooth"); 161 return; 162 } 163 boolean bluetoothEnabledByTest = false; 164 if (!bluetoothAdapter.isEnabled()) { 165 if (!bluetoothAdapter.enable()) { 166 Log.e(TAG, "Bluetooth is not enabled"); 167 return; 168 } 169 for (int attempt = 0; attempt < BT_ENABLE_ATTEMPTS; attempt++) { 170 if (bluetoothAdapter.isEnabled()) { 171 break; 172 } else { 173 if (attempt < BT_ENABLE_ATTEMPTS - 1) { 174 sleep(1_000); 175 } else { 176 throw new RuntimeException("Bluetooth enable failed."); 177 } 178 } 179 } 180 bluetoothEnabledByTest = true; 181 } 182 183 BluetoothLeScanner bleScanner = bluetoothAdapter.getBluetoothLeScanner(); 184 if (bleScanner == null) { 185 Log.e(TAG, "Cannot access BLE scanner"); 186 return; 187 } 188 189 ScanCallback scanCallback = new ScanCallback() { 190 @Override 191 public void onScanResult(int callbackType, ScanResult result) { 192 Log.v(TAG, "called onScanResult"); 193 } 194 195 @Override 196 public void onScanFailed(int errorCode) { 197 Log.v(TAG, "called onScanFailed"); 198 } 199 200 @Override 201 public void onBatchScanResults(List<ScanResult> results) { 202 Log.v(TAG, "called onBatchScanResults"); 203 } 204 }; 205 206 bleScanner.startScan(null, scanSettings, scanCallback); 207 sleep(2_000); 208 bleScanner.stopScan(scanCallback); 209 210 // Restore adapter state at end of test 211 if (bluetoothEnabledByTest) { 212 bluetoothAdapter.disable(); 213 } 214 } 215 doScheduleJob(Context ctx, String requestCode)216 private static void doScheduleJob(Context ctx, String requestCode) { 217 final ComponentName JOB_COMPONENT_NAME = 218 new ComponentName("com.android.server.cts.device.batterystats", 219 SimpleJobService.class.getName()); 220 JobScheduler js = ctx.getSystemService(JobScheduler.class); 221 if (js == null) { 222 Log.e(TAG, "JobScheduler service not available"); 223 tellHostActionFinished(ACTION_JOB_SCHEDULE, requestCode); 224 return; 225 } 226 final JobInfo job = (new JobInfo.Builder(1, JOB_COMPONENT_NAME)) 227 .setOverrideDeadline(0) 228 .build(); 229 CountDownLatch latch = SimpleJobService.resetCountDownLatch(); 230 js.schedule(job); 231 // Job starts in main thread so wait in another thread to see if job finishes. 232 new AsyncTask<Void, Void, Void>() { 233 @Override 234 protected Void doInBackground(Void... params) { 235 waitForReceiver(null, 60_000, latch, null); 236 tellHostActionFinished(ACTION_JOB_SCHEDULE, requestCode); 237 return null; 238 } 239 }.execute(); 240 } 241 doNothingAsync(Context ctx, String requestCode, String actionCode)242 private static void doNothingAsync(Context ctx, String requestCode, String actionCode) { 243 new AsyncTask<Void, Void, Void>() { 244 @Override 245 protected Void doInBackground(Void... params) { 246 sleep(DO_NOTHING_TIMEOUT); 247 return null; 248 } 249 250 @Override 251 protected void onPostExecute(Void nothing) { 252 if (ctx instanceof Activity) { 253 ((Activity) ctx).finish(); 254 tellHostActionFinished(actionCode, requestCode); 255 } 256 } 257 }.execute(); 258 } 259 doSync(Context ctx, String requestCode)260 private static void doSync(Context ctx, String requestCode) { 261 BatteryStatsAuthenticator.removeAllAccounts(ctx); 262 final Account account = BatteryStatsAuthenticator.getTestAccount(); 263 // Create the test account. 264 BatteryStatsAuthenticator.ensureTestAccount(ctx); 265 // Force set is syncable. 266 ContentResolver.setMasterSyncAutomatically(true); 267 ContentResolver.setIsSyncable(account, BatteryStatsProvider.AUTHORITY, 1); 268 269 new AsyncTask<Void, Void, Void>() { 270 @Override 271 protected Void doInBackground(Void... params) { 272 try { 273 Log.v(TAG, "Starting sync"); 274 BatteryStatsSyncAdapter.requestSync(account); 275 sleep(500); 276 } catch (Exception e) { 277 Log.e(TAG, "Exception trying to sync", e); 278 } 279 BatteryStatsAuthenticator.removeAllAccounts(ctx); 280 return null; 281 } 282 283 @Override 284 protected void onPostExecute(Void aVoid) { 285 super.onPostExecute(aVoid); 286 Log.v(TAG, "Finished sync method"); 287 // If ctx is an Activity, finish it when sync is done. If it's a service, don't. 288 if (ctx instanceof Activity) { 289 ((Activity) ctx).finish(); 290 } 291 tellHostActionFinished(ACTION_SYNC, requestCode); 292 } 293 }.execute(); 294 } 295 296 /** Register receiver to determine when given action is complete. */ registerReceiver( Context ctx, CountDownLatch onReceiveLatch, IntentFilter intentFilter)297 private static BroadcastReceiver registerReceiver( 298 Context ctx, CountDownLatch onReceiveLatch, IntentFilter intentFilter) { 299 BroadcastReceiver receiver = new BroadcastReceiver() { 300 @Override 301 public void onReceive(Context context, Intent intent) { 302 onReceiveLatch.countDown(); 303 } 304 }; 305 // run Broadcast receiver in a different thread since the foreground activity will wait. 306 HandlerThread handlerThread = new HandlerThread("br_handler_thread"); 307 handlerThread.start(); 308 Looper looper = handlerThread.getLooper(); 309 Handler handler = new Handler(looper); 310 ctx.registerReceiver(receiver, intentFilter, null, handler); 311 return receiver; 312 } 313 314 /** 315 * Uses the receiver to wait until the action is complete. ctx and receiver may be null if no 316 * receiver is needed to be unregistered. 317 */ waitForReceiver(Context ctx, int maxWaitTimeMs, CountDownLatch latch, BroadcastReceiver receiver)318 private static void waitForReceiver(Context ctx, 319 int maxWaitTimeMs, CountDownLatch latch, BroadcastReceiver receiver) { 320 try { 321 boolean didFinish = latch.await(maxWaitTimeMs, TimeUnit.MILLISECONDS); 322 if (didFinish) { 323 Log.v(TAG, "Finished performing action"); 324 } else { 325 // This is not necessarily a problem. If we just want to make sure a count was 326 // recorded for the request, it doesn't matter if the action actually finished. 327 Log.w(TAG, "Did not finish in specified time."); 328 } 329 } catch (InterruptedException e) { 330 Log.e(TAG, "Interrupted exception while awaiting action to finish", e); 331 } 332 if (ctx != null && receiver != null) { 333 ctx.unregisterReceiver(receiver); 334 } 335 } 336 337 /** Communicates to hostside (via logcat) that action has completed (regardless of success). */ tellHostActionFinished(String actionCode, String requestCode)338 private static void tellHostActionFinished(String actionCode, String requestCode) { 339 String s = String.format("Completed performing %s for request %s", actionCode, requestCode); 340 Log.i(TAG, s); 341 } 342 343 /** Determines whether the package is running as a background process. */ isAppInBackground(Context context)344 private static boolean isAppInBackground(Context context) throws ReflectiveOperationException { 345 String pkgName = context.getPackageName(); 346 ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 347 List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses(); 348 if (processes == null) { 349 return false; 350 } 351 for (ActivityManager.RunningAppProcessInfo r : processes) { 352 // BatteryStatsImpl treats as background if procState is >= 353 // Activitymanager.PROCESS_STATE_IMPORTANT_BACKGROUND (corresponding 354 // to BatteryStats.PROCESS_STATE_BACKGROUND). 355 // Due to lack of permissions, only the current app should show up in the list of 356 // processes, which is desired in this case; but in case this changes later, we check 357 // that the package name matches anyway. 358 int processState = -1; 359 int backgroundCode = -1; 360 try { 361 processState = ActivityManager.RunningAppProcessInfo.class 362 .getField("processState").getInt(r); 363 backgroundCode = (Integer) ActivityManager.class 364 .getDeclaredField("PROCESS_STATE_IMPORTANT_BACKGROUND").get(null); 365 } catch (ReflectiveOperationException ex) { 366 Log.e(TAG, "Failed to get proc state info via reflection", ex); 367 throw ex; 368 } 369 if (processState < backgroundCode) { // if foreground process 370 for (String rpkg : r.pkgList) { 371 if (pkgName.equals(rpkg)) { 372 return false; 373 } 374 } 375 } 376 } 377 return true; 378 } 379 380 /** 381 * Makes sure app is in desired state, either background (if shouldBeBg = true) or foreground 382 * (if shouldBeBg = false). 383 * Tries for up to PROC_STATE_CHECK_ATTEMPTS seconds. If app is still not in the correct state, 384 * throws an AssertionError failure to crash the app. 385 */ checkAppState( Context context, boolean shouldBeBg, String actionCode, String requestCode)386 public static void checkAppState( 387 Context context, boolean shouldBeBg, String actionCode, String requestCode) { 388 final String errMsg = "App is " + (shouldBeBg ? "not " : "") + "a background process!"; 389 try { 390 for (int attempt = 0; attempt < PROC_STATE_CHECK_ATTEMPTS; attempt++) { 391 if (shouldBeBg == isAppInBackground(context)) { 392 return; // No problems. 393 } else { 394 if (attempt < PROC_STATE_CHECK_ATTEMPTS - 1) { 395 Log.w(TAG, errMsg + " Trying again in 1s."); 396 sleep(1_000); 397 } else { 398 Log.e(TAG, errMsg + " Quiting app."); 399 BatteryStatsBgVsFgActions.tellHostActionFinished(actionCode, requestCode); 400 Assert.fail(errMsg + " Test requires app to be in the correct state."); 401 } 402 } 403 } 404 } catch(ReflectiveOperationException ex) { 405 Log.w(TAG, "Couldn't determine if app is in background. Proceeding with test anyway."); 406 } 407 } 408 409 /** Puts the current thread to sleep. */ sleep(int millis)410 private static void sleep(int millis) { 411 try { 412 Thread.sleep(millis); 413 } catch (InterruptedException e) { 414 Log.e(TAG, "Interrupted exception while sleeping", e); 415 } 416 } 417 } 418