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

Commit 0d483c9e authored by Makoto Onuki's avatar Makoto Onuki
Browse files

Introduce RavenwoodEnvironment

This class is responsible for various app/process lifecycle related
initialization, which is currently scattered across different classes,
such as RavenwoodContext / Environment / etc.

Currently, we have an init() method in *_ravenwoood classes to allow
RavenwoodDriver to push various information to them. We'll start using
this class to "pull" information from *_ravenwoood classes directly.

For example, an upcoming ContextImpl_ravenwood class will get various
states from this class directly.

Bug: 292141694
Flag: TEST_ONLY
Test: $ANDROID_BUILD_TOP/frameworks/base/ravenwood/scripts/run-ravenwood-tests.sh -s
Change-Id: I4637ad4958e5a09d0f6c5477ca38152f263056e8
parent b384973e
Loading
Loading
Loading
Loading
+2 −9
Original line number Diff line number Diff line
@@ -15,12 +15,12 @@
 */
package android.os;

import android.platform.test.ravenwood.RavenwoodEnvironment;
import android.util.Log;

import com.android.ravenwood.common.RavenwoodInternalUtils;

import java.io.File;
import java.util.Objects;

/**
 * Ravenwood redirect target class from {@link Environment}.
@@ -33,13 +33,6 @@ public class Environment_ravenwood {
    private Environment_ravenwood() {
    }

    private static volatile File sRootDir;

    /** Called by ravenwood to initialize it. */
    public static void init(File rootDir) {
        sRootDir = Objects.requireNonNull(rootDir);
    }

    /** Redirected from the corresponding {@link Environment} method. */
    static String getEnvPath(String variableName) {
        return System.getProperty("ravenwood.env." + variableName);
@@ -68,7 +61,7 @@ public class Environment_ravenwood {
            throw new RuntimeException(
                    "Path doesn't start with a '/'. Actual=" + path);
        }
        var root = Objects.requireNonNull(sRootDir);
        var root = RavenwoodEnvironment.getInstance().getRootDir();
        if (path.startsWith(root.toString())) {
            if (VERBOSE) {
                Log.v(TAG, "translate: " + path + " is already translated");
+4 −8
Original line number Diff line number Diff line
@@ -172,21 +172,17 @@ public class Log_ravenwood {
    }

    /**
     * PID. We need to use a JNI method to get it, but JNI isn't initially ready.
     * Call {@link #onRavenwoodRuntimeNativeReady} to signal when JNI is ready, at which point
     * we set this field.
     * (We don't want to call native methods that may not be fully initialized even with a
     * try-catch, because partially initialized JNI methods could crash the process.)
     * PID. Set from RavenwoodDriver
     */
    private static volatile int sPid = 0;

    private static ThreadLocal<Integer> sTid = ThreadLocal.withInitial(Process::myTid);

    /**
     * Call it when {@link RavenwoodRuntimeNative} is usable.
     * Call it when PID is available.
     */
    public static void onRavenwoodRuntimeNativeReady() {
        sPid = Process.myPid();
    public static void setPid(int pid) {
        sPid = pid;
    }

    private static int getPid() {
+2 −0
Original line number Diff line number Diff line
@@ -38,6 +38,8 @@ public class RavenwoodInternalUtils {
    private RavenwoodInternalUtils() {
    }

    public static final String ANDROID_PACKAGE_NAME = "android";

    /**
     * If set to "1", we enable the verbose logging.
     *
+3 −4
Original line number Diff line number Diff line
@@ -63,7 +63,6 @@ public class RavenwoodContext extends RavenwoodBaseContext {
    private final ArrayMap<Class<?>, String> mClassToName = new ArrayMap<>();
    private final ArrayMap<String, Supplier<?>> mNameToFactory = new ArrayMap<>();

    private final File mDataDir;
    private final Supplier<Resources> mResourcesSupplier;

    private Application mAppContext;
@@ -89,8 +88,6 @@ public class RavenwoodContext extends RavenwoodBaseContext {
        mMainThread = mainThread;
        mResourcesSupplier = resourcesSupplier;

        mDataDir = RavenwoodDriver.sAppDataDir;

        // Services provided by a typical shipping device
        registerService(ClipboardManager.class,
                Context.CLIPBOARD_SERVICE, memoize(() ->
@@ -212,7 +209,9 @@ public class RavenwoodContext extends RavenwoodBaseContext {

    @Override
    public File getDataDir() {
        return mDataDir;
        // Create the dir lazily upon request. Note, "android" package doesn't have a
        // data dir. This would throw.
        return RavenwoodEnvironment.getInstance().getAppDataDir(mPackageName);
    }

    @Override
+46 −75
Original line number Diff line number Diff line
@@ -18,9 +18,9 @@ package android.platform.test.ravenwood;

import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.UserHandle.SYSTEM;
import static android.platform.test.ravenwood.RavenwoodSystemServer.ANDROID_PACKAGE_NAME;

import static com.android.modules.utils.ravenwood.RavenwoodHelper.RavenwoodInternal.RAVENWOOD_RUNTIME_PATH_JAVA_SYSPROP;
import static com.android.ravenwood.common.RavenwoodInternalUtils.ANDROID_PACKAGE_NAME;
import static com.android.ravenwood.common.RavenwoodInternalUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
import static com.android.ravenwood.common.RavenwoodInternalUtils.RAVENWOOD_INST_RESOURCE_APK;
import static com.android.ravenwood.common.RavenwoodInternalUtils.RAVENWOOD_RESOURCE_APK;
@@ -52,10 +52,8 @@ import android.os.Binder;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Environment_ravenwood;
import android.os.HandlerThread;
import android.os.Handler_ravenwood;
import android.os.Looper;
import android.os.Looper_ravenwood;
import android.os.Message;
import android.os.Process_ravenwood;
@@ -74,7 +72,6 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.RuntimeInit;
import com.android.ravenwood.RavenwoodRuntimeNative;
import com.android.ravenwood.RavenwoodRuntimeState;
import com.android.ravenwood.common.RavenwoodInternalUtils;
import com.android.ravenwood.common.SneakyThrow;
import com.android.server.LocalServices;
@@ -85,9 +82,7 @@ import org.junit.internal.management.ManagementFactory;
import org.junit.runner.Description;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Locale;
@@ -117,7 +112,7 @@ public class RavenwoodDriver {
    private static final PrintStream sStdErr = System.err;

    private static final String MAIN_THREAD_NAME = "Ravenwood:Main";
    private static final String TESTS_THREAD_NAME = "Ravenwood:Test";
    private static final String TEST_THREAD_NAME = "Ravenwood:Test";

    private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer";
    private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
@@ -192,7 +187,7 @@ public class RavenwoodDriver {

    // TODO: expose packCallingIdentity function in libbinder and use it directly
    // See: packCallingIdentity in frameworks/native/libs/binder/IPCThreadState.cpp
    private static long packBinderIdentityToken(
    static long packBinderIdentityToken(
            boolean hasExplicitIdentity, int callingUid, int callingPid) {
        long res = ((long) callingUid << 32) | callingPid;
        if (hasExplicitIdentity) {
@@ -221,32 +216,16 @@ public class RavenwoodDriver {
    private static final String DEFAULT_INSTRUMENTATION_CLASS =
            "androidx.test.runner.AndroidJUnitRunner";

    static volatile Thread sTestThread;
    static volatile HandlerThread sMainThread;

    private static final int sMyPid = new Random().nextInt(100, 32768);
    private static int sTargetSdkLevel;

    private static String sTestPackageName;
    private static String sTargetPackageName;
    private static String sInstrumentationClass;

    static volatile RavenwoodContext sInstContext;
    static volatile RavenwoodContext sTargetContext;
    static volatile Application sTargetApplication;
    private static Instrumentation sInstrumentation;
    private static final long sCallingIdentity =
            packBinderIdentityToken(false, FIRST_APPLICATION_UID, sMyPid);

    static volatile File sRootDir;
    static volatile File sAppDataDir;

    /**
     * Initialize the global environment.
     */
    public static void globalInitOnce() {
        sTestThread = Thread.currentThread();
        Thread.currentThread().setName(TESTS_THREAD_NAME);
        Thread.currentThread().setName(TEST_THREAD_NAME);
        synchronized (sInitializationLock) {
            if (!sInitialized) {
                // globalInitOnce() is called from class initializer, which cause
@@ -282,7 +261,7 @@ public class RavenwoodDriver {
        }
    }

    private static void globalInitInner() throws IOException {
    private static void globalInitInner() throws Exception {
        // We haven't initialized liblog yet, so directly write to System.out here.
        RavenwoodInternalUtils.log(TAG, "globalInitInner()");

@@ -313,7 +292,6 @@ public class RavenwoodDriver {
        System.load(RavenwoodInternalUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME));

        Log_ravenwood.setLogLevels(getLogTags());
        Log_ravenwood.onRavenwoodRuntimeNativeReady();

        // Do the basic set up for the android sysprops.
        RavenwoodSystemProperties.initialize();
@@ -356,22 +334,12 @@ public class RavenwoodDriver {
        System.setProperty("android.junit.runner",
                "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");

        loadRavenwoodProperties();

        assertMockitoVersion();

        sRootDir = Files.createTempDirectory("ravenwood-root-dir-").toFile();
        sAppDataDir = new File(sRootDir, "data/app/appdatadir/");
        sAppDataDir.mkdirs();
        Environment_ravenwood.init(sRootDir);

        Log.i(TAG, "TargetPackageName=" + sTargetPackageName);
        Log.i(TAG, "TestPackageName=" + sTestPackageName);
        Log.i(TAG, "TargetSdkLevel=" + sTargetSdkLevel);
        Looper_ravenwood.sDispatcher = RavenwoodDriver::dispatchMessage;

        RavenwoodRuntimeState.sUid = FIRST_APPLICATION_UID;
        RavenwoodRuntimeState.sPid = sMyPid;
        RavenwoodRuntimeState.sTargetSdkLevel = sTargetSdkLevel;
        var env = createEnvironment();
        Log_ravenwood.setPid(env.getPid());

        RavenwoodUtils.sPendingExceptionThrower =
                RavenwoodDriver::maybeThrowPendingRecoverableUncaughtExceptionNoClear;
@@ -380,11 +348,6 @@ public class RavenwoodDriver {
            return null;
        };

        final var main = new HandlerThread(MAIN_THREAD_NAME);
        sMainThread = main;
        main.start();
        Looper_ravenwood.sDispatcher = RavenwoodDriver::dispatchMessage;
        Looper.setMainLooperForTest(main.getLooper());


        ServiceManager.init$ravenwood();
@@ -392,8 +355,7 @@ public class RavenwoodDriver {

        ActivityManager.init$ravenwood(SYSTEM.getIdentifier());

        final boolean isSelfInstrumenting =
                Objects.equals(sTestPackageName, sTargetPackageName);
        final boolean isSelfInstrumenting = env.isSelfInstrumenting();

        // This will load the resources from the apk set to `resource_apk` in the build file.
        // This is supposed to be the "target app"'s resources.
@@ -415,9 +377,9 @@ public class RavenwoodDriver {
        }

        sInstContext = new RavenwoodContext(
                sTestPackageName, main, instResourcesLoader);
                env.getInstPackageName(), env.getMainThread(), instResourcesLoader);
        sTargetContext = new RavenwoodContext(
                sTargetPackageName, main, targetResourcesLoader);
                env.getTargetPackageName(), env.getMainThread(), targetResourcesLoader);

        // Set up app context. App context is always created for the target app.
        var application = new Application();
@@ -438,25 +400,18 @@ public class RavenwoodDriver {

        final Supplier<Resources> systemResourcesLoader = () -> loadResources(null);

        var systemServerContext =
                new RavenwoodContext(ANDROID_PACKAGE_NAME, main, systemResourcesLoader);
        var systemServerContext = new RavenwoodContext(
                ANDROID_PACKAGE_NAME, env.getMainThread(), systemResourcesLoader);

        var uiAutomation = new UiAutomation(sInstContext, new IUiAutomationConnection.Default());

        var instArgs = Bundle.EMPTY;
        RavenwoodUtils.runOnMainThreadSync(() -> {
            var instClassName = withDefault(sInstrumentationClass, DEFAULT_INSTRUMENTATION_CLASS);
            try {
                var clazz = Class.forName(instClassName);
                var clazz = Class.forName(env.getInstrumentationClass());
                sInstrumentation = (Instrumentation) clazz.getConstructor().newInstance();
            } catch (ReflectiveOperationException e) {
                if (sInstrumentationClass != null) {
                    // If the class is explicitly set, it is an error if the class is not found
                SneakyThrow.sneakyThrow(e);
                } else {
                    // Fallback to the platform instrumentation
                    sInstrumentation = new Instrumentation();
                }
            }

            sInstrumentation.basicInit(sInstContext, sTargetContext, uiAutomation);
@@ -466,7 +421,7 @@ public class RavenwoodDriver {

        RavenwoodSystemServer.init(systemServerContext);

        initializeCompatIds();
        initializeCompatIds(env);
    }

    /**
@@ -481,16 +436,29 @@ public class RavenwoodDriver {
        return logTags;
    }

    private static void loadRavenwoodProperties() {
        var props = RavenwoodSystemProperties.readProperties("ravenwood.properties");
    private static RavenwoodEnvironment createEnvironment() throws Exception {
        final var props = RavenwoodSystemProperties.readProperties("ravenwood.properties");

        sTargetSdkLevel = withDefault(
        // TODO(b/377765941) Read them from the manifest too?
        var targetSdkLevel = withDefault(
                parseNullableInt(props.get("targetSdkVersionInt")), DEFAULT_TARGET_SDK_LEVEL);
        sTargetPackageName = withDefault(props.get("packageName"), DEFAULT_PACKAGE_NAME);
        sTestPackageName = withDefault(props.get("instPackageName"), sTargetPackageName);
        sInstrumentationClass = props.get("instrumentationClass");
        var targetPackageName = withDefault(props.get("packageName"), DEFAULT_PACKAGE_NAME);
        var testPackageName = withDefault(props.get("instPackageName"), targetPackageName);
        var instrumentationClass = withDefault(props.get("instrumentationClass"),
                DEFAULT_INSTRUMENTATION_CLASS);

        // TODO(b/377765941) Read them from the manifest too?
        // TODO: Why do we use a random PID? We can get the real PID via JNI. Why not use that?

        return RavenwoodEnvironment.init(
                FIRST_APPLICATION_UID,
                new Random().nextInt(100, 32768),
                targetSdkLevel,
                targetPackageName,
                testPackageName,
                instrumentationClass,
                Thread.currentThread(), // Test thread
                new HandlerThread(MAIN_THREAD_NAME)
        );
    }

    private static void maybeThrowUnrecoverableUncaughtExceptionIfDetected() {
@@ -508,7 +476,8 @@ public class RavenwoodDriver {
        UiAutomation_ravenwood.reset();
        Process_ravenwood.reset();
        DeviceConfig_ravenwood.reset();
        Binder.restoreCallingIdentity(sCallingIdentity);
        Binder.restoreCallingIdentity(
                RavenwoodEnvironment.getInstance().getDefaultCallingIdentity());

        SystemProperties.clearChangeCallbacksForTest();

@@ -527,7 +496,8 @@ public class RavenwoodDriver {
        maybeThrowUnrecoverableUncaughtExceptionIfDetected();

        // TODO(b/375272444): this is a hacky workaround to ensure binder identity
        Binder.restoreCallingIdentity(sCallingIdentity);
        Binder.restoreCallingIdentity(
                RavenwoodEnvironment.getInstance().getDefaultCallingIdentity());

        scheduleTimeout();
    }
@@ -562,7 +532,7 @@ public class RavenwoodDriver {
        }
    }

    private static void initializeCompatIds() {
    private static void initializeCompatIds(RavenwoodEnvironment env) {
        // Set up compat-IDs for the app side.
        // TODO: Inside the system server, all the compat-IDs should be enabled,
        // Due to the `AppCompatCallbacks.install(new long[0], new long[0] ...` call in
@@ -570,8 +540,8 @@ public class RavenwoodDriver {

        // Compat framework only uses the package name and the target SDK level.
        ApplicationInfo appInfo = new ApplicationInfo();
        appInfo.packageName = sTargetPackageName;
        appInfo.targetSdkVersion = sTargetSdkLevel;
        appInfo.packageName = env.getTargetPackageName();
        appInfo.targetSdkVersion = env.getTargetSdkLevel();

        PlatformCompat platformCompat = null;
        try {
@@ -740,8 +710,9 @@ public class RavenwoodDriver {
                    Comparator.comparingLong(Thread::getId)).collect(Collectors.toList());

            // Put the test and the main thread at the top.
            var testThread = sTestThread;
            var mainThread = sMainThread;
            var env = RavenwoodEnvironment.getInstance();
            var testThread = env.getTestThread();
            var mainThread = env.getMainThread();
            if (mainThread != null) {
                threads.remove(mainThread);
                threads.add(0, mainThread);
Loading