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

Commit 91f3a2db authored by Makoto Onuki's avatar Makoto Onuki Committed by Android (Google) Code Review
Browse files

Merge "Experimental implementation of Instrumentation.startActivity" into main

parents 01a47415 cce841ad
Loading
Loading
Loading
Loading
+18 −1
Original line number Diff line number Diff line
@@ -15,8 +15,12 @@
 */
package android.app;

import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.platform.test.ravenwood.RavenwoodExperimentalApiChecker.onExperimentalApiCalled;

import android.content.pm.PackageManager;
import android.os.FileUtils;
import android.os.IBinder;
import android.platform.test.ravenwood.RavenwoodPackageManager;

import java.io.File;
@@ -25,7 +29,7 @@ public class ContextImpl_ravenwood {
    private static final String TAG = "ContextImpl_ravenwood";

    static PackageManager getPackageManagerInner(ContextImpl contextImpl) {
        return new RavenwoodPackageManager(contextImpl);
        return RavenwoodPackageManager.create(contextImpl);
    }

    static File ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
@@ -40,4 +44,17 @@ public class ContextImpl_ravenwood {
        }
        return file;
    }

    /** Experimental implementation */
    static int checkPermission(ContextImpl self, String permission, int pid, int uid) {
        onExperimentalApiCalled(2);
        return PERMISSION_GRANTED;
    }

    /** Experimental implementation */
    static int checkPermission(ContextImpl self, String permission, int pid, int uid,
            IBinder callerToken) {
        onExperimentalApiCalled(2);
        return PERMISSION_GRANTED;
    }
}
+31 −0
Original line number Diff line number Diff line
@@ -15,7 +15,15 @@
 */
package android.app;

import static android.platform.test.ravenwood.RavenwoodExperimentalApiChecker.onExperimentalApiCalled;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.content.Intent;
import android.os.Bundle;
import android.platform.test.ravenwood.RavenwoodUtils;
import android.util.Log;

public class Instrumentation_ravenwood {
    private static final String TAG = "Instrumentation_ravenwood";
@@ -26,4 +34,27 @@ public class Instrumentation_ravenwood {
    static void checkPendingExceptionOnRavenwood() {
        RavenwoodUtils.getMainHandler().post(() -> {});
    }

    static Activity startActivitySync(@NonNull Instrumentation inst,
            @NonNull Intent intent,
            @Nullable Bundle options) {
        onExperimentalApiCalled(2);
        return RavenwoodUtils.runOnMainThreadSync(
                () -> startActivitySyncOnMain(inst, intent, options));
    }

    @UiThread
    private static Activity startActivitySyncOnMain(
            @NonNull Instrumentation inst,
            @NonNull Intent intent,
            @Nullable Bundle options) {
        Log.logStackTrace(Log.LOG_ID_MAIN, Log.INFO, TAG,
                "startActivity: intent=" + intent + " options=" + options, 2);

        try {
            return RavenwoodActivityDriver.getInstance().createResumedActivity(intent, options);
        } catch (Exception e) {
            throw new RuntimeException("Failed to start activity. Intent=" + intent, e);
        }
    }
}
+75 −0
Original line number Diff line number Diff line
@@ -15,16 +15,31 @@
 */
package android.app;

import static android.platform.test.ravenwood.RavenwoodExperimentalApiChecker.isExperimentalApiEnabled;

import android.annotation.NonNull;
import android.app.SystemServiceRegistry.CachedServiceFetcher;
import android.app.SystemServiceRegistry.ServiceFetcher;
import android.content.ClipboardManager;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.IBinder;
import android.os.IUserManager;
import android.os.PermissionEnforcer;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.UserManager;
import android.platform.test.ravenwood.RavenwoodPermissionEnforcer;
import android.ravenwood.example.BlueManager;
import android.ravenwood.example.RedManager;
import android.view.LayoutInflater;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
import android.view.autofill.AutofillManager;
import android.view.autofill.IAutoFillManager;
import android.view.inputmethod.InputMethodManager;

import com.android.internal.policy.PhoneLayoutInflater;

public class SystemServiceRegistry_ravenwood {
    private SystemServiceRegistry_ravenwood() {
@@ -67,6 +82,8 @@ public class SystemServiceRegistry_ravenwood {
                        return new RavenwoodPermissionEnforcer();
                    }});

        maybeRegisterExperimentalServices();

        registerRavenwoodSpecificServices();
    }

@@ -87,4 +104,62 @@ public class SystemServiceRegistry_ravenwood {
                    }
                });
    }

    /**
     * Register "experimental" system services, which are _not_ supported. They're used only for
     * Ravenwood internal development.
     */
    private static void maybeRegisterExperimentalServices() {

        if (!isExperimentalApiEnabled()) {
            return;
        }

        registerService(Context.INPUT_SERVICE, InputManager.class,
                new CachedServiceFetcher<InputManager>() {
            @Override
            public InputManager createService(ContextImpl ctx) {
                return new InputManager(ctx.getOuterContext());
            }});

        registerService(Context.INPUT_METHOD_SERVICE, InputMethodManager.class,
                new ServiceFetcher<InputMethodManager>() {
            @Override
            public InputMethodManager getService(ContextImpl ctx) {
                return InputMethodManager.forContext(ctx.getOuterContext());
            }});

        registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher<WindowManager>() {
            @Override
            public WindowManager createService(ContextImpl ctx) {
                return new WindowManagerImpl(ctx);
            }});

        registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});

        registerService(Context.USER_SERVICE, UserManager.class,
                new CachedServiceFetcher<UserManager>() {
            @Override
            public UserManager createService(ContextImpl ctx) throws ServiceNotFoundException {
                IBinder b = ServiceManager.getServiceOrThrow(Context.USER_SERVICE);
                IUserManager service = IUserManager.Stub.asInterface(b);
                return new UserManager(ctx, service);
            }});

        registerService(Context.AUTOFILL_MANAGER_SERVICE, AutofillManager.class,
                new CachedServiceFetcher<AutofillManager>() {
            @Override
            public AutofillManager createService(ContextImpl ctx) throws ServiceNotFoundException {
                // Get the services without throwing as this is an optional feature
                IBinder b = ServiceManager.getService(Context.AUTOFILL_MANAGER_SERVICE);
                IAutoFillManager service = IAutoFillManager.Stub.asInterface(b);
                return new AutofillManager(ctx.getOuterContext(), service);
            }});
    }
}
+152 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.app;

