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

Commit 0727c7a9 authored by Makoto Onuki's avatar Makoto Onuki
Browse files

Handle @RavenwoodSupported to show APIs supported by...

subclasses on the dashboard.

- This is implemented as "throw" but with "supported" in the stats label.

- Also updated the comment on HSG test annotations.

- Also added "comment" field for @RavenwoodKeep, etc, just for documentation.

- Also deleted the RELEASE_TARGET_JAVA_21 golden files. (only support 21 now)

- This should support class-wide too, which will be a follow-up.

Bug: 414821464
Test: $ANDROID_BUILD_TOP/frameworks/base/ravenwood/scripts/run-ravenwood-tests.sh -s
Flag: TEST_ONLY
Change-Id: I0dc7f9648ecb2d45905c9f3142cb4a003ebd9f37
parent 3fce9f7b
Loading
Loading
Loading
Loading
+67 −0
Original line number Diff line number Diff line
@@ -95,6 +95,7 @@ import android.provider.E2eeContactKeysManager;
import android.provider.MediaStore;
import android.ravenwood.annotation.RavenwoodKeep;
import android.ravenwood.annotation.RavenwoodKeepPartialClass;
import android.ravenwood.annotation.RavenwoodSupported.SupportType;
import android.telephony.TelephonyRegistryManager;
import android.util.AttributeSet;
import android.view.Display;
@@ -856,6 +857,8 @@ public abstract class Context {
     * @return an AssetManager instance for the application's package
     * @see #getResources()
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract AssetManager getAssets();

    /**
@@ -869,9 +872,14 @@ public abstract class Context {
     * @return a Resources instance for the application's package
     * @see #getAssets()
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract Resources getResources();

    /** Return PackageManager instance to find global package information. */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext",
            comment = "Almost no APIS on PackageManager are supported yet")
    public abstract PackageManager getPackageManager();

    /** Return a ContentResolver instance for your application's package. */
