diff options
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(); } |