diff options
Diffstat (limited to 'ravenwood')
7 files changed, 194 insertions, 19 deletions
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 41a4a1ad1ccb..e535f0a05a02 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -77,6 +77,7 @@ java_library { libs: [ "android.test.mock", "framework-minus-apex.ravenwood", + "ravenwood-framework", "services.core.ravenwood", "junit", ], @@ -102,6 +103,21 @@ java_library { visibility: ["//visibility:public"], } +// Library used to publish a handful of `android.ravenwood` APIs into +// the Ravenwood BCP; we don't want to publish these APIs into the BCP +// on physical devices, which is why this is a separate library +java_library { + name: "ravenwood-framework", + srcs: [ + "framework-src/**/*.java", + ], + libs: [ + "framework-minus-apex.ravenwood", + ], + sdk_version: "core_current", + visibility: ["//visibility:public"], +} + java_host_for_device { name: "androidx.test.monitor-for-device", libs: [ diff --git a/ravenwood/framework-src/android/ravenwood/example/BlueManager.java b/ravenwood/framework-src/android/ravenwood/example/BlueManager.java new file mode 100644 index 000000000000..fc713b1bd164 --- /dev/null +++ b/ravenwood/framework-src/android/ravenwood/example/BlueManager.java @@ -0,0 +1,34 @@ +/* + * 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.ravenwood.example; + +import android.annotation.SystemService; +import android.os.RemoteException; +import android.os.ServiceManager; + +@SystemService(BlueManager.SERVICE_NAME) +public class BlueManager { + public static final String SERVICE_NAME = "example_blue"; + + public String getInterfaceDescriptor() { + try { + return ServiceManager.getService(SERVICE_NAME).getInterfaceDescriptor(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/ravenwood/framework-src/android/ravenwood/example/RedManager.java b/ravenwood/framework-src/android/ravenwood/example/RedManager.java new file mode 100644 index 000000000000..381a901f234a --- /dev/null +++ b/ravenwood/framework-src/android/ravenwood/example/RedManager.java @@ -0,0 +1,34 @@ +/* + * 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.ravenwood.example; + +import android.annotation.SystemService; +import android.os.RemoteException; +import android.os.ServiceManager; + +@SystemService(RedManager.SERVICE_NAME) +public class RedManager { + public static final String SERVICE_NAME = "example_red"; + + public String getInterfaceDescriptor() { + try { + return ServiceManager.getService(SERVICE_NAME).getInterfaceDescriptor(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java index c17d0903f856..109ef76b535f 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java @@ -26,6 +26,8 @@ import android.os.Looper; import android.os.PermissionEnforcer; import android.os.ServiceManager; import android.os.UserHandle; +import android.ravenwood.example.BlueManager; +import android.ravenwood.example.RedManager; import android.test.mock.MockContext; import android.util.ArrayMap; import android.util.Singleton; @@ -53,16 +55,23 @@ public class RavenwoodContext extends MockContext { mPackageName = packageName; mMainThread = mainThread; + // Services provided by a typical shipping device registerService(ClipboardManager.class, - Context.CLIPBOARD_SERVICE, asSingleton(() -> + Context.CLIPBOARD_SERVICE, memoize(() -> new ClipboardManager(this, getMainThreadHandler()))); registerService(PermissionEnforcer.class, Context.PERMISSION_ENFORCER_SERVICE, () -> mEnforcer); registerService(SerialManager.class, - Context.SERIAL_SERVICE, asSingleton(() -> + Context.SERIAL_SERVICE, memoize(() -> new SerialManager(this, ISerialManager.Stub.asInterface( ServiceManager.getService(Context.SERIAL_SERVICE))) )); + + // Additional services we provide for testing purposes + registerService(BlueManager.class, + BlueManager.SERVICE_NAME, memoize(() -> new BlueManager())); + registerService(RedManager.class, + RedManager.SERVICE_NAME, memoize(() -> new RedManager())); } @Override @@ -143,9 +152,12 @@ public class RavenwoodContext extends MockContext { } /** - * Wrap the given {@link Supplier} to become a memoized singleton. + * Wrap the given {@link Supplier} to become memoized. + * + * The underlying {@link Supplier} will only be invoked once, and that result will be cached + * and returned for any future requests. */ - private static <T> Supplier<T> asSingleton(ThrowingSupplier<T> supplier) { + private static <T> Supplier<T> memoize(ThrowingSupplier<T> supplier) { final Singleton<T> singleton = new Singleton<>() { @Override protected T create() { diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java index 3de96c0990ea..cd6b61df392f 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java @@ -19,13 +19,19 @@ package android.platform.test.ravenwood; import android.content.ClipboardManager; import android.hardware.SerialManager; import android.os.SystemClock; +import android.ravenwood.example.BlueManager; +import android.ravenwood.example.RedManager; import android.util.ArrayMap; +import android.util.ArraySet; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; import com.android.server.utils.TimingsTraceAndSlog; +import java.util.List; +import java.util.Set; + public class RavenwoodSystemServer { /** * Set of services that we know how to provide under Ravenwood. We keep this set distinct @@ -37,16 +43,21 @@ public class RavenwoodSystemServer { */ 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 { + // Services provided by a typical shipping device sKnownServices.put(ClipboardManager.class, "com.android.server.FakeClipboardService$Lifecycle"); sKnownServices.put(SerialManager.class, "com.android.server.SerialService$Lifecycle"); + + // Additional services we provide for testing purposes + sKnownServices.put(BlueManager.class, + "com.android.server.example.BlueManagerService$Lifecycle"); + sKnownServices.put(RedManager.class, + "com.android.server.example.RedManagerService$Lifecycle"); } + private static Set<Class<?>> sStartedServices; private static TimingsTraceAndSlog sTimings; private static SystemServiceManager sServiceManager; @@ -54,6 +65,7 @@ public class RavenwoodSystemServer { // Avoid overhead if no services required if (rule.mServicesRequired.isEmpty()) return; + sStartedServices = new ArraySet<>(); sTimings = new TimingsTraceAndSlog(); sServiceManager = new SystemServiceManager(rule.mContext); sServiceManager.setStartInfo(false, @@ -61,17 +73,7 @@ public class RavenwoodSystemServer { 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); - } - } + startServices(rule.mServicesRequired); sServiceManager.sealStartedServices(); // TODO: expand to include additional boot phases when relevant @@ -85,5 +87,26 @@ public class RavenwoodSystemServer { LocalServices.removeServiceForTest(SystemServiceManager.class); sServiceManager = null; sTimings = null; + sStartedServices = null; + } + + private static void startServices(List<Class<?>> serviceClasses) { + for (Class<?> serviceClass : serviceClasses) { + // Quietly ignore duplicate requests if service already started + if (sStartedServices.contains(serviceClass)) continue; + sStartedServices.add(serviceClass); + + final String serviceName = sKnownServices.get(serviceClass); + if (serviceName == null) { + throw new RuntimeException("The requested service " + serviceClass + + " 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"); + } + + // Start service and then depth-first traversal of any dependencies + final SystemService instance = sServiceManager.startService(serviceName); + startServices(instance.getDependencies()); + } } } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index a520d4ccafa1..52ea3402fa62 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -35,6 +35,8 @@ import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; @@ -127,7 +129,7 @@ public class RavenwoodRule implements TestRule { final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties(); - final ArraySet<Class<?>> mServicesRequired = new ArraySet<>(); + final List<Class<?>> mServicesRequired = new ArrayList<>(); volatile Context mContext; volatile Instrumentation mInstrumentation; diff --git a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java b/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java new file mode 100644 index 000000000000..efe468d0df25 --- /dev/null +++ b/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java @@ -0,0 +1,54 @@ +/* + * 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 android.platform.test.ravenwood.RavenwoodRule; +import android.ravenwood.example.BlueManager; +import android.ravenwood.example.RedManager; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RavenwoodServicesDependenciesTest { + // NOTE: we carefully only ask for RedManager here, and rely on Ravenwood internals to spin + // up the implicit dependency on BlueManager + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProcessSystem() + .setServicesRequired(RedManager.class) + .build(); + + @Test + public void testDirect() { + final RedManager red = mRavenwood.getContext().getSystemService( + RedManager.class); + assertEquals("blue+red", red.getInterfaceDescriptor()); + } + + @Test + public void testIndirect() { + final BlueManager blue = mRavenwood.getContext().getSystemService( + BlueManager.class); + assertEquals("blue", blue.getInterfaceDescriptor()); + } +} |