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

Commit 7d03f382 authored by Zhi Dou's avatar Zhi Dou Committed by Gerrit Code Review
Browse files

Merge "add PlatformAconfigPackage" into main

parents 90aeed86 cdf17dc1
Loading
Loading
Loading
Loading
+2 −5
Original line number Diff line number Diff line
@@ -154,18 +154,15 @@ java_library {
java_library {
    name: "aconfig_storage_reader_java",
    srcs: [
        "srcs/android/os/flagging/PlatformAconfigPackageInternal.java",
        "srcs/android/os/flagging/*.java",
    ],
    libs: [
        "unsupportedappusage",
        "strict_mode_stub",
        "aconfig_storage_stub",
    ],
    static_libs: [
        "aconfig_storage_file_java",
    ],
    sdk_version: "core_current",
    host_supported: true,
    sdk_version: "current",
    visibility: [
        "//frameworks/base",
        "//build/make/tools/aconfig/aconfig_storage_read_api/tests",
+168 −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 android.os.flagging;

import android.aconfig.storage.AconfigStorageException;
import android.aconfig.storage.FlagTable;
import android.aconfig.storage.FlagValueList;
import android.aconfig.storage.PackageTable;
import android.compat.annotation.UnsupportedAppUsage;
import android.util.Log;

import java.io.Closeable;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * An {@code aconfig} package containing the enabled state of its flags.
 *
 * <p><strong>Note: this is intended only to be used by generated code. To determine if a given flag
 * is enabled in app code, the generated android flags should be used.</strong>
 *
 * <p>This class is used to read the flag from platform Aconfig Package.Each instance of this class
 * will cache information related to one package. To read flags from a different package, a new
 * instance of this class should be {@link #load loaded}.
 *
 * @hide
 */
public class PlatformAconfigPackage {
    private static final String TAG = "PlatformAconfigPackage";
    private static final String MAP_PATH = "/metadata/aconfig/maps/";
    private static final String BOOT_PATH = "/metadata/aconfig/boot/";

    private static final Map<String, PackageTable> sPackageTableCache = new HashMap<>();

    private FlagTable mFlagTable;
    private FlagValueList mFlagValueList;

    private int mPackageBooleanStartOffset = -1;
    private int mPackageId = -1;

    private PlatformAconfigPackage() {}

    /** @hide */
    @UnsupportedAppUsage
    public static final Set<String> PLATFORM_PACKAGE_MAP_FILES =
            Set.of("system.package.map", "vendor.package.map", "product.package.map");

    static {
        for (String pf : PLATFORM_PACKAGE_MAP_FILES) {
            try {
                PackageTable pTable = PackageTable.fromBytes(mapStorageFile(MAP_PATH + pf));
                for (String packageName : pTable.getPackageList()) {
                    sPackageTableCache.put(packageName, pTable);
                }
            } catch (Exception e) {
                // pass
                Log.w(TAG, e.toString());
            }
        }
    }

    /**
     * Loads a platform Aconfig Package from Aconfig Storage.
     *
     * <p>This method attempts to load the specified platform Aconfig package.
     *
     * @param packageName The name of the Aconfig package to load.
     * @return An instance of {@link PlatformAconfigPackage}, which may be empty if the package is
     *     not found in the container. Null if the package is not found in platform partitions.
     * @throws AconfigStorageReadException if there is an error reading from Aconfig Storage, such
     *     as if the storage system is not found, or there is an error reading the storage file. The
     *     specific error code can be got using {@link AconfigStorageReadException#getErrorCode()}.
     * @hide
     */
    @UnsupportedAppUsage
    public static PlatformAconfigPackage load(String packageName) {
        try {
            PlatformAconfigPackage aconfigPackage = new PlatformAconfigPackage();
            PackageTable pTable = sPackageTableCache.get(packageName);
            if (pTable == null) {
                return null;
            }
            PackageTable.Node pNode = pTable.get(packageName);
            String container = pTable.getHeader().getContainer();
            aconfigPackage.mFlagTable =
                    FlagTable.fromBytes(mapStorageFile(MAP_PATH + container + ".flag.map"));
            aconfigPackage.mFlagValueList =
                    FlagValueList.fromBytes(mapStorageFile(BOOT_PATH + container + ".val"));
            aconfigPackage.mPackageBooleanStartOffset = pNode.getBooleanStartIndex();
            aconfigPackage.mPackageId = pNode.getPackageId();
            return aconfigPackage;
        } catch (AconfigStorageException e) {
            throw new AconfigStorageReadException(
                    e.getErrorCode(), "Fail to create AconfigPackage", e);
        } catch (Exception e) {
            throw new AconfigStorageReadException(
                    AconfigStorageReadException.ERROR_GENERIC,
                    "Fail to create PlatformAconfigPackage",
                    e);
        }
    }

    /**
     * Retrieves the value of a boolean flag.
     *
     * <p>This method retrieves the value of the specified flag. If the flag exists within the
     * loaded Aconfig Package, its value is returned. Otherwise, the provided `defaultValue` is
     * returned.
     *
     * @param flagName The name of the flag (excluding any package name prefix).
     * @param defaultValue The value to return if the flag is not found.
     * @return The boolean value of the flag, or `defaultValue` if the flag is not found.
     * @hide
     */
    @UnsupportedAppUsage
    public boolean getBooleanFlagValue(String flagName, boolean defaultValue) {
        FlagTable.Node fNode = mFlagTable.get(mPackageId, flagName);
        if (fNode == null) {
            return defaultValue;
        }
        return mFlagValueList.getBoolean(fNode.getFlagIndex() + mPackageBooleanStartOffset);
    }

    // Map a storage file given file path
    private static MappedByteBuffer mapStorageFile(String file) {
        FileChannel channel = null;
        try {
            channel = FileChannel.open(Paths.get(file), StandardOpenOption.READ);
            return channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
        } catch (Exception e) {
            throw new AconfigStorageReadException(
                    AconfigStorageReadException.ERROR_CANNOT_READ_STORAGE_FILE,
                    "Fail to mmap storage",
                    e);
        } finally {
            quietlyDispose(channel);
        }
    }

    private static void quietlyDispose(Closeable closable) {
        try {
            if (closable != null) {
                closable.close();
            }
        } catch (Exception e) {
            // no need to care, at least as of now
        }
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -45,7 +45,7 @@ public class PlatformAconfigPackageInternalTest {
    private static final Set<String> PLATFORM_CONTAINERS = Set.of("system", "vendor", "product");

    @Test
    public void testAconfigPackageInternal_load() throws IOException {
    public void testPlatformAconfigPackageInternal_load() throws IOException {
        List<parsed_flag> flags = DeviceProtos.loadAndParseFlagProtos();
        Map<String, PlatformAconfigPackageInternal> readerMap = new HashMap<>();
        StorageFileProvider fp = StorageFileProvider.getDefaultProvider();
@@ -82,7 +82,7 @@ public class PlatformAconfigPackageInternalTest {
    }

    @Test
    public void testAconfigPackage_load_withError() throws IOException {
    public void testPlatformAconfigPackage_load_withError() throws IOException {
        // container not found fake_container
        AconfigStorageException e =
                assertThrows(
+88 −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 android.aconfig.storage.test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import android.aconfig.DeviceProtos;
import android.aconfig.nano.Aconfig;
import android.aconfig.nano.Aconfig.parsed_flag;
import android.aconfig.storage.FlagTable;
import android.aconfig.storage.FlagValueList;
import android.aconfig.storage.PackageTable;
import android.aconfig.storage.StorageFileProvider;
import android.os.flagging.PlatformAconfigPackage;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

@RunWith(JUnit4.class)
public class PlatformAconfigPackageTest {

    private static final Set<String> PLATFORM_CONTAINERS = Set.of("system", "vendor", "product");

    @Test
    public void testPlatformAconfigPackage_load() throws IOException {
        List<parsed_flag> flags = DeviceProtos.loadAndParseFlagProtos();
        Map<String, PlatformAconfigPackage> readerMap = new HashMap<>();
        StorageFileProvider fp = StorageFileProvider.getDefaultProvider();

        for (parsed_flag flag : flags) {
            if (flag.permission == Aconfig.READ_ONLY && flag.state == Aconfig.DISABLED) {
                continue;
            }
            String container = flag.container;
            String packageName = flag.package_;
            String flagName = flag.name;
            if (!PLATFORM_CONTAINERS.contains(container)) continue;

            PackageTable pTable = fp.getPackageTable(container);
            PackageTable.Node pNode = pTable.get(packageName);
            FlagTable fTable = fp.getFlagTable(container);
            FlagTable.Node fNode = fTable.get(pNode.getPackageId(), flagName);
            FlagValueList fList = fp.getFlagValueList(container);

            int index = pNode.getBooleanStartIndex() + fNode.getFlagIndex();
            boolean rVal = fList.getBoolean(index);

            long fingerprint = pNode.getPackageFingerprint();

            PlatformAconfigPackage reader = readerMap.get(packageName);
            if (reader == null) {
                reader = PlatformAconfigPackage.load(packageName);
                readerMap.put(packageName, reader);
            }
            boolean jVal = reader.getBooleanFlagValue(flagName, !rVal);

            assertEquals(rVal, jVal);
        }
    }

    @Test
    public void testPlatformAconfigPackage_load_withError() throws IOException {
        // package not found
        assertNull(PlatformAconfigPackage.load("fake_container"));
    }
}
+36 −0
Original line number Diff line number Diff line
@@ -16,46 +16,21 @@

package android.os.flagging;

public class AconfigStorageReadException extends RuntimeException {
import java.util.Set;

    /** Generic error code indicating an unspecified Aconfig Storage error. */
    public static final int ERROR_GENERIC = 0;

    /** Error code indicating that the Aconfig Storage system is not found on the device. */
    public static final int ERROR_STORAGE_SYSTEM_NOT_FOUND = 1;

    /** Error code indicating that the requested configuration package is not found. */
    public static final int ERROR_PACKAGE_NOT_FOUND = 2;

    /** Error code indicating that the specified container is not found. */
    public static final int ERROR_CONTAINER_NOT_FOUND = 3;

    /** Error code indicating that there was an error reading the Aconfig Storage file. */
    public static final int ERROR_CANNOT_READ_STORAGE_FILE = 4;

    public static final int ERROR_FILE_FINGERPRINT_MISMATCH = 5;

    public AconfigStorageReadException(int errorCode, String msg) {
        super(msg);
        throw new UnsupportedOperationException("Stub!");
    }

    public AconfigStorageReadException(int errorCode, String msg, Throwable cause) {
        super(msg, cause);
        throw new UnsupportedOperationException("Stub!");
    }
/*
 * This class allows generated aconfig code to compile independently of the framework.
 */
public class PlatformAconfigPackage {

    public AconfigStorageReadException(int errorCode, Throwable cause) {
        super(cause);
        throw new UnsupportedOperationException("Stub!");
    }
    public static final Set<String> PLATFORM_PACKAGE_MAP_FILES =
            Set.of("system.package.map", "vendor.package.map", "product.package.map");

    public int getErrorCode() {
    public static PlatformAconfigPackage load(String packageName) {
        throw new UnsupportedOperationException("Stub!");
    }

    @Override
    public String getMessage() {
    public boolean getBooleanFlagValue(String flagName, boolean defaultValue) {
        throw new UnsupportedOperationException("Stub!");
    }
}
Loading