Merge "Added CarWatchdog's CTS hostside test with shareUserId app" into sc-v2-dev
diff --git a/hostsidetests/car/Android.bp b/hostsidetests/car/Android.bp
index 6baaed4..6488d7e 100644
--- a/hostsidetests/car/Android.bp
+++ b/hostsidetests/car/Android.bp
@@ -42,5 +42,6 @@
],
data: [
":CtsCarApp",
+ ":CtsCarWatchdogSharedApp",
],
}
diff --git a/hostsidetests/car/AndroidTest.xml b/hostsidetests/car/AndroidTest.xml
index 3699a2b..81bcbb4 100644
--- a/hostsidetests/car/AndroidTest.xml
+++ b/hostsidetests/car/AndroidTest.xml
@@ -22,6 +22,7 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsCarApp.apk" />
+ <option name="test-file-name" value="CtsCarWatchdogSharedApp.apk" />
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsCarHostTestCases.jar" />
diff --git a/hostsidetests/car/app/Android.bp b/hostsidetests/car/app/Android.bp
index 0ed1890..32e33d8 100644
--- a/hostsidetests/car/app/Android.bp
+++ b/hostsidetests/car/app/Android.bp
@@ -32,3 +32,22 @@
libs: ["android.car"],
}
+
+android_test_helper_app {
+ name: "CtsCarWatchdogSharedApp",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.java"],
+ sdk_version: "test_current",
+
+ manifest: "AndroidManifestWithSharedUserId.xml",
+
+ enforce_uses_libs: false,
+ static_libs: [
+ "android.frameworks.automotive.powerpolicy-V1-java",
+ "android.hardware.automotive.vehicle-V2.0-java",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ ],
+
+ libs: ["android.car"],
+}
diff --git a/hostsidetests/car/app/AndroidManifest.xml b/hostsidetests/car/app/AndroidManifest.xml
index a4ec547..3e2d8c3 100755
--- a/hostsidetests/car/app/AndroidManifest.xml
+++ b/hostsidetests/car/app/AndroidManifest.xml
@@ -24,7 +24,7 @@
<uses-permission android:name="android.permission.STORAGE_INTERNAL"/>
<application>
- <activity android:name=".PowerPolicyTestActivity"
+ <activity android:name="android.car.cts.app.PowerPolicyTestActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
@@ -33,7 +33,7 @@
</intent-filter>
</activity>
- <activity android:name=".CarWatchdogTestActivity"
+ <activity android:name="android.car.cts.app.CarWatchdogTestActivity"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
@@ -42,7 +42,7 @@
</intent-filter>
</activity>
- <activity android:name=".SimpleActivity" android:exported="true">
+ <activity android:name="android.car.cts.app.SimpleActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
diff --git a/hostsidetests/car/app/AndroidManifestWithSharedUserId.xml b/hostsidetests/car/app/AndroidManifestWithSharedUserId.xml
new file mode 100755
index 0000000..5602770
--- /dev/null
+++ b/hostsidetests/car/app/AndroidManifestWithSharedUserId.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.car.cts.watchdog.sharedapp"
+ android:sharedUserId="android.car.cts.uid.watchdog.sharedapp">
+
+ <application>
+ <activity android:name="android.car.cts.app.CarWatchdogTestActivity"
+ android:launchMode="singleTask"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/hostsidetests/car/app/src/android/car/cts/app/CarWatchdogTestActivity.java b/hostsidetests/car/app/src/android/car/cts/app/CarWatchdogTestActivity.java
index 2e27427..86a667b 100644
--- a/hostsidetests/car/app/src/android/car/cts/app/CarWatchdogTestActivity.java
+++ b/hostsidetests/car/app/src/android/car/cts/app/CarWatchdogTestActivity.java
@@ -27,8 +27,6 @@
import android.util.Log;
import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileDescriptor;
@@ -40,27 +38,30 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-public class CarWatchdogTestActivity extends Activity {
+public final class CarWatchdogTestActivity extends Activity {
private static final String TAG = CarWatchdogTestActivity.class.getSimpleName();
+ private static final String BYTES_TO_KILL = "bytes_to_kill";
private static final long TEN_MEGABYTES = 1024 * 1024 * 10;
private static final long TWO_HUNDRED_MEGABYTES = 1024 * 1024 * 200;
private static final int DISK_DELAY_MS = 4000;
+ private static final double WARN_THRESHOLD_PERCENT = 0.8;
private static final double EXCEED_WARN_THRESHOLD_PERCENT = 0.9;
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private CarWatchdogManager mCarWatchdogManager;
+
private String mDumpMessage = "";
private Car mCar;
private File mTestDir;
- private final Object mLock = new Object();
- @GuardedBy("mLock")
- private CarWatchdogManager mCarWatchdogManager;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- initCarApi();
+ initCarApi();
try {
mTestDir =
Files.createTempDirectory(getFilesDir().toPath(), "testDir").toFile();
@@ -69,39 +70,58 @@
finish();
return;
}
-
mExecutor.execute(
() -> {
synchronized (mLock) {
- IoOveruseListener listener = addResourceOveruseListenerLocked();
- try {
- if (!writeToDisk(TEN_MEGABYTES)) {
- finish();
- return;
- }
+ if (mCarWatchdogManager == null) {
+ Log.e(TAG, "CarWatchdogManager is null.");
+ finish();
+ return;
+ }
+ }
+ IoOveruseListener listener = addResourceOveruseListener();
+ try {
+ if (!writeToDisk(TEN_MEGABYTES)) {
+ finish();
+ return;
+ }
- long remainingBytes = fetchRemainingBytesLocked(TEN_MEGABYTES);
- if (remainingBytes == 0) {
- finish();
- return;
- }
+ long remainingBytes = fetchRemainingBytes(TEN_MEGABYTES);
+ if (remainingBytes == 0) {
+ Log.d(TAG, "Remaining bytes is 0 after writing " + TEN_MEGABYTES
+ + " bytes to disk.");
+ finish();
+ return;
+ }
- long bytesToExceedWarnThreshold =
- (long) Math.ceil(remainingBytes
- * EXCEED_WARN_THRESHOLD_PERCENT);
+ /*
+ * Warning notification is received as soon as exceeding
+ * |WARN_THRESHOLD_PERCENT|. So, set expected minimum written bytes to
+ * |WARN_THRESHOLD_PERCENT| of the overuse threshold.
+ */
+ long bytesToWarnThreshold =
+ (long) (TWO_HUNDRED_MEGABYTES * WARN_THRESHOLD_PERCENT);
- listener.setExpectedMinWrittenBytes(
- TEN_MEGABYTES + bytesToExceedWarnThreshold);
+ listener.setExpectedMinWrittenBytes(bytesToWarnThreshold);
- if (!writeToDisk(bytesToExceedWarnThreshold)) {
- finish();
- return;
- }
+ long bytesToExceedWarnThreshold =
+ (long) Math.ceil(remainingBytes
+ * EXCEED_WARN_THRESHOLD_PERCENT);
- listener.checkIsNotified();
- } finally {
+ if (!writeToDisk(bytesToExceedWarnThreshold)) {
+ finish();
+ return;
+ }
+
+ listener.checkIsNotified();
+ } finally {
+ synchronized (mLock) {
mCarWatchdogManager.removeResourceOveruseListener(listener);
}
+ /* Foreground mode bytes dumped after removing listener to ensure hostside
+ * receives dump message after test is finished.
+ */
+ listener.dumpForegroundModeBytes();
}
});
}
@@ -109,20 +129,28 @@
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
+
setDumpMessage("");
Bundle extras = intent.getExtras();
if (extras == null) {
Log.w(TAG, "onNewIntent: empty extras");
return;
}
- long remainingBytes = extras.getLong("bytes_to_kill");
+ long remainingBytes = extras.getLong(BYTES_TO_KILL);
Log.d(TAG, "Bytes to kill: " + remainingBytes);
if (remainingBytes == 0) {
Log.w(TAG, "onNewIntent: remaining bytes is 0");
return;
}
mExecutor.execute(() -> {
- IoOveruseListener listener = addResourceOveruseListenerLocked();
+ synchronized (mLock) {
+ if (mCarWatchdogManager == null) {
+ Log.e(TAG, "onNewIntent: CarWatchdogManager is null.");
+ finish();
+ return;
+ }
+ }
+ IoOveruseListener listener = addResourceOveruseListener();
try {
listener.setExpectedMinWrittenBytes(TWO_HUNDRED_MEGABYTES);
@@ -130,15 +158,20 @@
listener.checkIsNotified();
} finally {
- mCarWatchdogManager.removeResourceOveruseListener(listener);
+ synchronized (mLock) {
+ mCarWatchdogManager.removeResourceOveruseListener(listener);
+ }
+ /* Foreground mode bytes dumped after removing listener to ensure hostside
+ * receives dump message after test is finished.
+ */
+ listener.dumpForegroundModeBytes();
}
});
}
@Override
- public void dump(@NonNull String prefix, @Nullable FileDescriptor fd,
- @NonNull PrintWriter writer, @Nullable String[] args) {
- writer.println(String.format("%s: %s\n", TAG, mDumpMessage));
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ writer.printf("%s: %s\n", TAG, mDumpMessage);
}
@Override
@@ -146,6 +179,7 @@
if (mCar != null) {
mCar.disconnect();
}
+
super.onDestroy();
}
@@ -171,11 +205,12 @@
}
}
- @GuardedBy("mLock")
- private IoOveruseListener addResourceOveruseListenerLocked() {
+ private IoOveruseListener addResourceOveruseListener() {
IoOveruseListener listener = new IoOveruseListener();
- mCarWatchdogManager.addResourceOveruseListener(getMainExecutor(),
- CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, listener);
+ synchronized (mLock) {
+ mCarWatchdogManager.addResourceOveruseListener(getMainExecutor(),
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, listener);
+ }
return listener;
}
@@ -213,11 +248,11 @@
try {
fos.write(new byte[writeSize]);
} catch (InterruptedIOException e) {
- e.printStackTrace();
+ Log.d(TAG, "Exception while writing to file", e);
Thread.currentThread().interrupt();
return writtenSize;
} catch (IOException e) {
- e.printStackTrace();
+ Log.d(TAG, "Exception while writing to file", e);
return writtenSize;
}
writtenSize += writeSize;
@@ -230,13 +265,13 @@
return writtenSize;
}
- @GuardedBy("mLock")
- private long fetchRemainingBytesLocked(long minWrittenBytes) {
- ResourceOveruseStats stats =
- mCarWatchdogManager.getResourceOveruseStats(
- CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
- CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
-
+ private long fetchRemainingBytes(long minWrittenBytes) {
+ ResourceOveruseStats stats;
+ synchronized (mLock) {
+ stats = mCarWatchdogManager.getResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+ }
IoOveruseStats ioOveruseStats = stats.getIoOveruseStats();
if (ioOveruseStats == null) {
setDumpMessage(
@@ -273,12 +308,15 @@
private final Object mLock = new Object();
@GuardedBy("mLock")
private boolean mNotificationReceived;
+ @GuardedBy("mLock")
+ private long mForegroundModeBytes;
private long mExpectedMinWrittenBytes;
@Override
public void onOveruse(ResourceOveruseStats resourceOveruseStats) {
synchronized (mLock) {
+ mForegroundModeBytes = -1;
mNotificationReceived = true;
mLock.notifyAll();
}
@@ -297,11 +335,18 @@
+ "' reported in overuse notification");
return;
}
- long foregroundModeBytes =
- resourceOveruseStats.getIoOveruseStats().getRemainingWriteBytes()
- .getForegroundModeBytes();
- // Dump the resource overuse stats
- setDumpMessage("INFO: --Notification-- foregroundModeBytes = " + foregroundModeBytes);
+ synchronized (mLock) {
+ mForegroundModeBytes =
+ resourceOveruseStats.getIoOveruseStats().getRemainingWriteBytes()
+ .getForegroundModeBytes();
+ }
+ }
+
+ public void dumpForegroundModeBytes() {
+ synchronized (mLock) {
+ setDumpMessage(
+ "INFO: --Notification-- foregroundModeBytes = " + mForegroundModeBytes);
+ }
}
public void setExpectedMinWrittenBytes(long expectedMinWrittenBytes) {
diff --git a/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java b/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
index 8cc1552..595881a 100644
--- a/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
+++ b/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
@@ -77,7 +77,7 @@
/**
* User's package permission pattern string format in the output of "dumpsys package PKG_NAME"
- */
+ */
protected static final String APP_APK = "CtsCarApp.apk";
protected static final String APP_PKG = "android.car.cts.app";
@@ -465,6 +465,13 @@
}
/**
+ * Checks if the given package has a process running on the device.
+ */
+ protected boolean isPackageRunning(String packageName) throws Exception {
+ return !executeCommand("pidof %s", packageName).isEmpty();
+ }
+
+ /**
* Sleeps for the given amount of milliseconds.
*/
protected void sleep(long ms) throws InterruptedException {
diff --git a/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java b/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
index c53c128b..82b29c2 100644
--- a/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
+++ b/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
@@ -34,29 +34,20 @@
@RunWith(DeviceJUnit4ClassRunner.class)
public class CarWatchdogHostTest extends CarHostJUnit4TestCase {
/**
+ * CarWatchdog app package.
+ */
+ protected static final String WATCHDOG_APP_PKG = "android.car.cts.watchdog.sharedapp";
+
+ /**
+ * CarWatchdog app shared user id.
+ */
+ protected static final String WATCHDOG_APP_SHARED_USER_ID =
+ "android.car.cts.uid.watchdog.sharedapp";
+
+ /**
* The class name of the main activity in the APK.
*/
- private static final String ACTIVITY_CLASS = "CarWatchdogTestActivity";
-
- /**
- * The command to launch the main activity.
- */
- private static final String START_CMD = String.format(
- "am start -W -a android.intent.action.MAIN -n %s/%s.%s", APP_PKG, APP_PKG,
- ACTIVITY_CLASS);
-
- /**
- * The command to clear the main activity.
- */
- private static final String CLEAR_CMD = String.format("pm clear %s", APP_PKG);
-
- /**
- * The command to get PID of the application.
- *
- * Note: If executing the command returns an empty string, the application process is not
- * running.
- */
- private static final String GET_PID_CMD = String.format("pidof %s", APP_PKG);
+ private static final String ACTIVITY_CLASS = APP_PKG + ".CarWatchdogTestActivity";
/**
* The command to start a custom performance collection with CarWatchdog.
@@ -77,7 +68,7 @@
*/
private static final String RESET_RESOURCE_OVERUSE_CMD = String.format(
"dumpsys android.automotive.watchdog.ICarWatchdog/default "
- + "--reset_resource_overuse_stats %s", APP_PKG);
+ + "--reset_resource_overuse_stats %s,%s", APP_PKG, WATCHDOG_APP_SHARED_USER_ID);
/**
* The command to get I/O overuse foreground bytes threshold in the adb shell.
@@ -99,14 +90,12 @@
private static final Pattern FOREGROUND_BYTES_PATTERN = Pattern.compile(
"foregroundModeBytes = (\\d+)");
- private static final long POLL_TIMEOUT_MS = 15000;
+ private static final long POLL_TIMEOUT_MS = 15_000;
private long mOriginalForegroundBytes;
@Before
public void setUp() throws Exception {
- String isClearSuccess = executeCommand(CLEAR_CMD);
- assertWithMessage("pm clear").that(isClearSuccess.trim()).isEqualTo("Success");
String foregroundBytesDump = executeCommand(GET_IO_OVERUSE_FOREGROUNG_BYTES_CMD);
mOriginalForegroundBytes = parseForegroundBytesFromMessage(foregroundBytesDump);
executeCommand("%s %d", SET_IO_OVERUSE_FOREGROUNG_BYTES_CMD, TWO_HUNDRED_MEGABYTES);
@@ -115,36 +104,44 @@
executeCommand(RESET_RESOURCE_OVERUSE_CMD);
}
- @Test
- public void testCarWatchdog() throws Exception {
- executeCommand(START_CMD);
- String pid = executeCommand(GET_PID_CMD);
- assertWithMessage("pid").that(pid).isNotEmpty();
-
- long remainingBytes = readForegroundBytesFromActivityDump();
-
- // Send intent with amount of bytes to kill app
- executeCommand(
- "am start -W -a android.intent.action.MAIN -n %s/%s.%s --el bytes_to_kill %d",
- APP_PKG, APP_PKG, ACTIVITY_CLASS, remainingBytes);
-
- remainingBytes = readForegroundBytesFromActivityDump();
- assertWithMessage("Application exceeded I/O overuse threshold").that(
- remainingBytes).isEqualTo(0);
-
- verifyTestAppKilled();
- }
-
@After
public void tearDown() throws Exception {
executeCommand(STOP_CUSTOM_PERF_COLLECTION_CMD);
executeCommand("%s %d", SET_IO_OVERUSE_FOREGROUNG_BYTES_CMD, mOriginalForegroundBytes);
}
- private long readForegroundBytesFromActivityDump() throws Exception {
+ @Test
+ public void testCarWatchdog() throws Exception {
+ startMainActivity(APP_PKG);
+
+ long remainingBytes = readForegroundBytesFromActivityDump(APP_PKG);
+ sendBytesToKillApp(remainingBytes, APP_PKG);
+
+ remainingBytes = readForegroundBytesFromActivityDump(APP_PKG);
+ assertWithMessage("Application exceeded I/O overuse threshold")
+ .that(remainingBytes).isEqualTo(0);
+
+ verifyTestAppKilled(APP_PKG);
+ }
+
+ @Test
+ public void testCarWatchdogWithShareUserId() throws Exception {
+ startMainActivity(WATCHDOG_APP_PKG);
+
+ long remainingBytes = readForegroundBytesFromActivityDump(WATCHDOG_APP_PKG);
+ sendBytesToKillApp(remainingBytes, WATCHDOG_APP_PKG);
+
+ remainingBytes = readForegroundBytesFromActivityDump(WATCHDOG_APP_PKG);
+ assertWithMessage("Application exceeded I/O overuse threshold")
+ .that(remainingBytes).isEqualTo(0);
+
+ verifyTestAppKilled(WATCHDOG_APP_PKG);
+ }
+
+ private long readForegroundBytesFromActivityDump(String packageName) throws Exception {
AtomicReference<String> notification = new AtomicReference<>();
PollingCheck.check("Unable to receive notification", POLL_TIMEOUT_MS, () -> {
- String dump = fetchActivityDumpsys();
+ String dump = fetchActivityDumpsys(packageName);
if (dump.startsWith("INFO") && dump.contains("--Notification--")) {
notification.set(dump);
return true;
@@ -163,18 +160,16 @@
throw new IllegalArgumentException("Invalid message format: " + message);
}
- private void verifyTestAppKilled() throws Exception {
+ private void verifyTestAppKilled(String packageName) throws Exception {
PollingCheck.check("Unable to kill application", POLL_TIMEOUT_MS, () -> {
- // Dump activity to check for logged errors.
- // If error log found in dump, an exception is thrown.
- fetchActivityDumpsys();
- String pid = executeCommand(GET_PID_CMD);
- return pid.isEmpty();
+ // Check activity dump for errors. Throws exception on error.
+ fetchActivityDumpsys(packageName);
+ return !isPackageRunning(packageName);
});
}
- private String fetchActivityDumpsys() throws Exception {
- String dump = executeCommand("dumpsys activity %s/.%s", APP_PKG, ACTIVITY_CLASS);
+ private String fetchActivityDumpsys(String packageName) throws Exception {
+ String dump = executeCommand("dumpsys activity %s/%s", packageName, ACTIVITY_CLASS);
Matcher m = DUMP_PATTERN.matcher(dump);
if (!m.find()) {
return "";
@@ -185,4 +180,21 @@
}
return message;
}
+
+ private void startMainActivity(String packageName) throws Exception {
+ String result = executeCommand("pm clear %s", packageName);
+ assertWithMessage("pm clear").that(result.trim()).isEqualTo("Success");
+
+ executeCommand("am start -W -a android.intent.action.MAIN -n %s/%s", packageName,
+ ACTIVITY_CLASS);
+
+ assertWithMessage("%s is running", packageName)
+ .that(isPackageRunning(packageName)).isTrue();
+ }
+
+ private void sendBytesToKillApp(long remainingBytes, String appPkg) throws Exception {
+ executeCommand(
+ "am start -W -a android.intent.action.MAIN -n %s/%s --el bytes_to_kill %d",
+ appPkg, ACTIVITY_CLASS, remainingBytes);
+ }
}