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

Commit c68733ec authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "[Ravenwood] Mock UiAutomation to support permission APIs" into main am: a297aabf

parents 91f8c0ea a297aabf
Loading
Loading
Loading
Loading
+19 −9
Original line number Diff line number Diff line
@@ -48,6 +48,9 @@ import android.os.SystemProperties;
import android.os.TestLooperManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.ravenwood.annotation.RavenwoodKeep;
import android.ravenwood.annotation.RavenwoodKeepPartialClass;
import android.ravenwood.annotation.RavenwoodReplace;
import android.util.AndroidRuntimeException;
import android.util.Log;
import android.view.Display;
@@ -80,7 +83,7 @@ import java.util.concurrent.TimeoutException;
 * implementation is described to the system through an AndroidManifest.xml's
 * <instrumentation> tag.
 */
@android.ravenwood.annotation.RavenwoodKeepPartialClass
@RavenwoodKeepPartialClass
public class Instrumentation {

    /**
@@ -134,7 +137,7 @@ public class Instrumentation {
    private UiAutomation mUiAutomation;
    private final Object mAnimationCompleteLock = new Object();

    @android.ravenwood.annotation.RavenwoodKeep
    @RavenwoodKeep
    public Instrumentation() {
    }

@@ -145,7 +148,7 @@ public class Instrumentation {
     * reflection, but it will serve as noticeable discouragement from
     * doing such a thing.
     */
    @android.ravenwood.annotation.RavenwoodKeep
    @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.
@@ -160,7 +163,7 @@ public class Instrumentation {
     *
     * @hide
     */
    @android.ravenwood.annotation.RavenwoodKeep
    @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.
@@ -324,7 +327,7 @@ public class Instrumentation {
     * 
     * @see #getTargetContext
     */
    @android.ravenwood.annotation.RavenwoodKeep
    @RavenwoodKeep
    public Context getContext() {
        return mInstrContext;
    }
@@ -349,7 +352,7 @@ public class Instrumentation {
     * 
     * @see #getContext
     */
    @android.ravenwood.annotation.RavenwoodKeep
    @RavenwoodKeep
    public Context getTargetContext() {
        return mAppContext;
    }
@@ -2405,10 +2408,11 @@ public class Instrumentation {
     *
     * @hide
     */
    @android.ravenwood.annotation.RavenwoodKeep
    public final void basicInit(Context instrContext, Context appContext) {
    @RavenwoodKeep
    public final void basicInit(Context instrContext, Context appContext, UiAutomation ui) {
        mInstrContext = instrContext;
        mAppContext = appContext;
        mUiAutomation = ui;
    }

    /** @hide */
@@ -2499,6 +2503,7 @@ public class Instrumentation {
     *
     * @see UiAutomation
     */
    @RavenwoodKeep
    public UiAutomation getUiAutomation() {
        return getUiAutomation(0);
    }
@@ -2537,6 +2542,7 @@ public class Instrumentation {
     *
     * @see UiAutomation
     */
    @RavenwoodReplace
    public UiAutomation getUiAutomation(@UiAutomationFlags int flags) {
        boolean mustCreateNewAutomation = (mUiAutomation == null) || (mUiAutomation.isDestroyed());

@@ -2567,11 +2573,15 @@ public class Instrumentation {
        return null;
    }

    private UiAutomation getUiAutomation$ravenwood(@UiAutomationFlags int flags) {
        return mUiAutomation;
    }

    /**
     * Takes control of the execution of messages on the specified looper until
     * {@link TestLooperManager#release} is called.
     */
    @android.ravenwood.annotation.RavenwoodKeep
    @RavenwoodKeep
    public TestLooperManager acquireLooperManager(Looper looper) {
        checkInstrumenting("acquireLooperManager");
        return new TestLooperManager(looper);
+2 −0
Original line number Diff line number Diff line
@@ -154,6 +154,8 @@ java_library {
        "framework-annotations-lib",
        "ravenwood-helper-framework-runtime",
        "ravenwood-helper-libcore-runtime",
        "hoststubgen-helper-runtime.ravenwood",
        "mockito-ravenwood-prebuilt",
    ],
    visibility: ["//frameworks/base"],
    jarjar_rules: ":ravenwood-services-jarjar-rules",
+40 −3
Original line number Diff line number Diff line
@@ -22,10 +22,14 @@ import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOS
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;

import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;

import android.app.ActivityManager;
import android.app.Instrumentation;
import android.app.ResourcesManager;
import android.app.UiAutomation;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Build;
@@ -40,6 +44,7 @@ import android.util.Log;

import androidx.test.platform.app.InstrumentationRegistry;

import com.android.hoststubgen.hosthelper.HostTestUtils;
import com.android.internal.os.RuntimeInit;
import com.android.ravenwood.RavenwoodRuntimeNative;
import com.android.ravenwood.common.RavenwoodCommonUtils;
@@ -52,8 +57,10 @@ import org.junit.runner.Description;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
@@ -125,6 +132,9 @@ public class RavenwoodRuntimeEnvironmentController {

    private static RavenwoodConfig sConfig;
    private static RavenwoodSystemProperties sProps;
    // TODO: use the real UiAutomation class instead of a mock
    private static UiAutomation sMockUiAutomation;
    private static Set<String> sAdoptedPermissions = Collections.emptySet();
    private static boolean sInitialized = false;

    /**
@@ -171,6 +181,7 @@ public class RavenwoodRuntimeEnvironmentController {
                "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");

        assertMockitoVersion();
        sMockUiAutomation = createMockUiAutomation();
    }

    /**
@@ -261,7 +272,7 @@ public class RavenwoodRuntimeEnvironmentController {

        // Prepare other fields.
        config.mInstrumentation = new Instrumentation();
        config.mInstrumentation.basicInit(config.mInstContext, config.mTargetContext);
        config.mInstrumentation.basicInit(instContext, targetContext, sMockUiAutomation);
        InstrumentationRegistry.registerInstance(config.mInstrumentation, Bundle.EMPTY);

        RavenwoodSystemServer.init(config);
@@ -300,12 +311,13 @@ public class RavenwoodRuntimeEnvironmentController {
        config.mInstrumentation = null;
        if (config.mInstContext != null) {
            ((RavenwoodContext) config.mInstContext).cleanUp();
            config.mInstContext = null;
        }
        if (config.mTargetContext != null) {
            ((RavenwoodContext) config.mTargetContext).cleanUp();
        }
        config.mInstContext = null;
            config.mTargetContext = null;
        }
        sMockUiAutomation.dropShellPermissionIdentity();

        if (config.mProvideMainThread) {
            Looper.getMainLooper().quit();
@@ -407,6 +419,31 @@ public class RavenwoodRuntimeEnvironmentController {
                () -> Class.forName("org.mockito.Matchers"));
    }

    private static UiAutomation createMockUiAutomation() {
        var mock = mock(UiAutomation.class, inv -> {
            HostTestUtils.onThrowMethodCalled();
            return null;
        });
        doAnswer(inv -> {
            sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS;
            return null;
        }).when(mock).adoptShellPermissionIdentity();
        doAnswer(inv -> {
            if (inv.getArgument(0) == null) {
                sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS;
            } else {
                sAdoptedPermissions = (Set) Set.of(inv.getArguments());
            }
            return null;
        }).when(mock).adoptShellPermissionIdentity(any());
        doAnswer(inv -> {
            sAdoptedPermissions = Collections.emptySet();
            return null;
        }).when(mock).dropShellPermissionIdentity();
        doAnswer(inv -> sAdoptedPermissions).when(mock).getAdoptedShellPermissions();
        return mock;
    }

    @SuppressWarnings("unused")  // Called from native code (ravenwood_sysprop.cpp)
    private static void checkSystemPropertyAccess(String key, boolean write) {
        boolean result = write ? sProps.isKeyWritable(key) : sProps.isKeyReadable(key);
+84 −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 com.android.ravenwoodtest.bivalenttest;

import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG;
import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;

import android.app.Instrumentation;
import android.app.UiAutomation;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;

import com.android.ravenwood.common.RavenwoodCommonUtils;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Set;

@RunWith(AndroidJUnit4.class)
public class RavenwoodUiAutomationTest {

    private Instrumentation mInstrumentation;

    @Before
    public void setup() {
        mInstrumentation = InstrumentationRegistry.getInstrumentation();
    }

    @Test
    public void testGetUiAutomation() {
        assertNotNull(mInstrumentation.getUiAutomation());
    }

    @Test
    public void testGetUiAutomationWithFlags() {
        assertNotNull(mInstrumentation.getUiAutomation(UiAutomation.FLAG_DONT_USE_ACCESSIBILITY));
    }

    @Test
    public void testShellPermissionApis() {
        var uiAutomation = mInstrumentation.getUiAutomation();
        assertTrue(uiAutomation.getAdoptedShellPermissions().isEmpty());
        uiAutomation.adoptShellPermissionIdentity();
        assertEquals(uiAutomation.getAdoptedShellPermissions(), UiAutomation.ALL_PERMISSIONS);
        uiAutomation.adoptShellPermissionIdentity((String[]) null);
        assertEquals(uiAutomation.getAdoptedShellPermissions(), UiAutomation.ALL_PERMISSIONS);
        uiAutomation.adoptShellPermissionIdentity(
                OVERRIDE_COMPAT_CHANGE_CONFIG, READ_COMPAT_CHANGE_CONFIG);
        assertEquals(uiAutomation.getAdoptedShellPermissions(),
                Set.of(OVERRIDE_COMPAT_CHANGE_CONFIG, READ_COMPAT_CHANGE_CONFIG));
        uiAutomation.dropShellPermissionIdentity();
        assertTrue(uiAutomation.getAdoptedShellPermissions().isEmpty());
    }

    @Test
    public void testUnsupportedMethod() {
        // Only unsupported on Ravenwood
        assumeTrue(RavenwoodCommonUtils.isOnRavenwood());
        assertThrows(RuntimeException.class,
                () -> mInstrumentation.getUiAutomation().executeShellCommand("echo ok"));
    }
}