import android.annotation.NonNull;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.os.Binder;
import android.os.Bundle;
import android.platform.test.ravenwood.RavenwoodExperimentalApiChecker;
import android.platform.test.ravenwood.RavenwoodUtils;
import android.view.Display;
import android.view.WindowManager;

/**
 * Experimental implementation of launching activities.
 */
@SuppressWarnings("unchecked")
public class RavenwoodActivityDriver {
    private static final RavenwoodActivityDriver sInstance = new RavenwoodActivityDriver();

    private RavenwoodActivityDriver() {
    }

    public static RavenwoodActivityDriver getInstance() {
        return sInstance;
    }

    @SuppressWarnings("unchecked")
    private static <T extends Activity> Class<T> resolveActivityClass(Intent intent) {
        try {
            // TODO: Handle the case where the component name is null.
            return (Class<T>) Class.forName(intent.getComponent().getClassName());
        } catch (Exception e) {
            throw new RuntimeException("Failed to start an activity", e);
        }
    }

    private static ActivityInfo makeActivityInfo(
            @NonNull ApplicationInfo appInfo, @NonNull Intent intent) {
        var clazz = resolveActivityClass(intent);

        // Here's an example ActivityInfo from the launcher:
        // http://screen/Beq9oSdYSntJr5k
        ActivityInfo acti = new ActivityInfo();
        acti.applicationInfo = appInfo;
        acti.enabled = true;
        acti.exported = true;
        acti.packageName = appInfo.packageName;

        acti.labelRes = 0; // TODO
        acti.launchMode = 2; // from the example
        acti.resizeMode = 2; // from the example
        acti.flags = 0; // TODO
        acti.name = clazz.getName();

        acti.processName = appInfo.packageName;

        return acti;
    }

    /**
     * Instantiate an activity and driver it to the "RESUMED" state.
     */
    public <T extends Activity> T createResumedActivity(
            Intent intent,
            Bundle activityOptions // ignored for now.
    ) throws Exception {
        RavenwoodExperimentalApiChecker.onExperimentalApiCalled(0);

        Application app = RavenwoodAppDriver.getInstance().getApplication();
        ContextImpl appImpl = ContextImpl.getImpl(app);
        ApplicationInfo appInfo = appImpl.getApplicationInfo();
        ActivityInfo activityInfo = makeActivityInfo(appInfo, intent);
        Binder token = new Binder();
        int displayId = Display.DEFAULT_DISPLAY;

        ContextImpl activityContextImpl = ContextImpl.createActivityContext(
                appImpl.mMainThread,
                appImpl.mPackageInfo,
                activityInfo,
                token,
                displayId,
                null // overrideConfiguration
        );

        T activity = (T) resolveActivityClass(intent).getConstructor().newInstance();
        activity.attach(
                activityContextImpl,
                appImpl.mMainThread,
                appImpl.mMainThread.mInstrumentation,
                token,
                0, // mIdent -- ??
                app,
                intent,
                activityInfo,
                "Activity title",
                null, // parent
                "embeddedID", // mEmbeddedID -- ??
                null, // lastNonConfigurationInstances
                null, // config
                "referrer",
                null, // IVoiceInteractor,
                null, // window
                null, // activityConfigCallback
                null, // assistToken
                null, // shareableActivityToken
                null // initialCallerInfoAccessToken
        );

        // ActivityController has this.
        // return create().start().postCreate(null).resume().visible().topActivityResumed(true);

        Bundle savedInstanceState = null;
        activity.performCreate(savedInstanceState);
        activity.performStart("Called by Ravenwood");
        activity.onPostCreate(savedInstanceState);
        activity.performResume(false, // Followed by pause
                "Called by Ravenwood");
        // ActivityController has
        // "emulate logic of ActivityThread#handleResumeActivity"

        // Make visible -- this requires some setup, which is copied from ActivityController.
        activity.getWindow().getAttributes().type =
                WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        activity.mDecor = activity.getWindow().getDecorView();
        activity.makeVisible();

        activity.performTopResumedActivityChanged(
                true, // isTop
                "Called by Ravenwood");

        RavenwoodUtils.getMainHandler().post(() ->
                activity.getWindow().getDecorView().getViewRootImpl().windowFocusChanged(true));

        return activity;
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -75,6 +75,8 @@ public final class RavenwoodAppDriver {

    private final Instrumentation mInstrumentation;

    private final RavenwoodActivityDriver mActivityDriver = RavenwoodActivityDriver.getInstance();

    /**
     * Constructor. It essentially simulates the start of an app lifecycle.
     *
@@ -174,6 +176,8 @@ public final class RavenwoodAppDriver {
        ai.sourceDir = env.getResourcesApkFile(packageName).getAbsolutePath();
        ai.publicSourceDir = ai.sourceDir;

        ai.processName = packageName;

        // TODO: Set CE/DE data dirs too.

        return ai;
Loading