diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 51bfc209bf558..3f2a46a0e9cab 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -68,6 +68,7 @@
+
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 23d70b8dedd40..970a0f00bf5ef 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -16,6 +16,7 @@
+
diff --git a/core/tests/coretests/BstatsTestApp/Android.mk b/core/tests/coretests/BstatsTestApp/Android.mk
new file mode 100644
index 0000000000000..628025751d77d
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := coretests-aidl
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := BstatsTestApp
+LOCAL_CERTIFICATE := platform
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
new file mode 100644
index 0000000000000..0cb5498a9c212
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
new file mode 100644
index 0000000000000..1f5f39730096d
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.coretests.apps.bstatstestapp;
+
+import com.android.frameworks.coretests.aidl.ICmdReceiver;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.Log;
+
+public class IsolatedTestService extends Service {
+ private static final String TAG = IsolatedTestService.class.getName();
+
+ @Override
+ public void onCreate() {
+ Log.d(TAG, "onCreate called. myUid=" + Process.myUid());
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mReceiver.asBinder();
+ }
+
+ private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
+ @Override
+ public void doSomeWork(int durationMs) {
+ final long endTime = SystemClock.uptimeMillis() + durationMs;
+ double x;
+ double y;
+ double z;
+ while (SystemClock.uptimeMillis() <= endTime) {
+ x = 0.02;
+ x *= 1000;
+ y = x % 5;
+ z = Math.sqrt(y / 100);
+ }
+ };
+
+ @Override
+ public void finishHost() {
+ stopSelf();
+ }
+ };
+}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
new file mode 100644
index 0000000000000..87b14d9349130
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.coretests.apps.bstatstestapp;
+
+import com.android.frameworks.coretests.aidl.ICmdCallback;
+import com.android.frameworks.coretests.aidl.ICmdReceiver;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+public class TestActivity extends Activity {
+ private static final String TAG = TestActivity.class.getName();
+
+ private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ Log.d(TAG, "onCreate called");
+ notifyActivityLaunched();
+ }
+
+ private void notifyActivityLaunched() {
+ if (getIntent() == null) {
+ return;
+ }
+
+ final Bundle extras = getIntent().getExtras();
+ if (extras == null) {
+ return;
+ }
+ final ICmdCallback callback = ICmdCallback.Stub.asInterface(
+ extras.getBinder(EXTRA_KEY_CMD_RECEIVER));
+ try {
+ callback.onActivityLaunched(mReceiver.asBinder());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error occured while notifying the test: " + e);
+ }
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ Log.d(TAG, "finish called");
+ }
+
+ private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
+ @Override
+ public void doSomeWork(int durationMs) {
+ final long endTime = SystemClock.uptimeMillis() + durationMs;
+ double x;
+ double y;
+ double z;
+ while (SystemClock.uptimeMillis() <= endTime) {
+ x = 0.02;
+ x *= 1000;
+ y = x % 5;
+ z = Math.sqrt(y / 100);
+ }
+ };
+
+ @Override
+ public void finishHost() {
+ finish();
+ }
+ };
+}
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
new file mode 100644
index 0000000000000..53a181acd5b6a
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.aidl;
+
+interface ICmdCallback {
+ void onActivityLaunched(IBinder receiver);
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
new file mode 100644
index 0000000000000..c406570ecfaec
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.aidl;
+
+interface ICmdReceiver {
+ void doSomeWork(int durationMs);
+ void finishHost();
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
new file mode 100644
index 0000000000000..682a0021b2744
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.frameworks.coretests.aidl.ICmdCallback;
+import com.android.frameworks.coretests.aidl.ICmdReceiver;
+
+import android.app.KeyguardManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BstatsCpuTimesValidationTest {
+ private static final String TAG = BstatsCpuTimesValidationTest.class.getName();
+
+ private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp";
+ private static final String TEST_ACTIVITY = TEST_PKG + ".TestActivity";
+ private static final String ISOLATED_TEST_SERVICE = TEST_PKG + ".IsolatedTestService";
+
+ private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+
+ private static final int BATTERY_STATE_TIMEOUT_MS = 2000;
+ private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 200;
+
+ private static final int START_ACTIVITY_TIMEOUT_MS = 2000;
+ private static final int START_ISOLATED_SERVICE_TIMEOUT_MS = 2000;
+
+ private static final int GENERAL_TIMEOUT_MS = 1000;
+ private static final int GENERAL_INTERVAL_MS = 100;
+
+ private static final int WORK_DURATION_MS = 2000;
+
+ private static Context sContext;
+ private static UiDevice sUiDevice;
+ private static int sTestPkgUid;
+ private static boolean sCpuFreqTimesAvailable;
+
+ @BeforeClass
+ public static void setupOnce() throws Exception {
+ sContext = InstrumentationRegistry.getContext();
+ sUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ sContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+ sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
+ sCpuFreqTimesAvailable = cpuFreqTimesAvailable();
+ }
+
+ // Checks cpu freq times of system uid as an indication of whether /proc/uid_time_in_state
+ // kernel node is available.
+ private static boolean cpuFreqTimesAvailable() throws Exception {
+ final long[] cpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID);
+ return cpuTimes != null;
+ }
+
+ @Test
+ public void testCpuFreqTimes() throws Exception {
+ assumeTrue(sCpuFreqTimesAvailable);
+
+ batteryOnScreenOn();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null", initialSnapshot);
+ doSomeWork();
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid);
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time", WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testCpuFreqTimes_screenOff() throws Exception {
+ assumeTrue(sCpuFreqTimesAvailable);
+
+ batteryOnScreenOff();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null", initialSnapshot);
+ doSomeWork();
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid);
+ assertCpuTimesValid(cpuTimesMs);
+ long actualTotalCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualTotalCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time", WORK_DURATION_MS, actualTotalCpuTimeMs);
+ long actualScreenOffCpuTimeMs = 0;
+ for (int i = cpuTimesMs.length / 2; i < cpuTimesMs.length; ++i) {
+ actualScreenOffCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect screen-off cpu time",
+ WORK_DURATION_MS, actualScreenOffCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ @Test
+ public void testCpuFreqTimes_isolatedProcess() throws Exception {
+ assumeTrue(sCpuFreqTimesAvailable);
+
+ batteryOnScreenOn();
+ forceStop();
+ resetBatteryStats();
+ final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+ assertNull("Initial snapshot should be null", initialSnapshot);
+ doSomeWorkInIsolatedProcess();
+ forceStop();
+
+ final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid);
+ assertCpuTimesValid(cpuTimesMs);
+ long actualCpuTimeMs = 0;
+ for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+ actualCpuTimeMs += cpuTimesMs[i];
+ }
+ assertApproximateValue("Incorrect total cpu time", WORK_DURATION_MS, actualCpuTimeMs);
+ batteryOffScreenOn();
+ }
+
+ private void assertCpuTimesValid(long[] cpuTimes) {
+ assertNotNull(cpuTimes);
+ for (int i = 0; i < cpuTimes.length; ++i) {
+ if (cpuTimes[i] < 0) {
+ fail("Malformed cpu times data (-ve values): " + Arrays.toString(cpuTimes));
+ }
+ }
+ final int numFreqs = cpuTimes.length / 2;
+ for (int i = 0; i < numFreqs; ++i) {
+ if (cpuTimes[i] < cpuTimes[numFreqs + i]) {
+ fail("Malformed cpu times data (screen-off > total)" + Arrays.toString(cpuTimes));
+ }
+ }
+ }
+
+ private void assertApproximateValue(String errorPrefix, long expectedValue, long actualValue) {
+ assertValueRange(errorPrefix, actualValue, expectedValue * 0.5, expectedValue * 1.5);
+ }
+
+ private void assertValueRange(String errorPrefix,
+ long actualvalue, double minValue, double maxValue) {
+ final String errorMsg = String.format(errorPrefix + "; actual=%s; min=%s; max=%s",
+ actualvalue, minValue, maxValue);
+ assertTrue(errorMsg, actualvalue < maxValue);
+ assertTrue(errorMsg, actualvalue > minValue);
+ }
+
+ private void doSomeWork() throws Exception {
+ final ICmdReceiver receiver = ICmdReceiver.Stub.asInterface(startActivity());
+ receiver.doSomeWork(WORK_DURATION_MS);
+ receiver.finishHost();
+ }
+
+ private void doSomeWorkInIsolatedProcess() throws Exception {
+ final ICmdReceiver receiver = ICmdReceiver.Stub.asInterface(startIsolatedService());
+ receiver.doSomeWork(WORK_DURATION_MS);
+ receiver.finishHost();
+ }
+
+ private IBinder startIsolatedService() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final IBinder[] binders = new IBinder[1];
+ final ServiceConnection connection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ binders[0] = service;
+ latch.countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+ };
+ final Intent launchIntent = new Intent()
+ .setComponent(new ComponentName(TEST_PKG, ISOLATED_TEST_SERVICE));
+ sContext.bindService(launchIntent, connection, Context.BIND_AUTO_CREATE
+ | Context.BIND_ALLOW_OOM_MANAGEMENT | Context.BIND_NOT_FOREGROUND);
+ if (latch.await(START_ISOLATED_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ if (binders[0] == null) {
+ fail("Receiver binder should not be null");
+ }
+ return binders[0];
+ } else {
+ fail("Timed out waiting for the isolated test service to start");
+ }
+ return null;
+ }
+
+ private IBinder startActivity() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Intent launchIntent = new Intent()
+ .setComponent(new ComponentName(TEST_PKG, TEST_ACTIVITY));
+ final Bundle extras = new Bundle();
+ final IBinder[] binders = new IBinder[1];
+ extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
+ @Override
+ public void onActivityLaunched(IBinder receiver) {
+ binders[0] = receiver;
+ latch.countDown();
+ }
+ });
+ launchIntent.putExtras(extras);
+ sContext.startActivity(launchIntent);
+ if (latch.await(START_ACTIVITY_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ if (binders[0] == null) {
+ fail("Receiver binder should not be null");
+ }
+ return binders[0];
+ } else {
+ fail("Timed out waiting for the test activity to start; testUid=" + sTestPkgUid);
+ }
+ return null;
+ }
+
+ private static long[] getAllCpuFreqTimes(int uid) throws Exception {
+ final String checkinDump = executeCmdSilent("dumpsys batterystats --checkin");
+ final Pattern pattern = Pattern.compile(uid + ",l,ctf,A,(.*?)\n");
+ final Matcher matcher = pattern.matcher(checkinDump);
+ if (!matcher.find()) {
+ return null;
+ }
+ final String[] uidTimesStr = matcher.group(1).split(",");
+ final int freqCount = Integer.parseInt(uidTimesStr[0]);
+ if (uidTimesStr.length != (2 * freqCount + 1)) {
+ fail("Malformed data: " + Arrays.toString(uidTimesStr));
+ }
+ final long[] cpuTimes = new long[freqCount * 2];
+ for (int i = 0; i < cpuTimes.length; ++i) {
+ cpuTimes[i] = Long.parseLong(uidTimesStr[i + 1]);
+ }
+ return cpuTimes;
+ }
+
+ private void resetBatteryStats() throws Exception {
+ executeCmd("dumpsys batterystats --reset");
+ }
+
+ private void batteryOnScreenOn() throws Exception {
+ batteryOn();
+ screenOn();
+ }
+
+ private void batteryOnScreenOff() throws Exception {
+ batteryOn();
+ screenoff();
+ }
+
+ private void batteryOffScreenOn() throws Exception {
+ batteryOff();
+ screenOn();
+ }
+
+ private void batteryOn() throws Exception {
+ executeCmd("dumpsys battery unplug");
+ assertBatteryState(false);
+ }
+
+ private void batteryOff() throws Exception {
+ executeCmd("dumpsys battery reset");
+ assertBatteryState(true);
+ }
+
+ private void screenOn() throws Exception {
+ executeCmd("input keyevent KEYCODE_WAKEUP");
+ executeCmd("wm dismiss-keyguard");
+ assertKeyguardUnLocked();
+ assertScreenInteractive(true);
+ }
+
+ private void screenoff() throws Exception {
+ executeCmd("input keyevent KEYCODE_SLEEP");
+ assertScreenInteractive(false);
+ }
+
+ private void forceStop() throws Exception {
+ executeCmd("cmd activity force-stop " + TEST_PKG);
+ assertUidState(PROCESS_STATE_NONEXISTENT);
+ }
+
+ private void assertUidState(int state) throws Exception {
+ final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
+ final int uidState = Integer.parseInt(uidStateStr.split(" ")[0]);
+ assertEquals(state, uidState);
+ }
+
+ private void assertKeyguardUnLocked() {
+ final KeyguardManager keyguardManager =
+ (KeyguardManager) sContext.getSystemService(Context.KEYGUARD_SERVICE);
+ assertDelayedCondition("Keyguard should be unlocked",
+ () -> !keyguardManager.isKeyguardLocked());
+ }
+
+ private void assertScreenInteractive(boolean interactive) {
+ final PowerManager powerManager =
+ (PowerManager) sContext.getSystemService(Context.POWER_SERVICE);
+ assertDelayedCondition("Unexpected screen interactive state",
+ () -> interactive == powerManager.isInteractive());
+ }
+
+ private void assertDelayedCondition(String errorMsg, ExpectedCondition condition) {
+ final long endTime = SystemClock.uptimeMillis() + GENERAL_TIMEOUT_MS;
+ while (SystemClock.uptimeMillis() <= endTime) {
+ if (condition.isTrue()) {
+ return;
+ }
+ SystemClock.sleep(GENERAL_INTERVAL_MS);
+ }
+ if (!condition.isTrue()) {
+ fail(errorMsg);
+ }
+ }
+
+ private void assertBatteryState(boolean pluggedIn) throws Exception {
+ final long endTime = SystemClock.uptimeMillis() + BATTERY_STATE_TIMEOUT_MS;
+ while (isDevicePluggedIn() != pluggedIn && SystemClock.uptimeMillis() <= endTime) {
+ Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
+ }
+ if (isDevicePluggedIn() != pluggedIn) {
+ fail("Timed out waiting for the plugged-in state to change,"
+ + " expected pluggedIn: " + pluggedIn);
+ }
+ }
+
+ private boolean isDevicePluggedIn() {
+ final Intent batteryIntent = sContext.registerReceiver(null,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
+ }
+
+ private String executeCmd(String cmd) throws Exception {
+ final String result = sUiDevice.executeShellCommand(cmd).trim();
+ Log.d(TAG, String.format("Result for '%s': %s", cmd, result));
+ return result;
+ }
+
+ private static String executeCmdSilent(String cmd) throws Exception {
+ return sUiDevice.executeShellCommand(cmd).trim();
+ }
+
+ private interface ExpectedCondition {
+ boolean isTrue();
+ }
+}