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

Commit 1cc1adc0 authored by Zhi Dou's avatar Zhi Dou Committed by Automerger Merge Worker
Browse files

Merge "add PlatformAconfigPackage" into main am: 7d03f382

parents 154fc0e4 7d03f382
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