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

Commit 8288632a authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Integrate system feature codegen into SystemConfig" into main

parents 614c7d1b 2a90b8fa
Loading
Loading
Loading
Loading
+27 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ filegroup {
    srcs: [
        "**/*.java",
        "**/*.aidl",
        ":systemfeatures-gen-srcs",
        ":framework-nfc-non-updatable-sources",
        ":ranging_stack_mock_initializer",
    ],
@@ -637,3 +638,29 @@ java_library {
}

// protolog end

// Whether to enable read-only system feature codegen.
gen_readonly_feature_apis = select(release_flag("RELEASE_USE_SYSTEM_FEATURE_BUILD_FLAGS"), {
    true: "true",
    false: "false",
    default: "false",
})

// Generates com.android.internal.pm.RoSystemFeatures, optionally compiling in
// details about fixed system features defined by build flags. When disabled,
// the APIs are simply passthrough stubs with no meaningful side effects.
genrule {
    name: "systemfeatures-gen-srcs",
    cmd: "$(location systemfeatures-gen-tool) com.android.internal.pm.RoSystemFeatures " +
        // --readonly=false (default) makes the codegen an effective no-op passthrough API.
        " --readonly=" + gen_readonly_feature_apis +
        // For now, only export "android.hardware.type.*" system features APIs.
        // TODO(b/203143243): Use an intermediate soong var that aggregates all declared
        // RELEASE_SYSTEM_FEATURE_* declarations into a single arg.
        " --feature-apis=AUTOMOTIVE,WATCH,TELEVISION,EMBEDDED,PC" +
        " > $(out)",
    out: [
        "RoSystemFeatures.java",
    ],
    tools: ["systemfeatures-gen-tool"],
}
+51 −1
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import android.util.TimingsTraceLog;
import android.util.Xml;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.pm.RoSystemFeatures;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.build.UnboundedSdkLevel;
import com.android.server.pm.permission.PermissionAllowlist;
@@ -212,6 +213,30 @@ public class SystemConfig {
        }
    }

    /**
     * Utility class for testing interaction with compile-time defined system features.
     * @hide
    */
    @VisibleForTesting
    public static class Injector {
        /** Whether a system feature is defined as enabled and available at compile-time. */
        public boolean isReadOnlySystemEnabledFeature(String featureName, int version) {
            return Boolean.TRUE.equals(RoSystemFeatures.maybeHasFeature(featureName, version));
        }

        /** Whether a system feature is defined as disabled and unavailable at compile-time. */
        public boolean isReadOnlySystemDisabledFeature(String featureName, int version) {
            return Boolean.FALSE.equals(RoSystemFeatures.maybeHasFeature(featureName, version));
        }

        /** The full set of system features defined as compile-time enabled and available. */
        public ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures() {
            return RoSystemFeatures.getReadOnlySystemEnabledFeatures();
        }
    }

    private final Injector mInjector;

    // These are the built-in shared libraries that were read from the
    // system configuration files. Keys are the library names; values are
    // the individual entries that contain information such as filename
