summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Ravenwood.bp68
-rw-r--r--core/java/android/app/Instrumentation.java21
-rw-r--r--core/java/android/hardware/SerialManager.java3
-rw-r--r--core/java/android/hardware/SerialManagerInternal.java35
-rw-r--r--core/java/android/os/PermissionEnforcer.java18
-rw-r--r--core/java/android/os/ServiceManager.java50
-rw-r--r--core/java/android/util/TimingsTraceLog.java1
-rw-r--r--core/tests/coretests/Android.bp1
-rw-r--r--core/tests/coretests/src/android/content/ContextTest.java38
-rw-r--r--ravenwood/Android.bp9
-rw-r--r--ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java10
-rw-r--r--ravenwood/framework-minus-apex-ravenwood-policies.txt1
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java90
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodPermissionEnforcer.java38
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java25
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java85
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java47
-rw-r--r--ravenwood/ravenwood-annotation-allowed-classes.txt13
-rw-r--r--ravenwood/ravenwood-services-jarjar-rules.txt11
-rw-r--r--ravenwood/services-test/Android.bp21
-rw-r--r--ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java83
-rw-r--r--ravenwood/services.core-ravenwood-policies.txt7
-rw-r--r--services/core/java/com/android/server/SerialService.java102
-rw-r--r--services/core/java/com/android/server/SystemService.java1
-rw-r--r--services/core/java/com/android/server/SystemServiceManager.java35
-rw-r--r--services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java1
-rw-r--r--services/core/java/com/android/server/utils/TimingsTraceAndSlog.java1
-rw-r--r--services/java/com/android/server/SystemServer.java9
28 files changed, 782 insertions, 42 deletions
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 633702233cf4..2df6d5811d44 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -78,6 +78,73 @@ java_genrule {
}
java_library {
+ name: "services.core-for-hoststubgen",
+ installable: false, // host only jar.
+ static_libs: [
+ "services.core",
+ ],
+ sdk_version: "core_platform",
+ visibility: ["//visibility:private"],
+}
+
+java_genrule {
+ name: "services.core.ravenwood-base",
+ tools: ["hoststubgen"],
+ cmd: "$(location hoststubgen) " +
+ "@$(location ravenwood/ravenwood-standard-options.txt) " +
+
+ "--debug-log $(location hoststubgen_services.core.log) " +
+ "--stats-file $(location hoststubgen_services.core_stats.csv) " +
+
+ "--out-impl-jar $(location ravenwood.jar) " +
+
+ "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " +
+ "--gen-input-dump-file $(location hoststubgen_dump.txt) " +
+
+ "--in-jar $(location :services.core-for-hoststubgen) " +
+ "--policy-override-file $(location ravenwood/services.core-ravenwood-policies.txt) " +
+ "--annotation-allowed-classes-file $(location ravenwood/ravenwood-annotation-allowed-classes.txt) ",
+ srcs: [
+ ":services.core-for-hoststubgen",
+ "ravenwood/services.core-ravenwood-policies.txt",
+ "ravenwood/ravenwood-standard-options.txt",
+ "ravenwood/ravenwood-annotation-allowed-classes.txt",
+ ],
+ out: [
+ "ravenwood.jar",
+
+ // Following files are created just as FYI.
+ "hoststubgen_keep_all.txt",
+ "hoststubgen_dump.txt",
+
+ "hoststubgen_services.core.log",
+ "hoststubgen_services.core_stats.csv",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+java_genrule {
+ name: "services.core.ravenwood",
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
+ cmd: "cp $(in) $(out)",
+ srcs: [
+ ":services.core.ravenwood-base{ravenwood.jar}",
+ ],
+ out: [
+ "services.core.ravenwood.jar",
+ ],
+}
+
+java_library {
+ name: "services.core.ravenwood-jarjar",
+ installable: false,
+ static_libs: [
+ "services.core.ravenwood",
+ ],
+ jarjar_rules: ":ravenwood-services-jarjar-rules",
+}
+
+java_library {
name: "mockito-ravenwood-prebuilt",
installable: false,
static_libs: [
@@ -121,6 +188,7 @@ android_ravenwood_libgroup {
"android.test.mock.ravenwood",
"ravenwood-helper-runtime",
"hoststubgen-helper-runtime.ravenwood",
+ "services.core.ravenwood-jarjar",
// Provide runtime versions of utils linked in below
"junit",
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index be7199b9a0fc..db216b1af974 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -145,7 +145,7 @@ public class Instrumentation {
* reflection, but it will serve as noticeable discouragement from
* doing such a thing.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @android.ravenwood.annotation.RavenwoodKeep
private void checkInstrumenting(String method) {
// Check if we have an instrumentation context, as init should only get called by
// the system in startup processes that are being instrumented.
@@ -155,16 +155,12 @@ public class Instrumentation {
}
}
- private void checkInstrumenting$ravenwood(String method) {
- // At the moment, Ravenwood doesn't attach a Context, but we're only ever
- // running code as part of tests, so we continue quietly
- }
-
/**
* Returns if it is being called in an instrumentation environment.
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public boolean isInstrumenting() {
// Check if we have an instrumentation context, as init should only get called by
// the system in startup processes that are being instrumented.
@@ -328,6 +324,7 @@ public class Instrumentation {
*
* @see #getTargetContext
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public Context getContext() {
return mInstrContext;
}
@@ -352,6 +349,7 @@ public class Instrumentation {
*
* @see #getContext
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public Context getTargetContext() {
return mAppContext;
}
@@ -2402,6 +2400,17 @@ public class Instrumentation {
mThread = thread;
}
+ /**
+ * Only sets the Context up, keeps everything else null.
+ *
+ * @hide
+ */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public final void basicInit(Context context) {
+ mInstrContext = context;
+ mAppContext = context;
+ }
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static void checkStartActivityResult(int res, Object intent) {
diff --git a/core/java/android/hardware/SerialManager.java b/core/java/android/hardware/SerialManager.java
index 26e5129b1fb3..e2ce7230a796 100644
--- a/core/java/android/hardware/SerialManager.java
+++ b/core/java/android/hardware/SerialManager.java
@@ -29,6 +29,7 @@ import java.io.IOException;
* @hide
*/
@SystemService(Context.SERIAL_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class SerialManager {
private static final String TAG = "SerialManager";
@@ -69,6 +70,8 @@ public class SerialManager {
* @return the serial port
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = ParcelFileDescriptor.class, reason =
+ "Needs socketpair() to offer accurate emulation")
public SerialPort openSerialPort(String name, int speed) throws IOException {
try {
ParcelFileDescriptor pfd = mService.openSerialPort(name);
diff --git a/core/java/android/hardware/SerialManagerInternal.java b/core/java/android/hardware/SerialManagerInternal.java
new file mode 100644
index 000000000000..9132da06aeac
--- /dev/null
+++ b/core/java/android/hardware/SerialManagerInternal.java
@@ -0,0 +1,35 @@
+/*
+ * 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.hardware;
+
+import android.annotation.NonNull;
+import android.os.ParcelFileDescriptor;
+
+import java.util.function.Supplier;
+
+/**
+ * Internal interactions with {@link SerialManager}.
+ *
+ * @hide
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+public abstract class SerialManagerInternal {
+ public abstract void addVirtualSerialPortForTest(@NonNull String name,
+ @NonNull Supplier<ParcelFileDescriptor> supplier);
+
+ public abstract void removeVirtualSerialPortForTest(@NonNull String name);
+}
diff --git a/core/java/android/os/PermissionEnforcer.java b/core/java/android/os/PermissionEnforcer.java
index 91d22698d7df..3cc6fb5ad0ef 100644
--- a/core/java/android/os/PermissionEnforcer.java
+++ b/core/java/android/os/PermissionEnforcer.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.permission.PermissionCheckerManager;
+import android.permission.PermissionManager;
/**
* PermissionEnforcer check permissions for AIDL-generated services which use
@@ -71,6 +72,7 @@ import android.permission.PermissionCheckerManager;
* @hide
*/
@SystemService(Context.PERMISSION_ENFORCER_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class PermissionEnforcer {
private final Context mContext;
@@ -84,6 +86,8 @@ public class PermissionEnforcer {
}
/** Constructor, prefer using the fromContext static method when possible */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = PermissionManager.class,
+ reason = "Use subclass for unit tests, such as FakePermissionEnforcer")
public PermissionEnforcer(@NonNull Context context) {
mContext = context;
}
@@ -103,9 +107,19 @@ public class PermissionEnforcer {
return PermissionCheckerManager.PERMISSION_HARD_DENIED;
}
+ @android.ravenwood.annotation.RavenwoodReplace(blockedBy = AppOpsManager.class,
+ reason = "Blocked on Mainline dependencies")
+ private static int permissionToOpCode(String permission) {
+ return AppOpsManager.permissionToOpCode(permission);
+ }
+
+ private static int permissionToOpCode$ravenwood(String permission) {
+ return AppOpsManager.OP_NONE;
+ }
+
private boolean anyAppOps(@NonNull String[] permissions) {
for (String permission : permissions) {
- if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
+ if (permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
return true;
}
}
@@ -122,7 +136,7 @@ public class PermissionEnforcer {
public void enforcePermission(@NonNull String permission, int pid, int uid)
throws SecurityException {
- if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
+ if (permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
AttributionSource source = new AttributionSource(uid, null, null);
enforcePermission(permission, source);
return;
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index e96c24d677f1..0be2d3e30c33 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -25,6 +25,7 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BinderInternal;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.StatLogger;
import java.util.Map;
@@ -38,6 +39,7 @@ import java.util.Map;
* @hide
**/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public final class ServiceManager {
private static final String TAG = "ServiceManager";
private static final Object sLock = new Object();
@@ -48,9 +50,16 @@ public final class ServiceManager {
/**
* Cache for the "well known" services, such as WM and AM.
*/
+ // NOTE: this cache is designed to be populated exactly once at process
+ // start to avoid any overhead from locking
@UnsupportedAppUsage
private static Map<String, IBinder> sCache = new ArrayMap<String, IBinder>();
+ @GuardedBy("ServiceManager.class")
+ // NOTE: this cache is designed to support mutation by tests, so we require
+ // a lock to be held for all accesses
+ private static Map<String, IBinder> sCache$ravenwood;
+
/**
* We do the "slow log" at most once every this interval.
*/
@@ -115,9 +124,27 @@ public final class ServiceManager {
/** @hide */
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodKeep
public ServiceManager() {
}
+ /** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public static void init$ravenwood() {
+ synchronized (ServiceManager.class) {
+ sCache$ravenwood = new ArrayMap<>();
+ }
+ }
+
+ /** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public static void reset$ravenwood() {
+ synchronized (ServiceManager.class) {
+ sCache$ravenwood.clear();
+ sCache$ravenwood = null;
+ }
+ }
+
@UnsupportedAppUsage
private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
@@ -138,6 +165,7 @@ public final class ServiceManager {
* @hide
*/
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodReplace
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
@@ -152,12 +180,21 @@ public final class ServiceManager {
return null;
}
+ /** @hide */
+ public static IBinder getService$ravenwood(String name) {
+ synchronized (ServiceManager.class) {
+ // Ravenwood is a single-process environment, so it only needs to store locally
+ return Preconditions.requireNonNullViaRavenwoodRule(sCache$ravenwood).get(name);
+ }
+ }
+
/**
* Returns a reference to a service with the given name, or throws
* {@link ServiceNotFoundException} if none is found.
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
final IBinder binder = getService(name);
if (binder != null) {
@@ -176,6 +213,7 @@ public final class ServiceManager {
* @hide
*/
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodKeep
public static void addService(String name, IBinder service) {
addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT);
}
@@ -191,6 +229,7 @@ public final class ServiceManager {
* @hide
*/
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodKeep
public static void addService(String name, IBinder service, boolean allowIsolated) {
addService(name, service, allowIsolated, IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT);
}
@@ -207,6 +246,7 @@ public final class ServiceManager {
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @android.ravenwood.annotation.RavenwoodReplace
public static void addService(String name, IBinder service, boolean allowIsolated,
int dumpPriority) {
try {
@@ -216,6 +256,15 @@ public final class ServiceManager {
}
}
+ /** @hide */
+ public static void addService$ravenwood(String name, IBinder service, boolean allowIsolated,
+ int dumpPriority) {
+ synchronized (ServiceManager.class) {
+ // Ravenwood is a single-process environment, so it only needs to store locally
+ Preconditions.requireNonNullViaRavenwoodRule(sCache$ravenwood).put(name, service);
+ }
+ }
+
/**
* Retrieve an existing service called @a name from the
* service manager. Non-blocking.
@@ -366,6 +415,7 @@ public final class ServiceManager {
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static class ServiceNotFoundException extends Exception {
public ServiceNotFoundException(String name) {
super("No service published for: " + name);
diff --git a/core/java/android/util/TimingsTraceLog.java b/core/java/android/util/TimingsTraceLog.java
index 48a5ceae1aef..b4f4729c82b2 100644
--- a/core/java/android/util/TimingsTraceLog.java
+++ b/core/java/android/util/TimingsTraceLog.java
@@ -34,6 +34,7 @@ import java.util.List;
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class TimingsTraceLog {
// Debug boot time for every step if it's non-user build.
private static final boolean DEBUG_BOOT_TIME = !Build.IS_USER;
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 861f71992f54..37e6780a8109 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -211,6 +211,7 @@ android_ravenwood_test {
],
srcs: [
"src/android/app/ActivityManagerTest.java",
+ "src/android/content/ContextTest.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/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java
index d4784374745d..a02af788d496 100644
--- a/core/tests/coretests/src/android/content/ContextTest.java
+++ b/core/tests/coretests/src/android/content/ContextTest.java
@@ -26,6 +26,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.app.ActivityThread;
@@ -35,7 +36,9 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.ImageReader;
import android.os.UserHandle;
+import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.view.Display;
import androidx.test.core.app.ApplicationProvider;
@@ -43,6 +46,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,7 +58,23 @@ import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ContextTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().build();
+
+ @Test
+ public void testInstrumentationContext() {
+ // Confirm that we have a valid Context
+ assertNotNull(InstrumentationRegistry.getInstrumentation().getContext());
+ }
+
+ @Test
+ public void testInstrumentationTargetContext() {
+ // Confirm that we have a valid Context
+ assertNotNull(InstrumentationRegistry.getInstrumentation().getTargetContext());
+ }
+
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDisplayIdForSystemContext() {
final Context systemContext =
ActivityThread.currentActivityThread().getSystemContext();
@@ -63,6 +83,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDisplayIdForSystemUiContext() {
final Context systemUiContext =
ActivityThread.currentActivityThread().getSystemUiContext();
@@ -71,6 +92,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDisplayIdForTestContext() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -79,6 +101,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDisplayIdForDefaultDisplayContext() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -91,6 +114,7 @@ public class ContextTest {
}
@Test(expected = NullPointerException.class)
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testStartActivityAsUserNullIntentNullUser() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -98,6 +122,7 @@ public class ContextTest {
}
@Test(expected = NullPointerException.class)
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testStartActivityAsUserNullIntentNonNullUser() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -105,6 +130,7 @@ public class ContextTest {
}
@Test(expected = NullPointerException.class)
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testStartActivityAsUserNonNullIntentNullUser() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -112,6 +138,7 @@ public class ContextTest {
}
@Test(expected = RuntimeException.class)
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testStartActivityAsUserNonNullIntentNonNullUser() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -119,6 +146,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_appContext_returnsFalse() {
final Context appContext = ApplicationProvider.getApplicationContext();
@@ -126,6 +154,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_systemContext_returnsTrue() {
final Context systemContext =
ActivityThread.currentActivityThread().getSystemContext();
@@ -134,6 +163,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_systemUiContext_returnsTrue() {
final Context systemUiContext =
ActivityThread.currentActivityThread().getSystemUiContext();
@@ -142,11 +172,13 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testGetDisplayFromDisplayContextDerivedContextOnPrimaryDisplay() {
verifyGetDisplayFromDisplayContextDerivedContext(false /* onSecondaryDisplay */);
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testGetDisplayFromDisplayContextDerivedContextOnSecondaryDisplay() {
verifyGetDisplayFromDisplayContextDerivedContext(true /* onSecondaryDisplay */);
}
@@ -179,6 +211,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_ContextWrapper() {
ContextWrapper wrapper = new ContextWrapper(null /* base */);
@@ -190,6 +223,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_UiContextDerivedContext() {
final Context uiContext = createUiContext();
Context context = uiContext.createAttributionContext(null /* attributionTag */);
@@ -202,6 +236,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_UiContextDerivedDisplayContext() {
final Context uiContext = createUiContext();
final Display secondaryDisplay =
@@ -212,6 +247,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDeviceIdForSystemContext() {
final Context systemContext =
ActivityThread.currentActivityThread().getSystemContext();
@@ -220,6 +256,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDeviceIdForSystemUiContext() {
final Context systemUiContext =
ActivityThread.currentActivityThread().getSystemUiContext();
@@ -228,6 +265,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDeviceIdForTestContext() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 132804f4f91d..53897e14ecd6 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -74,11 +74,14 @@ java_library {
"androidx.test.monitor-for-device",
],
libs: [
+ "android.test.mock",
"framework-minus-apex.ravenwood",
+ "services.core.ravenwood",
"junit",
],
sdk_version: "core_current",
visibility: ["//frameworks/base"],
+ jarjar_rules: ":ravenwood-services-jarjar-rules",
}
// Carefully compiles against only test_current to support tests that
@@ -111,3 +114,9 @@ java_device_for_host {
"androidx.test.monitor",
],
}
+
+filegroup {
+ name: "ravenwood-services-jarjar-rules",
+ srcs: ["ravenwood-services-jarjar-rules.txt"],
+ visibility: ["//frameworks/base"],
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
index a920f63152fb..83a7b6e54389 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
@@ -32,4 +32,14 @@ import java.lang.annotation.Target;
@Target({METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface RavenwoodReplace {
+ /**
+ * One or more classes that aren't yet supported by Ravenwood, which is why this method is
+ * being replaced.
+ */
+ Class<?>[] blockedBy() default {};
+
+ /**
+ * General free-form description of why this method is being replaced.
+ */
+ String reason() default "";
}
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 49cef07033c1..6b6736476210 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -52,5 +52,6 @@ class android.content.BroadcastReceiver stub
method <init> ()V stub
class android.content.Context stub
method <init> ()V stub
+ method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; stub
class android.content.pm.PackageManager stub
method <init> ()V stub
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
new file mode 100644
index 000000000000..3668b03e58d3
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -0,0 +1,90 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import android.content.Context;
+import android.hardware.ISerialManager;
+import android.hardware.SerialManager;
+import android.os.PermissionEnforcer;
+import android.os.ServiceManager;
+import android.test.mock.MockContext;
+import android.util.ArrayMap;
+import android.util.Singleton;
+
+import java.util.function.Supplier;
+
+public class RavenwoodContext extends MockContext {
+ private final RavenwoodPermissionEnforcer mEnforcer = new RavenwoodPermissionEnforcer();
+
+ private final ArrayMap<Class<?>, String> mClassToName = new ArrayMap<>();
+ private final ArrayMap<String, Supplier<?>> mNameToFactory = new ArrayMap<>();
+
+ private void registerService(Class<?> serviceClass, String serviceName,
+ Supplier<?> serviceSupplier) {
+ mClassToName.put(serviceClass, serviceName);
+ mNameToFactory.put(serviceName, serviceSupplier);
+ }
+
+ public RavenwoodContext() {
+ registerService(PermissionEnforcer.class,
+ Context.PERMISSION_ENFORCER_SERVICE, () -> mEnforcer);
+ registerService(SerialManager.class,
+ Context.SERIAL_SERVICE, asSingleton(() ->
+ new SerialManager(this, ISerialManager.Stub.asInterface(
+ ServiceManager.getService(Context.SERIAL_SERVICE)))
+ ));
+ }
+
+ @Override
+ public Object getSystemService(String serviceName) {
+ // TODO: pivot to using SystemServiceRegistry
+ final Supplier<?> serviceSupplier = mNameToFactory.get(serviceName);
+ if (serviceSupplier != null) {
+ return serviceSupplier.get();
+ } else {
+ throw new UnsupportedOperationException(
+ "Service " + serviceName + " not yet supported under Ravenwood");
+ }
+ }
+
+ @Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ // TODO: pivot to using SystemServiceRegistry
+ final String serviceName = mClassToName.get(serviceClass);
+ if (serviceName != null) {
+ return serviceName;
+ } else {
+ throw new UnsupportedOperationException(
+ "Service " + serviceClass + " not yet supported under Ravenwood");
+ }
+ }
+
+ /**
+ * Wrap the given {@link Supplier} to become a memoized singleton.
+ */
+ private static <T> Supplier<T> asSingleton(Supplier<T> supplier) {
+ final Singleton<T> singleton = new Singleton<>() {
+ @Override
+ protected T create() {
+ return supplier.get();
+ }
+ };
+ return () -> {
+ return singleton.get();
+ };
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodPermissionEnforcer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodPermissionEnforcer.java
new file mode 100644
index 000000000000..42441352536e
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodPermissionEnforcer.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.platform.test.ravenwood;
+
+import static android.permission.PermissionManager.PERMISSION_GRANTED;
+
+import android.content.AttributionSource;
+import android.os.PermissionEnforcer;
+
+public class RavenwoodPermissionEnforcer extends PermissionEnforcer {
+ @Override
+ protected int checkPermission(String permission, AttributionSource source) {
+ // For the moment, since Ravenwood doesn't offer cross-process capabilities, assume all
+ // permissions are granted during tests
+ return PERMISSION_GRANTED;
+ }
+
+ @Override
+ protected int checkPermission(String permission, int pid, int uid) {
+ // For the moment, since Ravenwood doesn't offer cross-process capabilities, assume all
+ // permissions are granted during tests
+ return PERMISSION_GRANTED;
+ }
+}
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 1d5c79cf10a6..231cce95f353 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -24,11 +24,13 @@ import android.os.Build;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.ServiceManager;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.os.RuntimeInit;
+import com.android.server.LocalServices;
import org.junit.After;
import org.junit.Assert;
@@ -103,9 +105,10 @@ public class RavenwoodRuleImpl {
rule.mSystemProperties.getKeyReadablePredicate(),
rule.mSystemProperties.getKeyWritablePredicate());
- ActivityManager.init$ravenwood(rule.mCurrentUser);
+ ServiceManager.init$ravenwood();
+ LocalServices.removeAllServicesForTest();
- com.android.server.LocalServices.removeAllServicesForTest();
+ ActivityManager.init$ravenwood(rule.mCurrentUser);
if (rule.mProvideMainThread) {
final HandlerThread main = new HandlerThread(MAIN_THREAD_NAME);
@@ -113,7 +116,12 @@ public class RavenwoodRuleImpl {
Looper.setMainLooperForTest(main.getLooper());
}
- InstrumentationRegistry.registerInstance(new Instrumentation(), Bundle.EMPTY);
+ rule.mContext = new RavenwoodContext();
+ rule.mInstrumentation = new Instrumentation();
+ rule.mInstrumentation.basicInit(rule.mContext);
+ InstrumentationRegistry.registerInstance(rule.mInstrumentation, Bundle.EMPTY);
+
+ RavenwoodSystemServer.init(rule);
if (ENABLE_TIMEOUT_STACKS) {
sPendingTimeout = sTimeoutExecutor.schedule(RavenwoodRuleImpl::dumpStacks,
@@ -121,7 +129,7 @@ public class RavenwoodRuleImpl {
}
// Touch some references early to ensure they're <clinit>'ed
- Objects.requireNonNull(Build.IS_USERDEBUG);
+ Objects.requireNonNull(Build.TYPE);
Objects.requireNonNull(Build.VERSION.SDK);
}
@@ -130,17 +138,22 @@ public class RavenwoodRuleImpl {
sPendingTimeout.cancel(false);
}
+ RavenwoodSystemServer.reset(rule);
+
InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
+ rule.mInstrumentation = null;
+ rule.mContext = null;
if (rule.mProvideMainThread) {
Looper.getMainLooper().quit();
Looper.clearMainLooperForTest();
}
- com.android.server.LocalServices.removeAllServicesForTest();
-
ActivityManager.reset$ravenwood();
+ LocalServices.removeAllServicesForTest();
+ ServiceManager.reset$ravenwood();
+
android.os.SystemProperties.reset$ravenwood();
android.os.Binder.reset$ravenwood();
android.os.Process.reset$ravenwood();
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
new file mode 100644
index 000000000000..bb280f47ccd9
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
@@ -0,0 +1,85 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import android.hardware.SerialManager;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.SystemServiceManager;
+import com.android.server.utils.TimingsTraceAndSlog;
+
+public class RavenwoodSystemServer {
+ /**
+ * Set of services that we know how to provide under Ravenwood. We keep this set distinct
+ * from {@code com.android.server.SystemServer} to give us the ability to choose either
+ * "real" or "fake" implementations based on the commitments of the service owner.
+ *
+ * Map from {@code FooManager.class} to the {@code com.android.server.SystemService}
+ * lifecycle class name used to instantiate and drive that service.
+ */
+ private static final ArrayMap<Class<?>, String> sKnownServices = new ArrayMap<>();
+
+ // TODO: expand SystemService API to support dependency expression, so we don't need test
+ // authors to exhaustively declare all transitive services
+
+ static {
+ sKnownServices.put(SerialManager.class, "com.android.server.SerialService$Lifecycle");
+ }
+
+ private static TimingsTraceAndSlog sTimings;
+ private static SystemServiceManager sServiceManager;
+
+ public static void init(RavenwoodRule rule) {
+ // Avoid overhead if no services required
+ if (rule.mServicesRequired.isEmpty()) return;
+
+ sTimings = new TimingsTraceAndSlog();
+ sServiceManager = new SystemServiceManager(rule.mContext);
+ sServiceManager.setStartInfo(false,
+ SystemClock.elapsedRealtime(),
+ SystemClock.uptimeMillis());
+ LocalServices.addService(SystemServiceManager.class, sServiceManager);
+
+ for (Class<?> service : rule.mServicesRequired) {
+ final String target = sKnownServices.get(service);
+ if (target == null) {
+ throw new RuntimeException("The requested service " + service
+ + " is not yet supported under the Ravenwood deviceless testing "
+ + "environment; consider requesting support from the API owner or "
+ + "consider using Mockito; more details at go/ravenwood-docs");
+ } else {
+ sServiceManager.startService(target);
+ }
+ }
+ sServiceManager.sealStartedServices();
+
+ // TODO: expand to include additional boot phases when relevant
+ sServiceManager.startBootPhase(sTimings, SystemService.PHASE_SYSTEM_SERVICES_READY);
+ sServiceManager.startBootPhase(sTimings, SystemService.PHASE_BOOT_COMPLETED);
+ }
+
+ public static void reset(RavenwoodRule rule) {
+ // TODO: consider introducing shutdown boot phases
+
+ LocalServices.removeServiceForTest(SystemServiceManager.class);
+ sServiceManager = null;
+ sTimings = null;
+ }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index b90f112c1655..a8c24fcbd7e0 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -22,10 +22,13 @@ import static android.os.UserHandle.USER_SYSTEM;
import static org.junit.Assert.fail;
+import android.app.Instrumentation;
+import android.content.Context;
import android.platform.test.annotations.DisabledOnNonRavenwood;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.EnabledOnRavenwood;
import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.util.ArraySet;
import org.junit.Assume;
import org.junit.rules.TestRule;
@@ -122,6 +125,11 @@ public class RavenwoodRule implements TestRule {
final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
+ final ArraySet<Class<?>> mServicesRequired = new ArraySet<>();
+
+ volatile Context mContext;
+ volatile Instrumentation mInstrumentation;
+
public RavenwoodRule() {
}
@@ -192,6 +200,23 @@ public class RavenwoodRule implements TestRule {
return this;
}
+ /**
+ * Configure the set of system services that are required for this test to operate.
+ *
+ * For example, passing {@code android.hardware.SerialManager.class} as an argument will
+ * ensure that the underlying service is created, initialized, and ready to use for the
+ * duration of the test. The {@code SerialManager} instance can be obtained via
+ * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and
+ * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}.
+ */
+ public Builder setServicesRequired(Class<?>... services) {
+ mRule.mServicesRequired.clear();
+ for (Class<?> service : services) {
+ mRule.mServicesRequired.add(service);
+ }
+ return this;
+ }
+
public RavenwoodRule build() {
return mRule;
}
@@ -212,6 +237,28 @@ public class RavenwoodRule implements TestRule {
return IS_ON_RAVENWOOD;
}
+ /**
+ * Return a {@code Context} available for usage during the currently running test case.
+ *
+ * Each test should obtain needed information or references via this method;
+ * references must not be stored beyond the scope of a test case.
+ */
+ public Context getContext() {
+ return Objects.requireNonNull(mContext,
+ "Context is only available during @Test execution");
+ }
+
+ /**
+ * Return a {@code Instrumentation} available for usage during the currently running test case.
+ *
+ * Each test should obtain needed information or references via this method;
+ * references must not be stored beyond the scope of a test case.
+ */
+ public Instrumentation getInstrumentation() {
+ return Objects.requireNonNull(mInstrumentation,
+ "Instrumentation is only available during @Test execution");
+ }
+
static boolean shouldEnableOnDevice(Description description) {
if (description.isTest()) {
if (description.getAnnotation(DisabledOnNonRavenwood.class) != null) {
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index b5baef666b52..4a4c29030f3c 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -99,6 +99,7 @@ android.util.SparseSetArray
android.util.StringBuilderPrinter
android.util.TeeWriter
android.util.TimeUtils
+android.util.TimingsTraceLog
android.util.UtilConfig
android.util.Xml
@@ -152,6 +153,7 @@ android.os.ParcelFormatException
android.os.ParcelUuid
android.os.Parcelable
android.os.PatternMatcher
+android.os.PermissionEnforcer
android.os.PersistableBundle
android.os.PowerComponents
android.os.Process
@@ -159,6 +161,8 @@ android.os.RemoteCallback
android.os.RemoteCallbackList
android.os.RemoteException
android.os.ResultReceiver
+android.os.ServiceManager
+android.os.ServiceManager$ServiceNotFoundException
android.os.ServiceSpecificException
android.os.StrictMode
android.os.SystemClock
@@ -252,6 +256,9 @@ android.view.Display$HdrCapabilities
android.view.Display$Mode
android.view.DisplayInfo
+android.hardware.SerialManager
+android.hardware.SerialManagerInternal
+
android.telephony.ActivityStatsTechSpecificInfo
android.telephony.CellSignalStrength
android.telephony.ModemActivityInfo
@@ -310,3 +317,9 @@ com.android.internal.os.StoragedUidIoStatsReader
com.google.android.collect.Lists
com.google.android.collect.Maps
com.google.android.collect.Sets
+
+com.android.server.SerialService
+com.android.server.SystemService
+com.android.server.SystemServiceManager
+
+com.android.server.utils.TimingsTraceAndSlog
diff --git a/ravenwood/ravenwood-services-jarjar-rules.txt b/ravenwood/ravenwood-services-jarjar-rules.txt
new file mode 100644
index 000000000000..8fdd3408f74d
--- /dev/null
+++ b/ravenwood/ravenwood-services-jarjar-rules.txt
@@ -0,0 +1,11 @@
+# Ignore one-off class defined out in core/java/
+rule com.android.server.LocalServices @0
+rule com.android.server.pm.pkg.AndroidPackage @0
+rule com.android.server.pm.pkg.AndroidPackageSplit @0
+
+# Rename all other service internals so that tests can continue to statically
+# link services code when owners aren't ready to support on Ravenwood
+rule com.android.server.** repackaged.@0
+
+# TODO: support AIDL generated Parcelables via hoststubgen
+rule android.hardware.power.stats.** repackaged.@0
diff --git a/ravenwood/services-test/Android.bp b/ravenwood/services-test/Android.bp
new file mode 100644
index 000000000000..39858f05e80d
--- /dev/null
+++ b/ravenwood/services-test/Android.bp
@@ -0,0 +1,21 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_ravenwood_test {
+ name: "RavenwoodServicesTest",
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ ],
+ srcs: [
+ "test/**/*.java",
+ ],
+ auto_gen_config: true,
+}
diff --git a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java b/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java
new file mode 100644
index 000000000000..c1dee5d2f55b
--- /dev/null
+++ b/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.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 com.android.ravenwood;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.hardware.SerialManager;
+import android.hardware.SerialManagerInternal;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodServicesTest {
+ private static final String TEST_VIRTUAL_PORT = "virtual:example";
+
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProcessSystem()
+ .setServicesRequired(SerialManager.class)
+ .build();
+
+ @Test
+ public void testDefined() {
+ final SerialManager fromName = (SerialManager)
+ mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
+ final SerialManager fromClass =
+ mRavenwood.getContext().getSystemService(SerialManager.class);
+ assertNotNull(fromName);
+ assertNotNull(fromClass);
+ assertEquals(fromName, fromClass);
+
+ assertNotNull(LocalServices.getService(SerialManagerInternal.class));
+ }
+
+ @Test
+ public void testSimple() {
+ // Verify that we can obtain a manager, and talk to the backend service, and that no
+ // serial ports are configured by default
+ final SerialManager service = (SerialManager)
+ mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
+ final String[] ports = service.getSerialPorts();
+ assertEquals(0, ports.length);
+ }
+
+ @Test
+ public void testDriven() {
+ final SerialManager service = (SerialManager)
+ mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
+ final SerialManagerInternal internal = LocalServices.getService(
+ SerialManagerInternal.class);
+
+ internal.addVirtualSerialPortForTest(TEST_VIRTUAL_PORT, () -> {
+ throw new UnsupportedOperationException(
+ "Needs socketpair() to offer accurate emulation");
+ });
+ final String[] ports = service.getSerialPorts();
+ assertEquals(1, ports.length);
+ assertEquals(TEST_VIRTUAL_PORT, ports[0]);
+ }
+}
diff --git a/ravenwood/services.core-ravenwood-policies.txt b/ravenwood/services.core-ravenwood-policies.txt
new file mode 100644
index 000000000000..d8d563e05435
--- /dev/null
+++ b/ravenwood/services.core-ravenwood-policies.txt
@@ -0,0 +1,7 @@
+# Ravenwood "policy" file for services.core.
+
+# Keep all AIDL interfaces
+class :aidl stubclass
+
+# Keep all feature flag implementations
+class :feature_flags stubclass
diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java
index ff903a0b4c24..82c2038d8011 100644
--- a/services/core/java/com/android/server/SerialService.java
+++ b/services/core/java/com/android/server/SerialService.java
@@ -17,51 +17,123 @@
package com.android.server;
import android.annotation.EnforcePermission;
+import android.annotation.NonNull;
import android.content.Context;
import android.hardware.ISerialManager;
+import android.hardware.SerialManagerInternal;
import android.os.ParcelFileDescriptor;
+import android.os.PermissionEnforcer;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
import java.io.File;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.function.Supplier;
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class SerialService extends ISerialManager.Stub {
-
private final Context mContext;
- private final String[] mSerialPorts;
+
+ @GuardedBy("mSerialPorts")
+ private final LinkedHashMap<String, Supplier<ParcelFileDescriptor>> mSerialPorts =
+ new LinkedHashMap<>();
+
+ private static final String PREFIX_VIRTUAL = "virtual:";
public SerialService(Context context) {
+ super(PermissionEnforcer.fromContext(context));
mContext = context;
- mSerialPorts = context.getResources().getStringArray(
+
+ synchronized (mSerialPorts) {
+ final String[] serialPorts = getSerialPorts(context);
+ for (String serialPort : serialPorts) {
+ mSerialPorts.put(serialPort, () -> {
+ return native_open(serialPort);
+ });
+ }
+ }
+ }
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private static String[] getSerialPorts(Context context) {
+ return context.getResources().getStringArray(
com.android.internal.R.array.config_serialPorts);
}
+ private static String[] getSerialPorts$ravenwood(Context context) {
+ return new String[0];
+ }
+
+ public static class Lifecycle extends SystemService {
+ private SerialService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mService = new SerialService(getContext());
+ publishBinderService(Context.SERIAL_SERVICE, mService);
+ publishLocalService(SerialManagerInternal.class, mService.mInternal);
+ }
+ }
+
@EnforcePermission(android.Manifest.permission.SERIAL_PORT)
public String[] getSerialPorts() {
super.getSerialPorts_enforcePermission();
- ArrayList<String> ports = new ArrayList<String>();
- for (int i = 0; i < mSerialPorts.length; i++) {
- String path = mSerialPorts[i];
- if (new File(path).exists()) {
- ports.add(path);
+ synchronized (mSerialPorts) {
+ final ArrayList<String> ports = new ArrayList<>();
+ for (String path : mSerialPorts.keySet()) {
+ if (path.startsWith(PREFIX_VIRTUAL) || new File(path).exists()) {
+ ports.add(path);
+ }
}
+ return ports.toArray(new String[ports.size()]);
}
- String[] result = new String[ports.size()];
- ports.toArray(result);
- return result;
}
@EnforcePermission(android.Manifest.permission.SERIAL_PORT)
public ParcelFileDescriptor openSerialPort(String path) {
super.openSerialPort_enforcePermission();
- for (int i = 0; i < mSerialPorts.length; i++) {
- if (mSerialPorts[i].equals(path)) {
- return native_open(path);
+ synchronized (mSerialPorts) {
+ final Supplier<ParcelFileDescriptor> supplier = mSerialPorts.get(path);
+ if (supplier != null) {
+ return supplier.get();
+ } else {
+ throw new IllegalArgumentException("Invalid serial port " + path);
}
}
- throw new IllegalArgumentException("Invalid serial port " + path);
}
+ private final SerialManagerInternal mInternal = new SerialManagerInternal() {
+ @Override
+ public void addVirtualSerialPortForTest(@NonNull String name,
+ @NonNull Supplier<ParcelFileDescriptor> supplier) {
+ synchronized (mSerialPorts) {
+ Preconditions.checkState(!mSerialPorts.containsKey(name),
+ "Port " + name + " already defined");
+ Preconditions.checkArgument(name.startsWith(PREFIX_VIRTUAL),
+ "Port " + name + " must be under " + PREFIX_VIRTUAL);
+ mSerialPorts.put(name, supplier);
+ }
+ }
+
+ @Override
+ public void removeVirtualSerialPortForTest(@NonNull String name) {
+ synchronized (mSerialPorts) {
+ Preconditions.checkState(mSerialPorts.containsKey(name),
+ "Port " + name + " not yet defined");
+ Preconditions.checkArgument(name.startsWith(PREFIX_VIRTUAL),
+ "Port " + name + " must be under " + PREFIX_VIRTUAL);
+ mSerialPorts.remove(name);
+ }
+ }
+ };
+
private native ParcelFileDescriptor native_open(String path);
}
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 933d2596aed8..7dc9f10e646c 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -66,6 +66,7 @@ import java.util.List;
* {@hide}
*/
@SystemApi(client = Client.SYSTEM_SERVER)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public abstract class SystemService {
/** @hide */
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index a05b84baf667..20816a1b22c8 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -67,6 +67,8 @@ import java.util.concurrent.TimeUnit;
*
* {@hide}
*/
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
+@android.ravenwood.annotation.RavenwoodKeepStaticInitializer
public final class SystemServiceManager implements Dumpable {
private static final String TAG = SystemServiceManager.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -123,7 +125,8 @@ public final class SystemServiceManager implements Dumpable {
@GuardedBy("mTargetUsers")
@Nullable private TargetUser mCurrentUser;
- SystemServiceManager(Context context) {
+ @android.ravenwood.annotation.RavenwoodKeep
+ public SystemServiceManager(Context context) {
mContext = context;
mServices = new ArrayList<>();
mServiceClassnames = new ArraySet<>();
@@ -136,6 +139,7 @@ public final class SystemServiceManager implements Dumpable {
*
* @return The service instance.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public SystemService startService(String className) {
final Class<SystemService> serviceClass = loadClassFromLoader(className,
this.getClass().getClassLoader());
@@ -178,6 +182,7 @@ public final class SystemServiceManager implements Dumpable {
* Loads and initializes a class from the given classLoader. Returns the class.
*/
@SuppressWarnings("unchecked")
+ @android.ravenwood.annotation.RavenwoodKeep
private static Class<SystemService> loadClassFromLoader(String className,
ClassLoader classLoader) {
try {
@@ -201,6 +206,7 @@ public final class SystemServiceManager implements Dumpable {
* @return The service instance, never null.
* @throws RuntimeException if the service fails to start.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public <T extends SystemService> T startService(Class<T> serviceClass) {
try {
final String name = serviceClass.getName();
@@ -237,6 +243,7 @@ public final class SystemServiceManager implements Dumpable {
}
}
+ @android.ravenwood.annotation.RavenwoodKeep
public void startService(@NonNull final SystemService service) {
// Check if already started
String className = service.getClass().getName();
@@ -261,7 +268,8 @@ public final class SystemServiceManager implements Dumpable {
}
/** Disallow starting new services after this call. */
- void sealStartedServices() {
+ @android.ravenwood.annotation.RavenwoodKeep
+ public void sealStartedServices() {
mServiceClassnames = Collections.emptySet();
mServices = Collections.unmodifiableList(mServices);
}
@@ -273,6 +281,7 @@ public final class SystemServiceManager implements Dumpable {
* @param t trace logger
* @param phase The boot phase to start.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public void startBootPhase(@NonNull TimingsTraceAndSlog t, int phase) {
if (phase <= mCurrentPhase) {
throw new IllegalArgumentException("Next phase must be larger than previous");
@@ -305,13 +314,23 @@ public final class SystemServiceManager implements Dumpable {
if (phase == SystemService.PHASE_BOOT_COMPLETED) {
final long totalBootTime = SystemClock.uptimeMillis() - mRuntimeStartUptime;
t.logDuration("TotalBootTime", totalBootTime);
- SystemServerInitThreadPool.shutdown();
+ shutdownInitThreadPool();
}
}
+ @android.ravenwood.annotation.RavenwoodReplace
+ private void shutdownInitThreadPool() {
+ SystemServerInitThreadPool.shutdown();
+ }
+
+ private void shutdownInitThreadPool$ravenwood() {
+ // Thread pool not configured yet on Ravenwood; ignored
+ }
+
/**
* @return true if system has completed the boot; false otherwise.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public boolean isBootCompleted() {
return mCurrentPhase >= SystemService.PHASE_BOOT_COMPLETED;
}
@@ -646,12 +665,14 @@ public final class SystemServiceManager implements Dumpable {
}
/** Logs the failure. That's all. Tests may rely on parsing it, so only modify carefully. */
+ @android.ravenwood.annotation.RavenwoodKeep
private void logFailure(String onWhat, TargetUser curUser, String serviceName, Exception ex) {
Slog.wtf(TAG, "SystemService failure: Failure reporting " + onWhat + " of user "
+ curUser + " to service " + serviceName, ex);
}
/** Sets the safe mode flag for services to query. */
+ @android.ravenwood.annotation.RavenwoodKeep
void setSafeMode(boolean safeMode) {
mSafeMode = safeMode;
}
@@ -661,6 +682,7 @@ public final class SystemServiceManager implements Dumpable {
*
* @return safe mode flag
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public boolean isSafeMode() {
return mSafeMode;
}
@@ -668,6 +690,7 @@ public final class SystemServiceManager implements Dumpable {
/**
* @return true if runtime was restarted, false if it's normal boot
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public boolean isRuntimeRestarted() {
return mRuntimeRestarted;
}
@@ -675,6 +698,7 @@ public final class SystemServiceManager implements Dumpable {
/**
* @return Time when SystemServer was started, in elapsed realtime.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public long getRuntimeStartElapsedTime() {
return mRuntimeStartElapsedTime;
}
@@ -682,17 +706,20 @@ public final class SystemServiceManager implements Dumpable {
/**
* @return Time when SystemServer was started, in uptime.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public long getRuntimeStartUptime() {
return mRuntimeStartUptime;
}
- void setStartInfo(boolean runtimeRestarted,
+ @android.ravenwood.annotation.RavenwoodKeep
+ public void setStartInfo(boolean runtimeRestarted,
long runtimeStartElapsedTime, long runtimeStartUptime) {
mRuntimeRestarted = runtimeRestarted;
mRuntimeStartElapsedTime = runtimeStartElapsedTime;
mRuntimeStartUptime = runtimeStartUptime;
}
+ @android.ravenwood.annotation.RavenwoodKeep
private void warnIfTooLong(long duration, SystemService service, String operation) {
if (duration > SERVICE_CALL_WARN_TIME_MS) {
Slog.w(TAG, "Service " + service.getClass().getName() + " took " + duration + " ms in "
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
index 1050e8a371e8..9ea143e5c201 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
@@ -32,7 +32,6 @@ import com.android.internal.power.ModemPowerProfile;
import java.util.ArrayList;
-@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class MobileRadioPowerCalculator extends PowerCalculator {
private static final String TAG = "MobRadioPowerCalculator";
private static final boolean DEBUG = PowerCalculator.DEBUG;
diff --git a/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java b/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java
index b45c962811ad..d8dfd9f13d18 100644
--- a/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java
+++ b/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java
@@ -23,6 +23,7 @@ import android.util.TimingsTraceLog;
/**
* Helper class for reporting boot and shutdown timing metrics, also logging to {@link Slog}.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class TimingsTraceAndSlog extends TimingsTraceLog {
/**
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ee758dbd0516..e19f08cb04a1 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1486,7 +1486,6 @@ public final class SystemServer implements Dumpable {
VcnManagementService vcnManagement = null;
NetworkPolicyManagerService networkPolicy = null;
WindowManagerService wm = null;
- SerialService serial = null;
NetworkTimeUpdateService networkTimeUpdater = null;
InputManagerService inputManager = null;
TelephonyRegistry telephonyRegistry = null;
@@ -2362,13 +2361,7 @@ public final class SystemServer implements Dumpable {
if (!isWatch) {
t.traceBegin("StartSerialService");
- try {
- // Serial port support
- serial = new SerialService(context);
- ServiceManager.addService(Context.SERIAL_SERVICE, serial);
- } catch (Throwable e) {
- Slog.e(TAG, "Failure starting SerialService", e);
- }
+ mSystemServiceManager.startService(SerialService.Lifecycle.class);
t.traceEnd();
}