Merge "Background exceptions fail tests, more utils." into main
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 6285eb3..084c71f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -147,6 +147,7 @@
* </p>
*/
@SystemService(Context.ACTIVITY_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public class ActivityManager {
private static String TAG = "ActivityManager";
@@ -966,6 +967,7 @@
* Print capability bits in human-readable form.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static void printCapabilitiesSummary(PrintWriter pw, @ProcessCapability int caps) {
pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
@@ -976,6 +978,7 @@
}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static void printCapabilitiesSummary(StringBuilder sb, @ProcessCapability int caps) {
sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-');
sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-');
@@ -989,6 +992,7 @@
* Print capability bits in human-readable form.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static void printCapabilitiesFull(PrintWriter pw, @ProcessCapability int caps) {
printCapabilitiesSummary(pw, caps);
final int remain = caps & ~PROCESS_CAPABILITY_ALL;
@@ -999,6 +1003,7 @@
}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String getCapabilitiesSummary(@ProcessCapability int caps) {
final StringBuilder sb = new StringBuilder();
printCapabilitiesSummary(sb, caps);
@@ -1018,6 +1023,7 @@
* @return the value of the corresponding enums.proto ProcessStateEnum value.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static final int processStateAmToProto(int amInt) {
switch (amInt) {
case PROCESS_STATE_UNKNOWN:
@@ -1078,16 +1084,19 @@
public static final int MAX_PROCESS_STATE = PROCESS_STATE_NONEXISTENT;
/** @hide Should this process state be considered a background state? */
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isProcStateBackground(int procState) {
return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
}
/** @hide Should this process state be considered in the cache? */
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isProcStateCached(int procState) {
return procState >= PROCESS_STATE_CACHED_ACTIVITY;
}
/** @hide Is this a foreground service type? */
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isForegroundService(int procState) {
return procState == PROCESS_STATE_FOREGROUND_SERVICE;
}
@@ -1161,10 +1170,25 @@
mContext = context;
}
+ private static volatile int sCurrentUser$ravenwood = UserHandle.USER_NULL;
+
+ /** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public static void init$ravenwood(int currentUser) {
+ sCurrentUser$ravenwood = currentUser;
+ }
+
+ /** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public static void reset$ravenwood() {
+ sCurrentUser$ravenwood = UserHandle.USER_NULL;
+ }
+
/**
* Returns whether the launch was successful.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isStartResultSuccessful(int result) {
return FIRST_START_SUCCESS_CODE <= result && result <= LAST_START_SUCCESS_CODE;
}
@@ -1173,6 +1197,7 @@
* Returns whether the launch result was a fatal error.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isStartResultFatalError(int result) {
return FIRST_START_FATAL_ERROR_CODE <= result && result <= LAST_START_FATAL_ERROR_CODE;
}
@@ -1343,6 +1368,7 @@
public @interface RestrictionLevel{}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String restrictionLevelToName(@RestrictionLevel int level) {
switch (level) {
case RESTRICTION_LEVEL_UNKNOWN:
@@ -4779,6 +4805,7 @@
* Returns "true" if the user interface is currently being messed with
* by a monkey.
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static boolean isUserAMonkey() {
try {
return getService().isUserAMonkey();
@@ -4787,6 +4814,12 @@
}
}
+ /** @hide */
+ public static boolean isUserAMonkey$ravenwood() {
+ // Ravenwood environment is never considered a "monkey"
+ return false;
+ }
+
/**
* Returns "true" if device is running in a test harness.
*
@@ -4973,6 +5006,7 @@
"android.permission.INTERACT_ACROSS_USERS",
"android.permission.INTERACT_ACROSS_USERS_FULL"
})
+ @android.ravenwood.annotation.RavenwoodReplace
public static int getCurrentUser() {
try {
return getService().getCurrentUserId();
@@ -4981,6 +5015,11 @@
}
}
+ /** @hide */
+ public static int getCurrentUser$ravenwood() {
+ return sCurrentUser$ravenwood;
+ }
+
/**
* @param userid the user's id. Zero indicates the default user.
* @hide
@@ -5320,6 +5359,7 @@
/**
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static boolean isSystemReady() {
if (!sSystemReady) {
if (ActivityThread.isSystem()) {
@@ -5334,6 +5374,12 @@
return sSystemReady;
}
+ /** @hide */
+ public static boolean isSystemReady$ravenwood() {
+ // Ravenwood environment is always considered as booted and ready
+ return true;
+ }
+
/**
* @hide
*/
@@ -5661,11 +5707,13 @@
}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isProcStateConsideredInteraction(@ProcessState int procState) {
return (procState <= PROCESS_STATE_TOP || procState == PROCESS_STATE_BOUND_TOP);
}
/** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
public static String procStateToString(int procState) {
final String procStateStr;
switch (procState) {
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 5078dc35..46705a3 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -29,6 +29,7 @@
/**
* Encapsulates a collection of attributes describing information about a vibration.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class VibrationAttributes implements Parcelable {
private static final String TAG = "VibrationAttributes";
@@ -463,6 +464,7 @@
* Builder class for {@link VibrationAttributes} objects.
* By default, all information is set to UNKNOWN.
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static final class Builder {
private int mUsage = USAGE_UNKNOWN;
private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 513e022..1b25d7f 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -208,6 +208,7 @@
"testng",
],
srcs: [
+ "src/android/app/ActivityManagerTest.java",
"src/android/content/pm/PackageManagerTest.java",
"src/android/content/pm/UserInfoTest.java",
"src/android/database/CursorWindowTest.java",
diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java
new file mode 100644
index 0000000..d930e4d
--- /dev/null
+++ b/core/tests/coretests/src/android/app/ActivityManagerTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 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 android.app;
+
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.UserHandle;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ @Test
+ public void testSimple() throws Exception {
+ assertTrue(ActivityManager.isSystemReady());
+ assertFalse(ActivityManager.isUserAMonkey());
+ assertNotEquals(UserHandle.USER_NULL, ActivityManager.getCurrentUser());
+ }
+
+ @Test
+ public void testCapabilities() throws Exception {
+ // For the moment mostly want to confirm we don't crash
+ assertNotNull(ActivityManager.getCapabilitiesSummary(~0));
+ ActivityManager.printCapabilitiesFull(new PrintWriter(new ByteArrayOutputStream()), ~0);
+ ActivityManager.printCapabilitiesSummary(new PrintWriter(new ByteArrayOutputStream()), ~0);
+ ActivityManager.printCapabilitiesSummary(new StringBuilder(), ~0);
+ }
+
+ @Test
+ public void testProcState() throws Exception {
+ // For the moment mostly want to confirm we don't crash
+ assertNotNull(ActivityManager.procStateToString(PROCESS_STATE_SERVICE));
+ assertNotNull(ActivityManager.processStateAmToProto(PROCESS_STATE_SERVICE));
+ assertTrue(ActivityManager.isProcStateBackground(PROCESS_STATE_SERVICE));
+ assertFalse(ActivityManager.isProcStateCached(PROCESS_STATE_SERVICE));
+ assertFalse(ActivityManager.isForegroundService(PROCESS_STATE_SERVICE));
+ assertFalse(ActivityManager.isProcStateConsideredInteraction(PROCESS_STATE_SERVICE));
+ }
+
+ @Test
+ public void testStartResult() throws Exception {
+ // For the moment mostly want to confirm we don't crash
+ assertTrue(ActivityManager.isStartResultSuccessful(50));
+ assertTrue(ActivityManager.isStartResultFatalError(-50));
+ }
+
+ @Test
+ public void testRestrictionLevel() throws Exception {
+ // For the moment mostly want to confirm we don't crash
+ assertNotNull(ActivityManager.restrictionLevelToName(
+ ActivityManager.RESTRICTION_LEVEL_HIBERNATION));
+ }
+}
diff --git a/core/tests/coretests/src/android/os/HandlerThreadTest.java b/core/tests/coretests/src/android/os/HandlerThreadTest.java
index 0bac1c7..1ad71da 100644
--- a/core/tests/coretests/src/android/os/HandlerThreadTest.java
+++ b/core/tests/coretests/src/android/os/HandlerThreadTest.java
@@ -28,15 +28,20 @@
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class HandlerThreadTest {
private static final int TEST_WHAT = 1;
- @Rule
+ @Rule(order = 1)
+ public ExpectedException mThrown = ExpectedException.none();
+
+ @Rule(order = 2)
public final RavenwoodRule mRavenwood = new RavenwoodRule();
private boolean mGotMessage = false;
@@ -112,4 +117,28 @@
assertTrue(mGotMessage);
assertEquals(TEST_WHAT, mGotMessageWhat);
}
+
+ /**
+ * Confirm that a background handler thread throwing an exception during a test results in a
+ * test failure being reported.
+ */
+ @Test
+ public void testUncaughtExceptionFails() throws Exception {
+ // For the moment we can only test Ravenwood; on a physical device uncaught exceptions
+ // are detected, but reported as test failures at a higher level where we can't inspect
+ Assume.assumeTrue(RavenwoodRule.isOnRavenwood());
+ mThrown.expect(IllegalStateException.class);
+
+ final HandlerThread thread = new HandlerThread("HandlerThreadTest");
+ thread.start();
+ thread.getThreadHandler().post(() -> {
+ throw new IllegalStateException();
+ });
+
+ // Wait until we've drained past the message above, then terminate test without throwing
+ // directly; the test harness should notice and report the uncaught exception
+ while (!thread.getThreadHandler().getLooper().getQueue().isIdle()) {
+ SystemClock.sleep(10);
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/os/VibrationAttributesTest.java b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
new file mode 100644
index 0000000..f5a81c5
--- /dev/null
+++ b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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 android.os;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VibrationAttributesTest {
+ @Test
+ public void testSimple() throws Exception {
+ final VibrationAttributes attr = new VibrationAttributes.Builder()
+ .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+ .setUsage(VibrationAttributes.USAGE_ALARM)
+ .build();
+
+ assertEquals(VibrationAttributes.CATEGORY_KEYBOARD, attr.getCategory());
+ assertEquals(VibrationAttributes.USAGE_ALARM, attr.getUsage());
+ }
+}
diff --git a/ravenwood/bulk_enable.py b/ravenwood/bulk_enable.py
index 36d398c..83fda9e 100644
--- a/ravenwood/bulk_enable.py
+++ b/ravenwood/bulk_enable.py
@@ -34,6 +34,8 @@
re_result = re.compile("I/ModuleListener.+?null-device-0 (.+?)#(.+?) ([A-Z_]+)(.*)$")
+DRY_RUN = "-n" in sys.argv
+
ANNOTATION = "@android.platform.test.annotations.EnabledOnRavenwood"
SED_ARG = "s/^((public )?class )/%s\\n\\1/g" % (ANNOTATION)
@@ -46,7 +48,7 @@
stats_class = collections.defaultdict(lambda: collections.defaultdict(int))
stats_method = collections.defaultdict()
-with open(sys.argv[1]) as f:
+with open(sys.argv[-1]) as f:
for line in f.readlines():
result = re_result.search(line)
if result:
@@ -67,7 +69,7 @@
clazz_match = re.compile("%s\.(kt|java)" % (clazz.split(".")[-1]))
for root, dirs, files in os.walk("."):
for f in files:
- if clazz_match.match(f):
+ if clazz_match.match(f) and not DRY_RUN:
path = os.path.join(root, f)
subprocess.run(["sed", "-i", "-E", SED_ARG, path])
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 3670459..8af561f 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -16,7 +16,9 @@
package android.platform.test.ravenwood;
+import android.app.ActivityManager;
import android.app.Instrumentation;
+import android.os.Build;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
@@ -30,10 +32,12 @@
import java.io.PrintStream;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
public class RavenwoodRuleImpl {
private static final String MAIN_THREAD_NAME = "RavenwoodMain";
@@ -50,11 +54,27 @@
private static ScheduledFuture<?> sPendingTimeout;
+ /**
+ * When set, an unhandled exception was discovered (typically on a background thread), and we
+ * capture it here to ensure it's reported as a test failure.
+ */
+ private static final AtomicReference<Throwable> sPendingUncaughtException =
+ new AtomicReference<>();
+
+ private static final Thread.UncaughtExceptionHandler sUncaughtExceptionHandler =
+ (thread, throwable) -> {
+ // Remember the first exception we discover
+ sPendingUncaughtException.compareAndSet(null, throwable);
+ };
+
public static boolean isOnRavenwood() {
return true;
}
public static void init(RavenwoodRule rule) {
+ maybeThrowPendingUncaughtException(false);
+ Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
+
RuntimeInit.redirectLogStreams();
android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
@@ -64,6 +84,8 @@
rule.mSystemProperties.getKeyReadablePredicate(),
rule.mSystemProperties.getKeyWritablePredicate());
+ ActivityManager.init$ravenwood(rule.mCurrentUser);
+
com.android.server.LocalServices.removeAllServicesForTest();
if (rule.mProvideMainThread) {
@@ -78,6 +100,10 @@
sPendingTimeout = sTimeoutExecutor.schedule(RavenwoodRuleImpl::dumpStacks,
TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
+
+ // Touch some references early to ensure they're <clinit>'ed
+ Objects.requireNonNull(Build.IS_USERDEBUG);
+ Objects.requireNonNull(Build.VERSION.SDK);
}
public static void reset(RavenwoodRule rule) {
@@ -94,9 +120,13 @@
com.android.server.LocalServices.removeAllServicesForTest();
+ ActivityManager.reset$ravenwood();
+
android.os.SystemProperties.reset$ravenwood();
android.os.Binder.reset$ravenwood();
android.os.Process.reset$ravenwood();
+
+ maybeThrowPendingUncaughtException(true);
}
public static void logTestRunner(String label, Description description) {
@@ -120,4 +150,21 @@
}
out.println("-----END ALL THREAD STACKS-----");
}
+
+ /**
+ * If there's a pending uncaught exception, consume and throw it now. Typically used to
+ * report an exception on a background thread as a failure for the currently running test.
+ */
+ private static void maybeThrowPendingUncaughtException(boolean duringReset) {
+ final Throwable pending = sPendingUncaughtException.getAndSet(null);
+ if (pending != null) {
+ if (duringReset) {
+ throw new IllegalStateException(
+ "Found an uncaught exception during this test", pending);
+ } else {
+ throw new IllegalStateException(
+ "Found an uncaught exception before this test started", pending);
+ }
+ }
+ }
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 0285b38..daed457 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -16,6 +16,10 @@
package android.platform.test.ravenwood;
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.SYSTEM_UID;
+import static android.os.UserHandle.USER_SYSTEM;
+
import static org.junit.Assert.fail;
import android.platform.test.annotations.DisabledOnRavenwood;
@@ -94,12 +98,12 @@
}
}
- private static final int SYSTEM_UID = 1000;
private static final int NOBODY_UID = 9999;
- private static final int FIRST_APPLICATION_UID = 10000;
private static final AtomicInteger sNextPid = new AtomicInteger(100);
+ int mCurrentUser = USER_SYSTEM;
+
/**
* Unless the test author requests differently, run as "nobody", and give each collection of
* tests its own unique PID.
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index e49b64e..a5ecd20 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -82,6 +82,8 @@
android.os.UidBatteryConsumer$Builder
android.os.UserHandle
android.os.UserManager
+android.os.VibrationAttributes
+android.os.VibrationAttributes$Builder
android.os.WorkSource
android.content.ClipData
@@ -144,6 +146,7 @@
android.content.ContentProvider
+android.app.ActivityManager
android.app.Instrumentation
android.metrics.LogMaker