Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3e91c2b2 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

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
parent f1790276
Loading
Loading
Loading
Loading
+68 −0
Original line number Diff line number Diff line
@@ -77,6 +77,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,
@@ -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",
+15 −6
Original line number Diff line number Diff line
@@ -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) {
+3 −0
Original line number Diff line number Diff line
@@ -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);
+35 −0
Original line number Diff line number Diff line
/*
 * 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);
}
+16 −2
Original line number Diff line number Diff line
@@ -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;
Loading