@@ -888,6 +896,8 @@ public abstract class Context {
     *
     * @return The main looper.
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract Looper getMainLooper();

    /**
@@ -895,6 +905,8 @@ public abstract class Context {
     * thread associated with this context. This is the thread used to dispatch
     * calls to application components (activities, services, etc).
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public Executor getMainExecutor() {
        // This is pretty inefficient, which is why ContextImpl overrides it
        return new HandlerExecutor(new Handler(getMainLooper()));
@@ -925,6 +937,8 @@ public abstract class Context {
     * if you forget to unregister, unbind, etc.
     * </ul>
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract Context getApplicationContext();

    /** Non-activity related autofill ids are unique in the app */
@@ -1092,6 +1106,8 @@ public abstract class Context {
     *
     * @param resid The style resource describing the theme.
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract void setTheme(@StyleRes int resid);

    /** @hide Needed for some internal implementation...  not public because
@@ -1105,6 +1121,8 @@ public abstract class Context {
     * Return the Theme object associated with this Context.
     */
    @ViewDebug.ExportedProperty(deepExport = true)
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract Resources.Theme getTheme();

    /**
@@ -1167,9 +1185,13 @@ public abstract class Context {
    /**
     * Return a class loader you can use to retrieve classes in this package.
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract ClassLoader getClassLoader();

    /** Return the name of this application's package. */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract String getPackageName();

    /**
@@ -1190,6 +1212,8 @@ public abstract class Context {
     * This is not generally intended for third party application developers.
     */
    @NonNull
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public String getOpPackageName() {
        throw new RuntimeException("Not implemented. Must override in a subclass.");
    }
@@ -1201,6 +1225,9 @@ public abstract class Context {
     *
     * @return the attribution tag this context is for or {@code null} if this is the default.
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext",
            comment = "Always returns null (for now)")
    public @Nullable String getAttributionTag() {
        return null;
    }
@@ -1244,6 +1271,8 @@ public abstract class Context {
     *
     * @return String Path to the resources.
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract String getPackageResourcePath();

    /**
@@ -1361,6 +1390,8 @@ public abstract class Context {
     * @see #deleteFile
     * @see java.io.FileInputStream#FileInputStream(String)
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract FileInputStream openFileInput(String name)
        throws FileNotFoundException;

@@ -1382,6 +1413,8 @@ public abstract class Context {
     * @see #deleteFile
     * @see java.io.FileOutputStream#FileOutputStream(String)
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract FileOutputStream openFileOutput(String name, @FileMode int mode)
        throws FileNotFoundException;

@@ -1400,6 +1433,8 @@ public abstract class Context {
     * @see #fileList
     * @see java.io.File#delete()
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract boolean deleteFile(String name);

    /**
@@ -1418,6 +1453,8 @@ public abstract class Context {
     * @see #getFilesDir
     * @see #getDir
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract File getFileStreamPath(String name);

    /**
@@ -1434,6 +1471,8 @@ public abstract class Context {
     * @removed
     */
    @SuppressWarnings("HiddenAbstractMethod")
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract File getSharedPreferencesPath(String name);

    /**
@@ -1451,6 +1490,8 @@ public abstract class Context {
     *
     * @see ApplicationInfo#dataDir
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract File getDataDir();

    /**
@@ -1468,6 +1509,8 @@ public abstract class Context {
     * @see #getFileStreamPath
     * @see #getDir
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract File getFilesDir();

    /**
@@ -1515,6 +1558,8 @@ public abstract class Context {
     * @see #getDir
     * @see android.app.backup.BackupAgent
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract File getNoBackupFilesDir();

    /**
@@ -1819,6 +1864,8 @@ public abstract class Context {
     * @see #getDir
     * @see #getExternalCacheDir
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract File getCacheDir();

    /**
@@ -1840,6 +1887,8 @@ public abstract class Context {
     *
     * @return The path of the directory holding application code cache files.
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract File getCodeCacheDir();

    /**
@@ -2054,6 +2103,8 @@ public abstract class Context {
     *
     * @see #openFileOutput(String, int)
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract File getDir(String name, @FileMode int mode);

    /**
@@ -4601,6 +4652,8 @@ public abstract class Context {
     * @see #AUTHENTICATION_POLICY_SERVICE
     * @see android.security.authenticationpolicy.AuthenticationPolicyManager
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract Object getSystemService(@ServiceName @NonNull String name);

    /**
@@ -4661,6 +4714,8 @@ public abstract class Context {
     * @param serviceClass The class of the desired service.
     * @return The service name or null if the class is not a supported system service.
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract @Nullable String getSystemServiceName(@NonNull Class<?> serviceClass);

    /**
@@ -7695,6 +7750,8 @@ public abstract class Context {
    @NonNull
    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
    @TestApi
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public @CanBeALL @CanBeCURRENT UserHandle getUser() {
        return android.os.Process.myUserHandle();
    }
@@ -7705,6 +7762,8 @@ public abstract class Context {
     */
    @UnsupportedAppUsage
    @TestApi
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public @CanBeALL @CanBeCURRENT @UserIdInt int getUserId() {
        return android.os.UserHandle.myUserId();
    }
@@ -8162,6 +8221,8 @@ public abstract class Context {
     * @see #registerDeviceIdChangeListener(Executor, IntConsumer)
     * @see #isUiContext()
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public int getDeviceId() {
        throw new RuntimeException("Not implemented. Must override in a subclass.");
    }
@@ -8209,6 +8270,8 @@ public abstract class Context {
     *
     * @see #CONTEXT_RESTRICTED
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public boolean isRestricted() {
        return false;
    }
@@ -8237,6 +8300,8 @@ public abstract class Context {
     * @hide
     */
    @SuppressWarnings("HiddenAbstractMethod")
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public abstract boolean canLoadUnsafeResources();

    /**
@@ -8306,6 +8371,8 @@ public abstract class Context {
    /**
     * @hide
     */
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodContext")
    public Handler getMainThreadHandler() {
        throw new RuntimeException("Not implemented. Must override in a subclass.");
    }
+3 −0
Original line number Diff line number Diff line
@@ -89,6 +89,7 @@ import android.os.incremental.IncrementalManager;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.permission.PermissionManager;
import android.ravenwood.annotation.RavenwoodSupported.SupportType;
import android.telephony.TelephonyManager;
import android.telephony.UiccCardInfo;
import android.telephony.gba.GbaService;
@@ -8493,6 +8494,8 @@ public abstract class PackageManager {
     *             found on the system.
     */
    @NonNull
    @android.ravenwood.annotation.RavenwoodSupported(
            type = SupportType.SUBCLASS, subclass = "RavenwoodPackageManager")
    public abstract InstrumentationInfo getInstrumentationInfo(@NonNull ComponentName className,
            @InstrumentationInfoFlags int flags) throws NameNotFoundException;

+2 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.os.Looper;
import android.os.PermissionEnforcer;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.ravenwood.annotation.RavenwoodSupported.RavenwoodProvidingImplementation;
import android.ravenwood.example.BlueManager;
import android.ravenwood.example.RedManager;
import android.util.ArrayMap;
@@ -48,6 +49,7 @@ import java.nio.file.Files;
import java.util.concurrent.Executor;
import java.util.function.Supplier;

@RavenwoodProvidingImplementation(target = Context.class)
public class RavenwoodContext extends RavenwoodBaseContext {
    private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;

+3 −0
Original line number Diff line number Diff line
@@ -17,7 +17,10 @@ package android.platform.test.ravenwood;

import android.content.ComponentName;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageManager;
import android.ravenwood.annotation.RavenwoodSupported.RavenwoodProvidingImplementation;

@RavenwoodProvidingImplementation(target = PackageManager.class)
public class RavenwoodPackageManager extends RavenwoodBasePackageManager {

    private final RavenwoodContext mContext;
+188 −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 com.android.ravenwoodtest.coretest;

import static com.google.common.truth.Truth.assertThat;

import android.ravenwood.annotation.RavenwoodSupported.RavenwoodProvidingImplementation;

import org.junit.Test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Test to verify {@link android.ravenwood.annotation.RavenwoodSupported}
 * and {@link RavenwoodProvidingImplementation} are used correctly.
 */
public class RavenwoodSupportedAnnotationTest {
    private static final Class<? extends Annotation> RAVENWOOD_SUPPORTED_ANNOT =
            getThrowButSupportedAnnotation();

    @Test
    public void testContext() throws Exception {
        check("android.content.Context", "android.platform.test.ravenwood.RavenwoodContext");
    }

    @Test
    public void testPackageManager() throws Exception {
        check("android.content.pm.PackageManager",
                "android.platform.test.ravenwood.RavenwoodPackageManager");
    }

    @SuppressWarnings("unchecked")
    private static Class<? extends Annotation> getThrowButSupportedAnnotation() {
        try {
            return (Class<? extends Annotation>) Class.forName(
                    "com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrowButSupported");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static void check(String frameworkClass, String subclass) throws Exception {
        check(Class.forName(frameworkClass), Class.forName(subclass));
    }

    /** Class to hold a Method with its signature. */
    private static class MethodRef {
        public final Method method;
        private final String mSignature;

        MethodRef(Method method) {
            this.method = method;
            this.mSignature = getMethodSignature(method);
        }

        String getSignature() {
            return mSignature;
        }
    }

    /** Build a simple method signature. (method name + arg types only) */
    private static String getMethodSignature(Method method) {
        var sb = new StringBuilder();

        sb.append(method.getName());

        sb.append("(");
        for (var p : method.getParameterTypes()) {
            sb.append(p.getName());
            sb.append(",");
        }
        sb.append(")");

        return sb.toString();
    }


    /** Return all methods (including super and interfaces' ones) from a given type. */
    private static List<MethodRef> findAllMethods(Class<?> cls) throws Exception {
        var ret = new ArrayList<MethodRef>();

        collectAllMethods(cls, ret);
        ret.sort(Comparator.comparing(MethodRef::getSignature));

        return ret;
    }

    /** Collect methods from a given type. */
    private static void collectAllMethods(Class<?> cls, List<MethodRef> methods) throws Exception {
        if (cls == null || cls == Object.class) {
            return;
        }
        for (var m : cls.getDeclaredMethods()) {
            methods.add(new MethodRef(m));
        }

        // Collect super methods.
        collectAllMethods(cls.getSuperclass(), methods);

        // Collect interface methods.
        for (var i : cls.getInterfaces()) {
            collectAllMethods(i, methods);
        }
    }

    /**
     * Find methods declared in {@code subclass} that's in {@code superMethods},
     * meaning methods overriding the super methods.
     */
    private static List<MethodRef> findOverridingMethods(Class<?> subclass,
            List<MethodRef> superMethods) {
        // Create a set of super method signatures.
        var superMethodSet = new HashSet<String>();
        superMethods.forEach(superMethod -> superMethodSet.add(superMethod.getSignature()));

        var ret = new ArrayList<MethodRef>();
        for (var m : subclass.getDeclaredMethods()) {
            var sig = getMethodSignature(m);
            if (superMethodSet.contains(sig)) {
                ret.add(new MethodRef(m));
            }
        }
        return ret;
    }

    /** Convert a method list to a single string for diffing. */
    private static String toMethodListString(List<MethodRef> methods) {
        var sb = new StringBuilder();

        for (var m : methods) {
            sb.append(m.getSignature());
            sb.append("\n");
        }
        // If no methods are found, there's something wrong.
        assertThat(sb.length()).isGreaterThan(0);

        return sb.toString();
    }

    /**
     * Actual test method
     * @param frameworkClass target (base) class
     * @param subclass subclass that provides the implementation.
     */
    private static void check(Class<?> frameworkClass, Class<?> subclass) throws Exception {
        // First, check the class's annotation too.
        var anot = subclass.getAnnotation(RavenwoodProvidingImplementation.class);
        assertThat(anot).isNotNull();
        assertThat(anot.target()).isEqualTo(frameworkClass);

        // List all the methods from the target class.
        var frameworkMethods = findAllMethods(frameworkClass);

        // Extract only methods with @RavenwoodSupported.
        var supportedFrameworkMethods = frameworkMethods.stream()
                .filter(m -> m.method.isAnnotationPresent(RAVENWOOD_SUPPORTED_ANNOT))
                .collect(Collectors.toList());

        // Methods in the subclass that
        var overridingMethods = findOverridingMethods(subclass, frameworkMethods);

        supportedFrameworkMethods.sort(Comparator.comparing(MethodRef::getSignature));
        overridingMethods.sort(Comparator.comparing(MethodRef::getSignature));

        // Then compare them. They should match.
        assertThat(toMethodListString(supportedFrameworkMethods))
                .isEqualTo(toMethodListString(overridingMethods));
    }
}
Loading