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

Commit 8c1279d0 authored by Weilin Xu's avatar Weilin Xu
Browse files

Add HD radio support in broadcast radio service

Implemented the support for config flags, identifier types and metadata
types added for HD radio. Added SDK version check for V to maintain
backward compatiblity for app with SDK version lower than HAL APIs or
HD radio improvment feature flag disabled.

Bug: 280300929
Test: atest com.android.server.broadcastradio.aidl
Change-Id: I13fb70235056b3b92e7c59df906c16fd42d8d901
parent ab6db579
Loading
Loading
Loading
Loading
+19 −7
Original line number Diff line number Diff line
@@ -16,7 +16,9 @@

package com.android.server.broadcastradio.aidl;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;

import android.app.compat.CompatChanges;
import android.hardware.broadcastradio.AmFmBandRange;
@@ -43,14 +45,16 @@ import com.google.common.truth.Expect;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.stubbing.Answer;

import java.util.Map;
import java.util.Set;

public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase {

    private static final int U_APP_UID = 1001;
    private static final int T_APP_UID = 1002;
    private static final int T_APP_UID = 1001;
    private static final int U_APP_UID = 1002;
    private static final int V_APP_UID = 1003;

    private static final int FM_LOWER_LIMIT = 87_500;
    private static final int FM_UPPER_LIMIT = 108_000;
@@ -133,10 +137,18 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase {

    @Before
    public void setUp() {
        doReturn(true).when(() -> CompatChanges.isChangeEnabled(
                ConversionUtils.RADIO_U_VERSION_REQUIRED, U_APP_UID));
        doReturn(false).when(() -> CompatChanges.isChangeEnabled(
                ConversionUtils.RADIO_U_VERSION_REQUIRED, T_APP_UID));
        doAnswer((Answer<Boolean>) invocationOnMock -> {
                    long changeId = invocationOnMock.getArgument(0);
                    int uid = invocationOnMock.getArgument(1);
                    if (uid == V_APP_UID) {
                        return changeId == ConversionUtils.RADIO_V_VERSION_REQUIRED
                                || changeId == ConversionUtils.RADIO_U_VERSION_REQUIRED;
                    } else if (uid == U_APP_UID) {
                        return changeId == ConversionUtils.RADIO_U_VERSION_REQUIRED;
                    }
                    return false;
                }
        ).when(() -> CompatChanges.isChangeEnabled(anyLong(), anyInt()));
    }

    @Test
+1 −1
Original line number Diff line number Diff line
@@ -174,7 +174,7 @@ java_library_static {
        "android.hardware.boot-V1.2-java", // HIDL
        "android.hardware.boot-V1-java", // AIDL
        "android.hardware.broadcastradio-V2.0-java", // HIDL
        "android.hardware.broadcastradio-V1-java", // AIDL
        "android.hardware.broadcastradio-V2-java", // AIDL
        "android.hardware.health-V1.0-java", // HIDL
        "android.hardware.health-V2.0-java", // HIDL
        "android.hardware.health-V2.1-java", // HIDL
+132 −27
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.hardware.broadcastradio.AmFmRegionConfig;
import android.hardware.broadcastradio.Announcement;
import android.hardware.broadcastradio.ConfigFlag;
import android.hardware.broadcastradio.DabTableEntry;
import android.hardware.broadcastradio.IdentifierType;
import android.hardware.broadcastradio.Metadata;
@@ -32,6 +33,7 @@ import android.hardware.broadcastradio.ProgramInfo;
import android.hardware.broadcastradio.Properties;
import android.hardware.broadcastradio.Result;
import android.hardware.broadcastradio.VendorKeyValue;
import android.hardware.radio.Flags;
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
@@ -45,6 +47,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.utils.Slogf;

import java.util.ArrayList;
@@ -65,13 +68,22 @@ final class ConversionUtils {

    /**
     * With RADIO_U_VERSION_REQUIRED enabled, 44-bit DAB identifier
     * {@link IdentifierType#DAB_SID_EXT} from broadcast radio HAL can be passed as
     * {@link ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT} to {@link RadioTuner}.
     * {@code IdentifierType#DAB_SID_EXT} from broadcast radio HAL can be passed as
     * {@code ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT} to {@code RadioTuner}.
     */
    @ChangeId
    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
    public static final long RADIO_U_VERSION_REQUIRED = 261770108L;

    /**
     * With RADIO_V_VERSION_REQUIRED enabled, identifier types, config flags and metadata added
     * in V for HD radio can be passed to {@code RadioTuner} by
     * {@code android.hardware.radio.ITunerCallback}
     */
    @ChangeId
    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
    public static final long RADIO_V_VERSION_REQUIRED = 302589903L;

    private ConversionUtils() {
        throw new UnsupportedOperationException("ConversionUtils class is noninstantiable");
    }
@@ -81,6 +93,11 @@ final class ConversionUtils {
        return CompatChanges.isChangeEnabled(RADIO_U_VERSION_REQUIRED, uid);
    }

    @SuppressLint("AndroidFrameworkRequiresPermission")
    static boolean isAtLeastV(int uid) {
        return CompatChanges.isChangeEnabled(RADIO_V_VERSION_REQUIRED, uid);
    }

    static RuntimeException throwOnError(RuntimeException halException, String action) {
        if (!(halException instanceof ServiceSpecificException)) {
            return new ParcelableException(new RuntimeException(
@@ -181,6 +198,7 @@ final class ConversionUtils {
                // TODO(b/69958423): verify AM/FM with frequency range
                return ProgramSelector.PROGRAM_TYPE_FM;
            case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT:
            case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME:
                // TODO(b/69958423): verify AM/FM with frequency range
                return ProgramSelector.PROGRAM_TYPE_FM_HD;
            case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC:
@@ -195,6 +213,12 @@ final class ConversionUtils {
            case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID:
            case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL:
                return ProgramSelector.PROGRAM_TYPE_SXM;
            default:
                if (Flags.hdRadioImproved()) {
                    if (idType == ProgramSelector.IDENTIFIER_TYPE_HD_STATION_LOCATION) {
                        return ProgramSelector.PROGRAM_TYPE_FM_HD;
                    }
                }
        }
        if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START
                && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) {
@@ -322,9 +346,16 @@ final class ConversionUtils {

    static ProgramIdentifier identifierToHalProgramIdentifier(ProgramSelector.Identifier id) {
        ProgramIdentifier hwId = new ProgramIdentifier();
        hwId.type = id.getType();
        if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
            hwId.type = IdentifierType.DAB_SID_EXT;
        } else if (Flags.hdRadioImproved()) {
            if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_HD_STATION_LOCATION) {
                hwId.type = IdentifierType.HD_STATION_LOCATION;
            } else {
                hwId.type = id.getType();
            }
        } else {
            hwId.type = id.getType();
        }
        long value = id.getValue();
        if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT) {
@@ -344,6 +375,12 @@ final class ConversionUtils {
        int idType;
        if (id.type == IdentifierType.DAB_SID_EXT) {
            idType = ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT;
        } else if (id.type == IdentifierType.HD_STATION_LOCATION) {
            if (Flags.hdRadioImproved()) {
                idType = ProgramSelector.IDENTIFIER_TYPE_HD_STATION_LOCATION;
            } else {
                return null;
            }
        } else {
            idType = id.type;
        }
@@ -375,7 +412,12 @@ final class ConversionUtils {
        ProgramSelector.Identifier[] secondaryIds = sel.getSecondaryIds();
        ArrayList<ProgramIdentifier> secondaryIdList = new ArrayList<>(secondaryIds.length);
        for (int i = 0; i < secondaryIds.length; i++) {
            secondaryIdList.add(identifierToHalProgramIdentifier(secondaryIds[i]));
            ProgramIdentifier hwId = identifierToHalProgramIdentifier(secondaryIds[i]);
            if (hwId.type != IdentifierType.INVALID) {
                secondaryIdList.add(hwId);
            } else {
                Slogf.w(TAG, "Invalid secondary id: %s", secondaryIds[i]);
            }
        }
        hwSel.secondaryIds = secondaryIdList.toArray(ProgramIdentifier[]::new);
        if (!isValidHalProgramSelector(hwSel)) {
@@ -400,7 +442,12 @@ final class ConversionUtils {
        List<ProgramSelector.Identifier> secondaryIdList = new ArrayList<>();
        for (int i = 0; i < sel.secondaryIds.length; i++) {
            if (sel.secondaryIds[i] != null) {
                secondaryIdList.add(identifierFromHalProgramIdentifier(sel.secondaryIds[i]));
                ProgramSelector.Identifier id = identifierFromHalProgramIdentifier(
                        sel.secondaryIds[i]);
                if (id == null) {
                    Slogf.e(TAG, "invalid secondary id: %s", sel.secondaryIds[i]);
                }
                secondaryIdList.add(id);
            }
        }

@@ -411,11 +458,13 @@ final class ConversionUtils {
                /* vendorIds= */ null);
    }

    private static RadioMetadata radioMetadataFromHalMetadata(Metadata[] meta) {
    @VisibleForTesting
    static RadioMetadata radioMetadataFromHalMetadata(Metadata[] meta) {
        RadioMetadata.Builder builder = new RadioMetadata.Builder();

        for (int i = 0; i < meta.length; i++) {
            switch (meta[i].getTag()) {
            int tag = meta[i].getTag();
            switch (tag) {
                case Metadata.rdsPs:
                    builder.putString(RadioMetadata.METADATA_KEY_RDS_PS, meta[i].getRdsPs());
                    break;
@@ -472,10 +521,52 @@ final class ConversionUtils {
                            meta[i].getDabComponentNameShort());
                    break;
                default:
                    Slogf.w(TAG, "Ignored unknown metadata entry: %s", meta[i]);
                    if (Flags.hdRadioImproved()) {
                        switch (tag) {
                            case Metadata.genre:
                                builder.putString(RadioMetadata.METADATA_KEY_GENRE,
                                        meta[i].getGenre());
                                break;
                            case Metadata.commentShortDescription:
                                builder.putString(
                                        RadioMetadata.METADATA_KEY_COMMENT_SHORT_DESCRIPTION,
                                        meta[i].getCommentShortDescription());
                                break;
                            case Metadata.commentActualText:
                                builder.putString(RadioMetadata.METADATA_KEY_COMMENT_ACTUAL_TEXT,
                                        meta[i].getCommentActualText());
                                break;
                            case Metadata.commercial:
                                builder.putString(RadioMetadata.METADATA_KEY_COMMERCIAL,
                                        meta[i].getCommercial());
                                break;
                            case Metadata.ufids:
                                builder.putStringArray(RadioMetadata.METADATA_KEY_UFIDS,
                                        meta[i].getUfids());
                                break;
                            case Metadata.hdStationNameShort:
                                builder.putString(RadioMetadata.METADATA_KEY_HD_STATION_NAME_SHORT,
                                        meta[i].getHdStationNameShort());
                                break;
                            case Metadata.hdStationNameLong:
                                builder.putString(RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG,
                                        meta[i].getHdStationNameLong());
                                break;
                            case Metadata.hdSubChannelsAvailable:
                                builder.putInt(RadioMetadata.METADATA_KEY_HD_SUBCHANNELS_AVAILABLE,
                                        meta[i].getHdSubChannelsAvailable());
                                break;
                            default:
                                Slogf.w(TAG, "Ignored unknown metadata entry: %s with HD radio flag"
                                        + " enabled", meta[i]);
                                break;
                        }
                    } else {
                        Slogf.w(TAG, "Ignored unknown metadata entry: %s with HD radio flag "
                                + "disabled", meta[i]);
                    }
                    break;
            }

        }

        return builder.build();
@@ -547,7 +638,13 @@ final class ConversionUtils {
        }
        Iterator<ProgramSelector.Identifier> idIterator = filter.getIdentifiers().iterator();
        while (idIterator.hasNext()) {
            identifiersList.add(identifierToHalProgramIdentifier(idIterator.next()));
            ProgramSelector.Identifier id = idIterator.next();
            ProgramIdentifier hwId = identifierToHalProgramIdentifier(id);
            if (hwId.type != IdentifierType.INVALID) {
                identifiersList.add(hwId);
            } else {
                Slogf.w(TAG, "Invalid identifiers: %s", id);
            }
        }

        hwFilter.identifierTypes = identifierTypeList.toArray();
@@ -558,20 +655,26 @@ final class ConversionUtils {
        return hwFilter;
    }

    private static boolean isNewIdentifierInU(ProgramSelector.Identifier id) {
        return id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT;
    private static boolean identifierMeetsSdkVersionRequirement(ProgramSelector.Identifier id,
            int uid) {
        if (Flags.hdRadioImproved() && !isAtLeastV(uid)) {
            if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_HD_STATION_LOCATION) {
                return false;
            }
        }
        if (!isAtLeastU(uid)) {
            return id.getType() != ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT;
        }

    static boolean programSelectorMeetsSdkVersionRequirement(ProgramSelector sel, int uid) {
        if (isAtLeastU(uid)) {
        return true;
    }
        if (sel.getPrimaryId().getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {

    static boolean programSelectorMeetsSdkVersionRequirement(ProgramSelector sel, int uid) {
        if (!identifierMeetsSdkVersionRequirement(sel.getPrimaryId(), uid)) {
            return false;
        }
        ProgramSelector.Identifier[] secondaryIds = sel.getSecondaryIds();
        for (int i = 0; i < secondaryIds.length; i++) {
            if (isNewIdentifierInU(secondaryIds[i])) {
            if (!identifierMeetsSdkVersionRequirement(secondaryIds[i], uid)) {
                return false;
            }
        }
@@ -579,14 +682,11 @@ final class ConversionUtils {
    }

    static boolean programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info, int uid) {
        if (isAtLeastU(uid)) {
            return true;
        }
        if (!programSelectorMeetsSdkVersionRequirement(info.getSelector(), uid)) {
            return false;
        }
        if (isNewIdentifierInU(info.getLogicallyTunedTo())
                || isNewIdentifierInU(info.getPhysicallyTunedTo())) {
        if (!identifierMeetsSdkVersionRequirement(info.getLogicallyTunedTo(), uid)
                || !identifierMeetsSdkVersionRequirement(info.getPhysicallyTunedTo(), uid)) {
            return false;
        }
        if (info.getRelatedContent() == null) {
@@ -594,7 +694,7 @@ final class ConversionUtils {
        }
        Iterator<ProgramSelector.Identifier> relatedContentIt = info.getRelatedContent().iterator();
        while (relatedContentIt.hasNext()) {
            if (isNewIdentifierInU(relatedContentIt.next())) {
            if (!identifierMeetsSdkVersionRequirement(relatedContentIt.next(), uid)) {
                return false;
            }
        }
@@ -602,9 +702,6 @@ final class ConversionUtils {
    }

    static ProgramList.Chunk convertChunkToTargetSdkVersion(ProgramList.Chunk chunk, int uid) {
        if (isAtLeastU(uid)) {
            return chunk;
        }
        Set<RadioManager.ProgramInfo> modified = new ArraySet<>();
        Iterator<RadioManager.ProgramInfo> modifiedIterator = chunk.getModified().iterator();
        while (modifiedIterator.hasNext()) {
@@ -617,13 +714,21 @@ final class ConversionUtils {
        Iterator<UniqueProgramIdentifier> removedIterator = chunk.getRemoved().iterator();
        while (removedIterator.hasNext()) {
            UniqueProgramIdentifier id = removedIterator.next();
            if (!isNewIdentifierInU(id.getPrimaryId())) {
            if (identifierMeetsSdkVersionRequirement(id.getPrimaryId(), uid)) {
                removed.add(id);
            }
        }
        return new ProgramList.Chunk(chunk.isPurge(), chunk.isComplete(), modified, removed);
    }

    static boolean configFlagMeetsSdkVersionRequirement(int configFlag, int uid) {
        if (!Flags.hdRadioImproved() || !isAtLeastV(uid)) {
            return configFlag != ConfigFlag.FORCE_ANALOG_AM
                    && configFlag != ConfigFlag.FORCE_ANALOG_FM;
        }
        return true;
    }

    public static android.hardware.radio.Announcement announcementFromHalAnnouncement(
            Announcement hwAnnouncement) {
        return new android.hardware.radio.Announcement(
+5 −0
Original line number Diff line number Diff line
@@ -167,6 +167,11 @@ final class RadioModule {
            fireLater(() -> {
                synchronized (mLock) {
                    fanoutAidlCallbackLocked((cb, uid) -> {
                        if (!ConversionUtils.configFlagMeetsSdkVersionRequirement(flag, uid)) {
                            Slogf.e(TAG, "onConfigFlagUpdated: cannot send program info "
                                    + "requiring higher target SDK version");
                            return;
                        }
                        cb.onConfigFlagUpdated(flag, value);
                    });
                }