@@ -220,7 +245,7 @@ public class SystemConfig {

    // These are the features this devices supports that were read from the
    // system configuration files.
    final ArrayMap<String, FeatureInfo> mAvailableFeatures = new ArrayMap<>();
    final ArrayMap<String, FeatureInfo> mAvailableFeatures;

    // These are the features which this device doesn't support; the OEM
    // partition uses these to opt-out of features from the system image.
@@ -602,12 +627,26 @@ public class SystemConfig {
    public ArrayMap<String, Integer> getOemDefinedUids() {
        return mOemDefinedUids;
    }

    /**
     * Only use for testing. Do NOT use in production code.
     * @param readPermissions false to create an empty SystemConfig; true to read the permissions.
     */
    @VisibleForTesting
    public SystemConfig(boolean readPermissions) {
        this(readPermissions, new Injector());
    }

    /**
     * Only use for testing. Do NOT use in production code.
     * @param readPermissions false to create an empty SystemConfig; true to read the permissions.
     * @param injector Additional dependency injection for testing.
     */
    @VisibleForTesting
    public SystemConfig(boolean readPermissions, Injector injector) {
        mInjector = injector;
        mAvailableFeatures = mInjector.getReadOnlySystemEnabledFeatures();

        if (readPermissions) {
            Slog.w(TAG, "Constructing a test SystemConfig");
            readAllPermissions();
@@ -617,6 +656,9 @@ public class SystemConfig {
    }

    SystemConfig() {
        mInjector = new Injector();
        mAvailableFeatures = mInjector.getReadOnlySystemEnabledFeatures();

        TimingsTraceLog log = new TimingsTraceLog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
        log.traceBegin("readAllPermissions");
        try {
@@ -1777,6 +1819,10 @@ public class SystemConfig {
    }

    private void addFeature(String name, int version) {
        if (mInjector.isReadOnlySystemDisabledFeature(name, version)) {
            Slog.w(TAG, "Skipping feature addition for compile-time disabled feature: " + name);
            return;
        }
        FeatureInfo fi = mAvailableFeatures.get(name);
        if (fi == null) {
            fi = new FeatureInfo();
@@ -1789,6 +1835,10 @@ public class SystemConfig {
    }

    private void removeFeature(String name) {
        if (mInjector.isReadOnlySystemEnabledFeature(name, /*version=*/0)) {
            Slog.w(TAG, "Skipping feature removal for compile-time enabled feature: " + name);
            return;
        }
        if (mAvailableFeatures.remove(name) != null) {
            Slog.d(TAG, "Removed unavailable feature " + name);
        }
+167 −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.server.systemconfig

import android.content.Context
import android.content.pm.FeatureInfo
import android.util.ArrayMap
import android.util.Xml

import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.SystemConfig
import com.google.common.truth.Truth.assertThat
import org.junit.runner.RunWith
import org.junit.Rule

import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runners.JUnit4

@SmallTest
@RunWith(JUnit4::class)
class SystemConfigReadOnlyFeaturesTest {

    companion object {
        private const val FEATURE_ONE = "feature.test.1"
        private const val FEATURE_TWO = "feature.test.2"
        private const val FEATURE_RUNTIME_AVAILABLE_TEMPLATE =
        """
            <permissions>
                <feature name="%s" />
            </permissions>
        """
        private const val FEATURE_RUNTIME_DISABLED_TEMPLATE =
        """
            <permissions>
                <Disabled-feature name="%s" />
            </permissions>
        """

        fun featureInfo(featureName: String) = FeatureInfo().apply { name = featureName }
    }

    private val context: Context = InstrumentationRegistry.getInstrumentation().context

    @get:Rule
    val tempFolder = TemporaryFolder(context.filesDir)

    private val injector = TestInjector()

    private var uniqueCounter = 0

    @Test
    fun empty() {
        assertFeatures().isEmpty()
    }

    @Test
    fun readOnlyEnabled() {
        addReadOnlyEnabledFeature(FEATURE_ONE)
        addReadOnlyEnabledFeature(FEATURE_TWO)

        assertFeatures().containsAtLeast(FEATURE_ONE, FEATURE_TWO)
    }

    @Test
    fun readOnlyAndRuntimeEnabled() {
        addReadOnlyEnabledFeature(FEATURE_ONE)
        addRuntimeEnabledFeature(FEATURE_TWO)

        // No issues with matching availability.
        assertFeatures().containsAtLeast(FEATURE_ONE, FEATURE_TWO)
    }

    @Test
    fun readOnlyEnabledRuntimeDisabled() {
        addReadOnlyEnabledFeature(FEATURE_ONE)
        addRuntimeDisabledFeature(FEATURE_ONE)

        // Read-only feature availability should take precedence.
        assertFeatures().contains(FEATURE_ONE)
    }

    @Test
    fun readOnlyDisabled() {
        addReadOnlyDisabledFeature(FEATURE_ONE)

        assertFeatures().doesNotContain(FEATURE_ONE)
    }

    @Test
    fun readOnlyAndRuntimeDisabled() {
        addReadOnlyDisabledFeature(FEATURE_ONE)
        addRuntimeDisabledFeature(FEATURE_ONE)

        // No issues with matching (un)availability.
        assertFeatures().doesNotContain(FEATURE_ONE)
    }

    @Test
    fun readOnlyDisabledRuntimeEnabled() {
        addReadOnlyDisabledFeature(FEATURE_ONE)
        addRuntimeEnabledFeature(FEATURE_ONE)
        addRuntimeEnabledFeature(FEATURE_TWO)

        // Read-only feature (un)availability should take precedence.
        assertFeatures().doesNotContain(FEATURE_ONE)
        assertFeatures().contains(FEATURE_TWO)
    }

    fun addReadOnlyEnabledFeature(featureName: String) {
        injector.readOnlyEnabledFeatures[featureName] = featureInfo(featureName)
    }

    fun addReadOnlyDisabledFeature(featureName: String) {
        injector.readOnlyDisabledFeatures.add(featureName)
    }

    fun addRuntimeEnabledFeature(featureName: String) {
        FEATURE_RUNTIME_AVAILABLE_TEMPLATE.format(featureName).write()
    }

    fun addRuntimeDisabledFeature(featureName: String) {
        FEATURE_RUNTIME_DISABLED_TEMPLATE.format(featureName).write()
    }

    private fun String.write() = tempFolder.root.resolve("${uniqueCounter++}.xml")
            .writeText(this.trimIndent())

    private fun assertFeatures() = assertThat(availableFeatures().keys)

    private fun availableFeatures() = SystemConfig(false, injector).apply {
        val parser = Xml.newPullParser()
        readPermissions(parser, tempFolder.root, /*Grant all permission flags*/ 0.inv())
    }.let { it.availableFeatures }

    internal class TestInjector() : SystemConfig.Injector() {
        val readOnlyEnabledFeatures = ArrayMap<String, FeatureInfo>()
        val readOnlyDisabledFeatures = mutableSetOf<String>()

        override fun isReadOnlySystemEnabledFeature(featureName: String, version: Int): Boolean {
            return readOnlyEnabledFeatures.containsKey(featureName)
        }

        override fun isReadOnlySystemDisabledFeature(featureName: String, version: Int): Boolean {
            return readOnlyDisabledFeatures.contains(featureName)
        }

        override fun getReadOnlySystemEnabledFeatures(): ArrayMap<String, FeatureInfo> {
            return readOnlyEnabledFeatures
        }
    }
}
+6 −7
Original line number Diff line number Diff line
@@ -22,8 +22,6 @@ import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeSpec
import java.util.HashMap
import java.util.Map
import javax.lang.model.element.Modifier

/*
@@ -52,7 +50,7 @@ import javax.lang.model.element.Modifier
 *     public static boolean hasFeatureAutomotive(Context context);
 *     public static boolean hasFeatureLeanback(Context context);
 *     public static Boolean maybeHasFeature(String feature, int version);
 *     public static ArrayMap<String, FeatureInfo> getCompileTimeAvailableFeatures();
 *     public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures();
 * }
 * </pre>
 */
@@ -63,6 +61,7 @@ object SystemFeaturesGenerator {
    private val PACKAGEMANAGER_CLASS = ClassName.get("android.content.pm", "PackageManager")
    private val CONTEXT_CLASS = ClassName.get("android.content", "Context")
    private val FEATUREINFO_CLASS = ClassName.get("android.content.pm", "FeatureInfo")
    private val ARRAYMAP_CLASS = ClassName.get("android.util", "ArrayMap")
    private val ASSUME_TRUE_CLASS =
        ClassName.get("com.android.aconfig.annotations", "AssumeTrueForR8")
    private val ASSUME_FALSE_CLASS =
@@ -291,19 +290,19 @@ object SystemFeaturesGenerator {
        features: Collection<FeatureInfo>,
    ) {
        val methodBuilder =
                MethodSpec.methodBuilder("getCompileTimeAvailableFeatures")
                MethodSpec.methodBuilder("getReadOnlySystemEnabledFeatures")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .addAnnotation(ClassName.get("android.annotation", "NonNull"))
                .addJavadoc("Gets features marked as available at compile-time, keyed by name." +
                        "\n\n@hide")
                .returns(ParameterizedTypeName.get(
                        ClassName.get(Map::class.java),
                        ARRAYMAP_CLASS,
                        ClassName.get(String::class.java),
                        FEATUREINFO_CLASS))

        val availableFeatures = features.filter { it.readonly && it.version != null }
        methodBuilder.addStatement("Map<String, FeatureInfo> features = new \$T<>(\$L)",
                HashMap::class.java, availableFeatures.size)
        methodBuilder.addStatement("\$T<String, FeatureInfo> features = new \$T<>(\$L)",
                ARRAYMAP_CLASS, ARRAYMAP_CLASS, availableFeatures.size)
        if (!availableFeatures.isEmpty()) {
            methodBuilder.addStatement("FeatureInfo fi = new FeatureInfo()")
        }
+3 −4
Original line number Diff line number Diff line
@@ -13,10 +13,9 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
import android.util.ArrayMap;
import com.android.aconfig.annotations.AssumeFalseForR8;
import com.android.aconfig.annotations.AssumeTrueForR8;
import java.util.HashMap;
import java.util.Map;

/**
 * @hide
@@ -94,8 +93,8 @@ public final class RoFeatures {
     * @hide
     */
    @NonNull
    public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() {
        Map<String, FeatureInfo> features = new HashMap<>(2);
    public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures() {
        ArrayMap<String, FeatureInfo> features = new ArrayMap<>(2);
        FeatureInfo fi = new FeatureInfo();
        fi.name = PackageManager.FEATURE_WATCH;
        fi.version = 1;
Loading