diff options
author | 2024-02-15 14:12:07 -0700 | |
---|---|---|
committer | 2024-02-20 13:43:19 -0700 | |
commit | 3e91c2b2bc3b90da2513cc84b0c65ddce9fae46b (patch) | |
tree | 6d64621bdc31c20407babfa5b3229af03d6d8ab8 | |
parent | f17902765f6d34d0e07d0836e8676358605d868f (diff) |
First pass of "real" services on Ravenwood.
One of our eventual goals with Ravenwood is to support usage of
system services from test code. Robolectric takes the approach of
publishing "shadows" which are effectively fakes of the Manager
objects visible to app processes, and it unfortunately doesn't offer
a mechanism to run "real" services code.
In contrast, Ravenwood aims to support API owners progressively
offering their system services either via a "fake" approach, or by
using various levels of the "real" code that would run on a device.
This change wires up the foundational support and uses the simple
`SerialManager` example to demonstrate using the same "real" code
on both Ravenwood and devices. It also demonstrates the `Internal`
pattern being used to customize behavior for tests.
To offer as hermetic as a test environment as possible, we start
new instances of each requested service for each test. Requiring
developers to be explicit about the services they need will help
keep overhead low, especially for tests that don't need services.
Bug: 325506297
Test: atest RavenwoodServicesTest
Change-Id: Ie22436b38f2176f91dfce746b899ebab7752bbb8
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(); } |