Merge "Add unique program identifier for broadcast radio" into main
diff --git a/core/java/android/hardware/radio/UniqueProgramIdentifier.aidl b/core/java/android/hardware/radio/UniqueProgramIdentifier.aidl
new file mode 100644
index 0000000..2ed2bcc
--- /dev/null
+++ b/core/java/android/hardware/radio/UniqueProgramIdentifier.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (C) 2023 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.hardware.radio;
+
+/** @hide */
+parcelable UniqueProgramIdentifier;
diff --git a/core/java/android/hardware/radio/UniqueProgramIdentifier.java b/core/java/android/hardware/radio/UniqueProgramIdentifier.java
new file mode 100644
index 0000000..ea8948e
--- /dev/null
+++ b/core/java/android/hardware/radio/UniqueProgramIdentifier.java
@@ -0,0 +1,163 @@
+/**
+ * Copyright (C) 2023 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.hardware.radio;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * Identifier that can uniquely identifies a program.
+ *
+ * This is a transport class used for internal communication between
+ * Broadcast Radio Service and Radio Manager. Do not use it directly.
+ *
+ * @hide
+ */
+public final class UniqueProgramIdentifier implements Parcelable {
+
+ @NonNull private final ProgramSelector.Identifier mPrimaryId;
+ @NonNull private final ProgramSelector.Identifier[] mCriticalSecondaryIds;
+
+ /**
+ * Check whether some secondary identifier is needed to uniquely specify a program for
+ * a given primary identifier type
+ *
+ * @param type primary identifier type {@link ProgramSelector.IdentifierType}
+ * @return whether some secondary identifier is needed to uniquely specify a program.
+ */
+ public static boolean requireCriticalSecondaryIds(@ProgramSelector.IdentifierType int type) {
+ return type == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT || type
+ == ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT;
+ }
+
+ public UniqueProgramIdentifier(ProgramSelector selector) {
+ Objects.requireNonNull(selector, "Program selector can not be null");
+ mPrimaryId = selector.getPrimaryId();
+ switch (mPrimaryId.getType()) {
+ case ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT:
+ case ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT:
+ ProgramSelector.Identifier ensembleId = null;
+ ProgramSelector.Identifier frequencyId = null;
+ ProgramSelector.Identifier[] secondaryIds = selector.getSecondaryIds();
+ for (int i = 0; i < secondaryIds.length; i++) {
+ if (ensembleId == null && secondaryIds[i].getType()
+ == ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE) {
+ ensembleId = selector.getSecondaryIds()[i];
+ } else if (frequencyId == null && secondaryIds[i].getType()
+ == ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY) {
+ frequencyId = secondaryIds[i];
+ }
+ if (ensembleId != null && frequencyId != null) {
+ break;
+ }
+ }
+ if (ensembleId == null) {
+ if (frequencyId == null) {
+ mCriticalSecondaryIds = new ProgramSelector.Identifier[]{};
+ } else {
+ mCriticalSecondaryIds = new ProgramSelector.Identifier[]{frequencyId};
+ }
+ } else if (frequencyId == null) {
+ mCriticalSecondaryIds = new ProgramSelector.Identifier[]{ensembleId};
+ } else {
+ mCriticalSecondaryIds = new ProgramSelector.Identifier[]{ensembleId,
+ frequencyId};
+ }
+ break;
+ default:
+ mCriticalSecondaryIds = new ProgramSelector.Identifier[]{};
+ }
+
+ }
+
+ public UniqueProgramIdentifier(ProgramSelector.Identifier primaryId) {
+ mPrimaryId = primaryId;
+ mCriticalSecondaryIds = new ProgramSelector.Identifier[]{};
+ }
+
+ @NonNull
+ public ProgramSelector.Identifier getPrimaryId() {
+ return mPrimaryId;
+ }
+
+ @NonNull
+ public List<ProgramSelector.Identifier> getCriticalSecondaryIds() {
+ return List.of(mCriticalSecondaryIds);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return new StringBuilder("UniqueProgramIdentifier(primary=").append(mPrimaryId)
+ .append(", criticalSecondary=")
+ .append(Arrays.toString(mCriticalSecondaryIds)).append(")")
+ .toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPrimaryId, Arrays.hashCode(mCriticalSecondaryIds));
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof UniqueProgramIdentifier)) return false;
+ UniqueProgramIdentifier other = (UniqueProgramIdentifier) obj;
+ return other.mPrimaryId.equals(mPrimaryId)
+ && Arrays.equals(other.mCriticalSecondaryIds, mCriticalSecondaryIds);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private UniqueProgramIdentifier(Parcel in) {
+ mPrimaryId = in.readTypedObject(ProgramSelector.Identifier.CREATOR);
+ mCriticalSecondaryIds = in.createTypedArray(ProgramSelector.Identifier.CREATOR);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedObject(mPrimaryId, 0);
+ dest.writeTypedArray(mCriticalSecondaryIds, 0);
+ if (Stream.of(mCriticalSecondaryIds).anyMatch(Objects::isNull)) {
+ throw new IllegalArgumentException(
+ "criticalSecondaryIds list must not contain nulls");
+ }
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<UniqueProgramIdentifier> CREATOR =
+ new Parcelable.Creator<UniqueProgramIdentifier>() {
+ public UniqueProgramIdentifier createFromParcel(Parcel in) {
+ return new UniqueProgramIdentifier(in);
+ }
+
+ public UniqueProgramIdentifier[] newArray(int size) {
+ return new UniqueProgramIdentifier[size];
+ }
+ };
+}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
index ae43a1c..b1cf9c2 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
@@ -437,8 +437,8 @@
@Test
public void writeToParcel_forProgramSelector() {
- ProgramSelector selectorExpected =
- getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
+ ProgramSelector selectorExpected = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
Parcel parcel = Parcel.obtain();
selectorExpected.writeToParcel(parcel, /* flags= */ 0);
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java
new file mode 100644
index 0000000..b36367b
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java
@@ -0,0 +1,187 @@
+/**
+ * Copyright (C) 2023 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.hardware.radio;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+public final class UniqueProgramIdentifierTest {
+ private static final ProgramSelector.Identifier FM_IDENTIFIER = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 88_500);
+
+ private static final ProgramSelector.Identifier DAB_DMB_SID_EXT_IDENTIFIER_1 =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+ /* value= */ 0xA000000111L);
+ private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+ /* value= */ 0x1001);
+ private static final ProgramSelector.Identifier DAB_FREQUENCY_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+ /* value= */ 220352);
+ private static final ProgramSelector.Identifier DAB_SCID_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SCID,
+ /* value= */ 0x101);
+
+ @Rule
+ public final Expect expect = Expect.create();
+
+ @Test
+ public void getPrimaryId_forUniqueProgramIdentifier() {
+ ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier = new UniqueProgramIdentifier(dabSelector);
+
+ expect.withMessage("Primary id of DAB unique identifier")
+ .that(dabIdentifier.getPrimaryId()).isEqualTo(DAB_DMB_SID_EXT_IDENTIFIER_1);
+ }
+
+ @Test
+ public void getCriticalSecondaryIds_forDabUniqueProgramIdentifier() {
+ ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER, DAB_SCID_IDENTIFIER},
+ /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier = new UniqueProgramIdentifier(dabSelector);
+
+ expect.withMessage("Critical secondary ids of DAB unique identifier")
+ .that(dabIdentifier.getCriticalSecondaryIds()).containsExactly(
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER);
+ }
+
+ @Test
+ public void getCriticalSecondaryIds_forFmUniqueProgramIdentifier() {
+ UniqueProgramIdentifier fmUniqueIdentifier = new UniqueProgramIdentifier(
+ new ProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER,
+ new ProgramSelector.Identifier[]{new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_RDS_PI, /* value= */ 0x1003)},
+ /* vendorIds= */ null));
+
+ expect.withMessage("Empty critical secondary id list of FM unique identifier")
+ .that(fmUniqueIdentifier.getCriticalSecondaryIds()).isEmpty();
+ }
+
+ @Test
+ public void toString_forUniqueProgramIdentifier() {
+ ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier = new UniqueProgramIdentifier(dabSelector);
+
+ String identifierString = dabIdentifier.toString();
+
+ expect.withMessage("Primary id in DAB unique identifier")
+ .that(identifierString).contains(DAB_DMB_SID_EXT_IDENTIFIER_1.toString());
+ expect.withMessage("Ensemble id in DAB unique identifier")
+ .that(identifierString).contains(DAB_ENSEMBLE_IDENTIFIER.toString());
+ expect.withMessage("Frequency id in DAB unique identifier")
+ .that(identifierString).contains(DAB_FREQUENCY_IDENTIFIER.toString());
+ }
+
+ @Test
+ public void hashCode_withTheSameUniqueProgramIdentifier_equals() {
+ ProgramSelector dabSelector1 = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+ ProgramSelector dabSelector2 = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_FREQUENCY_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER}, /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier1 = new UniqueProgramIdentifier(dabSelector1);
+ UniqueProgramIdentifier dabIdentifier2 = new UniqueProgramIdentifier(dabSelector2);
+
+ expect.withMessage("Hash code of the same DAB unique identifiers")
+ .that(dabIdentifier1.hashCode()).isEqualTo(dabIdentifier2.hashCode());
+ }
+
+ @Test
+ public void equals_withIdsForUniqueProgramIdentifier_returnsTrue() {
+ ProgramSelector dabSelector1 = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+ ProgramSelector dabSelector2 = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_FREQUENCY_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER}, /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier1 = new UniqueProgramIdentifier(dabSelector1);
+ UniqueProgramIdentifier dabIdentifier2 = new UniqueProgramIdentifier(dabSelector2);
+
+ expect.withMessage("The same DAB unique identifiers")
+ .that(dabIdentifier1).isEqualTo(dabIdentifier2);
+ }
+
+ @Test
+ public void equals_withDifferentPrimaryIdsForUniqueProgramIdentifier_returnsFalse() {
+ ProgramSelector dabSelector1 = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier1 = new UniqueProgramIdentifier(dabSelector1);
+ UniqueProgramIdentifier fmUniqueIdentifier = new UniqueProgramIdentifier(FM_IDENTIFIER);
+
+ expect.withMessage("Unique identifier with different primary ids")
+ .that(dabIdentifier1).isNotEqualTo(fmUniqueIdentifier);
+ }
+
+ @Test
+ public void equals_withDifferentSecondaryIdsForUniqueProgramIdentifier_returnsFalse() {
+ ProgramSelector dabSelector1 = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+ ProgramSelector.Identifier dabFreqIdentifier2 = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, /* value= */ 222064);
+ ProgramSelector dabSelector2 = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, dabFreqIdentifier2}, /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier1 = new UniqueProgramIdentifier(dabSelector1);
+ UniqueProgramIdentifier dabIdentifier2 = new UniqueProgramIdentifier(dabSelector2);
+
+ expect.withMessage("DAB unique identifier with different secondary ids")
+ .that(dabIdentifier1).isNotEqualTo(dabIdentifier2);
+ }
+
+ @Test
+ public void describeContents_forUniqueProgramIdentifier() {
+ UniqueProgramIdentifier fmUniqueIdentifier = new UniqueProgramIdentifier(FM_IDENTIFIER);
+
+ expect.withMessage("FM unique identifier contents")
+ .that(fmUniqueIdentifier.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void newArray_forUniqueProgramIdentifier() {
+ int createArraySize = 3;
+ UniqueProgramIdentifier[] identifiers = UniqueProgramIdentifier.CREATOR.newArray(
+ createArraySize);
+
+ expect.withMessage("Unique identifiers").that(identifiers).hasLength(createArraySize);
+ }
+
+ @Test
+ public void writeToParcel_forUniqueProgramIdentifier() {
+ ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier = new UniqueProgramIdentifier(dabSelector);
+ Parcel parcel = Parcel.obtain();
+
+ dabIdentifier.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ UniqueProgramIdentifier identifierFromParcel = UniqueProgramIdentifier.CREATOR
+ .createFromParcel(parcel);
+ expect.withMessage("Unique identifier created from parcel")
+ .that(identifierFromParcel).isEqualTo(dabIdentifier);
+ }
+
+ private ProgramSelector getDabSelector(@Nullable ProgramSelector.Identifier[] secondaryIds,
+ @Nullable long[] vendorIds) {
+ return new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB, DAB_DMB_SID_EXT_IDENTIFIER_1,
+ secondaryIds, vendorIds);
+ }
+}