Implement front-end APIs for announcements.
Bug: 68045105
Test: instrumentation (none added)
Change-Id: I602e8bb0c40516a732d606f745c8f7721583155f
diff --git a/Android.bp b/Android.bp
index d1332bb..c571c802 100644
--- a/Android.bp
+++ b/Android.bp
@@ -177,6 +177,8 @@
"core/java/android/hardware/location/IContextHubClientCallback.aidl",
"core/java/android/hardware/location/IContextHubService.aidl",
"core/java/android/hardware/location/IContextHubTransactionCallback.aidl",
+ "core/java/android/hardware/radio/IAnnouncementListener.aidl",
+ "core/java/android/hardware/radio/ICloseHandle.aidl",
"core/java/android/hardware/radio/IRadioService.aidl",
"core/java/android/hardware/radio/ITuner.aidl",
"core/java/android/hardware/radio/ITunerCallback.aidl",
diff --git a/api/system-current.txt b/api/system-current.txt
index a1ec2c4..9e5c30b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1739,6 +1739,27 @@
package android.hardware.radio {
+ public final class Announcement implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.hardware.radio.ProgramSelector getSelector();
+ method public int getType();
+ method public java.util.Map<java.lang.String, java.lang.String> getVendorInfo();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.radio.Announcement> CREATOR;
+ field public static final int TYPE_EMERGENCY = 1; // 0x1
+ field public static final int TYPE_EVENT = 6; // 0x6
+ field public static final int TYPE_MISC = 8; // 0x8
+ field public static final int TYPE_NEWS = 5; // 0x5
+ field public static final int TYPE_SPORT = 7; // 0x7
+ field public static final int TYPE_TRAFFIC = 3; // 0x3
+ field public static final int TYPE_WARNING = 2; // 0x2
+ field public static final int TYPE_WEATHER = 4; // 0x4
+ }
+
+ public static abstract interface Announcement.OnListUpdatedListener {
+ method public abstract void onListUpdated(java.util.Collection<android.hardware.radio.Announcement>);
+ }
+
public final class ProgramList implements java.lang.AutoCloseable {
method public void addOnCompleteListener(java.util.concurrent.Executor, android.hardware.radio.ProgramList.OnCompleteListener);
method public void addOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener);
@@ -1833,8 +1854,11 @@
}
public class RadioManager {
+ method public void addAnnouncementListener(java.util.Set<java.lang.Integer>, android.hardware.radio.Announcement.OnListUpdatedListener);
+ method public void addAnnouncementListener(java.util.concurrent.Executor, java.util.Set<java.lang.Integer>, android.hardware.radio.Announcement.OnListUpdatedListener);
method public int listModules(java.util.List<android.hardware.radio.RadioManager.ModuleProperties>);
method public android.hardware.radio.RadioTuner openTuner(int, android.hardware.radio.RadioManager.BandConfig, boolean, android.hardware.radio.RadioTuner.Callback, android.os.Handler);
+ method public void removeAnnouncementListener(android.hardware.radio.Announcement.OnListUpdatedListener);
field public static final int BAND_AM = 0; // 0x0
field public static final int BAND_AM_HD = 3; // 0x3
field public static final int BAND_FM = 1; // 0x1
diff --git a/core/java/android/hardware/radio/Announcement.aidl b/core/java/android/hardware/radio/Announcement.aidl
new file mode 100644
index 0000000..eeb5951
--- /dev/null
+++ b/core/java/android/hardware/radio/Announcement.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (C) 2018 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 Announcement;
diff --git a/core/java/android/hardware/radio/Announcement.java b/core/java/android/hardware/radio/Announcement.java
new file mode 100644
index 0000000..166fe60
--- /dev/null
+++ b/core/java/android/hardware/radio/Announcement.java
@@ -0,0 +1,133 @@
+/**
+ * Copyright (C) 2018 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.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class Announcement implements Parcelable {
+
+ /** DAB alarm, RDS emergency program type (PTY 31). */
+ public static final int TYPE_EMERGENCY = 1;
+ /** DAB warning. */
+ public static final int TYPE_WARNING = 2;
+ /** DAB road traffic, RDS TA, HD Radio transportation. */
+ public static final int TYPE_TRAFFIC = 3;
+ /** Weather. */
+ public static final int TYPE_WEATHER = 4;
+ /** News. */
+ public static final int TYPE_NEWS = 5;
+ /** DAB event, special event. */
+ public static final int TYPE_EVENT = 6;
+ /** DAB sport report, RDS sports. */
+ public static final int TYPE_SPORT = 7;
+ /** All others. */
+ public static final int TYPE_MISC = 8;
+ /** @hide */
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_EMERGENCY,
+ TYPE_WARNING,
+ TYPE_TRAFFIC,
+ TYPE_WEATHER,
+ TYPE_NEWS,
+ TYPE_EVENT,
+ TYPE_SPORT,
+ TYPE_MISC,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ /**
+ * Listener of announcement list events.
+ */
+ public interface OnListUpdatedListener {
+ /**
+ * An event called whenever a list of active announcements change.
+ *
+ * The entire list is sent each time a new announcement appears or any ends broadcasting.
+ *
+ * @param activeAnnouncements a full list of active announcements
+ */
+ void onListUpdated(Collection<Announcement> activeAnnouncements);
+ }
+
+ @NonNull private final ProgramSelector mSelector;
+ @Type private final int mType;
+ @NonNull private final Map<String, String> mVendorInfo;
+
+ /** @hide */
+ public Announcement(@NonNull ProgramSelector selector, @Type int type,
+ @NonNull Map<String, String> vendorInfo) {
+ mSelector = Objects.requireNonNull(selector);
+ mType = Objects.requireNonNull(type);
+ mVendorInfo = Objects.requireNonNull(vendorInfo);
+ }
+
+ private Announcement(@NonNull Parcel in) {
+ mSelector = in.readTypedObject(ProgramSelector.CREATOR);
+ mType = in.readInt();
+ mVendorInfo = Utils.readStringMap(in);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(mSelector, 0);
+ dest.writeInt(mType);
+ Utils.writeStringMap(dest, mVendorInfo);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<Announcement> CREATOR =
+ new Parcelable.Creator<Announcement>() {
+ public Announcement createFromParcel(Parcel in) {
+ return new Announcement(in);
+ }
+
+ public Announcement[] newArray(int size) {
+ return new Announcement[size];
+ }
+ };
+
+ public @NonNull ProgramSelector getSelector() {
+ return mSelector;
+ }
+
+ public @Type int getType() {
+ return mType;
+ }
+
+ public @NonNull Map<String, String> getVendorInfo() {
+ return mVendorInfo;
+ }
+}
diff --git a/core/java/android/hardware/radio/IAnnouncementListener.aidl b/core/java/android/hardware/radio/IAnnouncementListener.aidl
new file mode 100644
index 0000000..b4d974a
--- /dev/null
+++ b/core/java/android/hardware/radio/IAnnouncementListener.aidl
@@ -0,0 +1,24 @@
+/**
+ * Copyright (C) 2018 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.hardware.radio.Announcement;
+
+/** {@hide} */
+oneway interface IAnnouncementListener {
+ void onListUpdated(in List<Announcement> activeAnnouncements);
+}
diff --git a/core/java/android/hardware/radio/ICloseHandle.aidl b/core/java/android/hardware/radio/ICloseHandle.aidl
new file mode 100644
index 0000000..576c03b
--- /dev/null
+++ b/core/java/android/hardware/radio/ICloseHandle.aidl
@@ -0,0 +1,22 @@
+/**
+ * Copyright (C) 2018 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} */
+interface ICloseHandle {
+ void close();
+}
diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl
index c43fd26..9349cf7 100644
--- a/core/java/android/hardware/radio/IRadioService.aidl
+++ b/core/java/android/hardware/radio/IRadioService.aidl
@@ -16,6 +16,8 @@
package android.hardware.radio;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
@@ -30,4 +32,7 @@
ITuner openTuner(int moduleId, in RadioManager.BandConfig bandConfig, boolean withAudio,
in ITunerCallback callback);
+
+ ICloseHandle addAnnouncementListener(in int[] enabledTypes,
+ in IAnnouncementListener listener);
}
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 56668ac..c219759 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -17,6 +17,7 @@
package android.hardware.radio;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -40,6 +41,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.stream.Collectors;
/**
@@ -1713,6 +1715,68 @@
config != null ? config.getType() : BAND_INVALID);
}
+ private final Map<Announcement.OnListUpdatedListener, ICloseHandle> mAnnouncementListeners =
+ new HashMap<>();
+
+ /**
+ * Adds new announcement listener.
+ *
+ * @param enabledAnnouncementTypes a set of announcement types to listen to
+ * @param listener announcement listener
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ public void addAnnouncementListener(@NonNull Set<Integer> enabledAnnouncementTypes,
+ @NonNull Announcement.OnListUpdatedListener listener) {
+ addAnnouncementListener(cmd -> cmd.run(), enabledAnnouncementTypes, listener);
+ }
+
+ /**
+ * Adds new announcement listener with executor.
+ *
+ * @param executor the executor
+ * @param enabledAnnouncementTypes a set of announcement types to listen to
+ * @param listener announcement listener
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ public void addAnnouncementListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Set<Integer> enabledAnnouncementTypes,
+ @NonNull Announcement.OnListUpdatedListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ int[] types = enabledAnnouncementTypes.stream().mapToInt(Integer::intValue).toArray();
+ IAnnouncementListener listenerIface = new IAnnouncementListener.Stub() {
+ public void onListUpdated(List<Announcement> activeAnnouncements) {
+ executor.execute(() -> listener.onListUpdated(activeAnnouncements));
+ }
+ };
+ synchronized (mAnnouncementListeners) {
+ ICloseHandle closeHandle = null;
+ try {
+ closeHandle = mService.addAnnouncementListener(types, listenerIface);
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ Objects.requireNonNull(closeHandle);
+ ICloseHandle oldCloseHandle = mAnnouncementListeners.put(listener, closeHandle);
+ if (oldCloseHandle != null) Utils.close(oldCloseHandle);
+ }
+ }
+
+ /**
+ * Removes previously registered announcement listener.
+ *
+ * @param listener announcement listener, previously registered with
+ * {@link addAnnouncementListener}
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+ public void removeAnnouncementListener(@NonNull Announcement.OnListUpdatedListener listener) {
+ Objects.requireNonNull(listener);
+ synchronized (mAnnouncementListeners) {
+ ICloseHandle closeHandle = mAnnouncementListeners.remove(listener);
+ if (closeHandle != null) Utils.close(closeHandle);
+ }
+ }
+
@NonNull private final Context mContext;
@NonNull private final IRadioService mService;
diff --git a/core/java/android/hardware/radio/Utils.java b/core/java/android/hardware/radio/Utils.java
index 09bf8fe..a80bbf2 100644
--- a/core/java/android/hardware/radio/Utils.java
+++ b/core/java/android/hardware/radio/Utils.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
import java.util.HashMap;
import java.util.HashSet;
@@ -28,6 +29,8 @@
import java.util.Set;
final class Utils {
+ private static final String TAG = "BroadcastRadio.utils";
+
static void writeStringMap(@NonNull Parcel dest, @Nullable Map<String, String> map) {
if (map == null) {
dest.writeInt(0);
@@ -89,4 +92,12 @@
}
});
}
+
+ static void close(ICloseHandle handle) {
+ try {
+ handle.close();
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index 10e6cad..4289a25 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -20,6 +20,8 @@
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
import android.hardware.radio.IRadioService;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
@@ -28,14 +30,18 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.internal.util.Preconditions;
import com.android.server.SystemService;
+import com.android.server.broadcastradio.hal2.AnnouncementAggregator;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.OptionalInt;
public class BroadcastRadioService extends SystemService {
private static final String TAG = "BcRadioSrv";
+ private static final boolean DEBUG = false;
private final ServiceImpl mServiceImpl = new ServiceImpl();
@@ -88,7 +94,7 @@
@Override
public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
boolean withAudio, ITunerCallback callback) throws RemoteException {
- Slog.i(TAG, "openTuner(" + moduleId + ", _, " + withAudio + ", _)");
+ if (DEBUG) Slog.i(TAG, "Opening module " + moduleId);
enforcePolicyAccess();
if (callback == null) {
throw new IllegalArgumentException("Callback must not be empty");
@@ -101,5 +107,25 @@
}
}
}
+
+ @Override
+ public ICloseHandle addAnnouncementListener(int[] enabledTypes,
+ IAnnouncementListener listener) {
+ if (DEBUG) {
+ Slog.i(TAG, "Adding announcement listener for " + Arrays.toString(enabledTypes));
+ }
+ Objects.requireNonNull(enabledTypes);
+ Objects.requireNonNull(listener);
+ enforcePolicyAccess();
+
+ synchronized (mLock) {
+ if (!mHal2.hasAnyModules()) {
+ Slog.i(TAG, "There are no HAL 2.x modules registered");
+ return new AnnouncementAggregator(listener);
+ }
+
+ return mHal2.addAnnouncementListener(enabledTypes, listener);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
new file mode 100644
index 0000000..0bbaf25
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
@@ -0,0 +1,128 @@
+/**
+ * Copyright (C) 2018 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.broadcastradio.hal2;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+public class AnnouncementAggregator extends ICloseHandle.Stub {
+ private static final String TAG = "BcRadio2Srv.AnnAggr";
+
+ private final Object mLock = new Object();
+ @NonNull private final IAnnouncementListener mListener;
+ private final IBinder.DeathRecipient mDeathRecipient = new DeathRecipient();
+
+ @GuardedBy("mLock")
+ private final Collection<ModuleWatcher> mModuleWatchers = new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private boolean mIsClosed = false;
+
+ public AnnouncementAggregator(@NonNull IAnnouncementListener listener) {
+ mListener = Objects.requireNonNull(listener);
+ try {
+ listener.asBinder().linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
+
+ private class ModuleWatcher extends IAnnouncementListener.Stub {
+ private @Nullable ICloseHandle mCloseHandle;
+ public @NonNull List<Announcement> currentList = new ArrayList<>();
+
+ public void onListUpdated(List<Announcement> active) {
+ currentList = Objects.requireNonNull(active);
+ AnnouncementAggregator.this.onListUpdated();
+ }
+
+ public void setCloseHandle(@NonNull ICloseHandle closeHandle) {
+ mCloseHandle = Objects.requireNonNull(closeHandle);
+ }
+
+ public void close() throws RemoteException {
+ if (mCloseHandle != null) mCloseHandle.close();
+ }
+ }
+
+ private class DeathRecipient implements IBinder.DeathRecipient {
+ public void binderDied() {
+ try {
+ close();
+ } catch (RemoteException ex) {}
+ }
+ }
+
+ private void onListUpdated() {
+ synchronized (mLock) {
+ if (mIsClosed) {
+ Slog.e(TAG, "Announcement aggregator is closed, it shouldn't receive callbacks");
+ return;
+ }
+ List<Announcement> combined = new ArrayList<>();
+ for (ModuleWatcher watcher : mModuleWatchers) {
+ combined.addAll(watcher.currentList);
+ }
+ TunerCallback.dispatch(() -> mListener.onListUpdated(combined));
+ }
+ }
+
+ public void watchModule(@NonNull RadioModule module, @NonNull int[] enabledTypes) {
+ synchronized (mLock) {
+ if (mIsClosed) throw new IllegalStateException();
+
+ ModuleWatcher watcher = new ModuleWatcher();
+ ICloseHandle closeHandle;
+ try {
+ closeHandle = module.addAnnouncementListener(enabledTypes, watcher);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to add announcement listener", ex);
+ return;
+ }
+ watcher.setCloseHandle(closeHandle);
+ mModuleWatchers.add(watcher);
+ }
+ }
+
+ @Override
+ public void close() throws RemoteException {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ mIsClosed = true;
+
+ mListener.asBinder().unlinkToDeath(mDeathRecipient, 0);
+
+ for (ModuleWatcher watcher : mModuleWatchers) {
+ watcher.close();
+ }
+ mModuleWatchers.clear();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index fc9a5d6..406231a 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
@@ -81,6 +83,10 @@
return mModules.containsKey(id);
}
+ public boolean hasAnyModules() {
+ return !mModules.isEmpty();
+ }
+
public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
Objects.requireNonNull(callback);
@@ -98,4 +104,22 @@
session.setConfiguration(legacyConfig);
return session;
}
+
+ public ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
+ @NonNull IAnnouncementListener listener) {
+ AnnouncementAggregator aggregator = new AnnouncementAggregator(listener);
+ boolean anySupported = false;
+ for (RadioModule module : mModules.values()) {
+ try {
+ aggregator.watchModule(module, enabledTypes);
+ anySupported = true;
+ } catch (UnsupportedOperationException ex) {
+ Slog.v(TAG, "Announcements not supported for this module", ex);
+ }
+ }
+ if (!anySupported) {
+ Slog.i(TAG, "There are no HAL modules that support announcements");
+ }
+ return aggregator;
+ }
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index 60a927c..f1e36bb 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.hardware.broadcastradio.V2_0.AmFmBandRange;
import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
+import android.hardware.broadcastradio.V2_0.Announcement;
import android.hardware.broadcastradio.V2_0.ProgramFilter;
import android.hardware.broadcastradio.V2_0.ProgramIdentifier;
import android.hardware.broadcastradio.V2_0.ProgramInfo;
@@ -266,4 +267,13 @@
return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
}
+
+ public static @NonNull android.hardware.radio.Announcement announcementFromHal(
+ @NonNull Announcement hwAnnouncement) {
+ return new android.hardware.radio.Announcement(
+ programSelectorFromHal(hwAnnouncement.selector),
+ hwAnnouncement.type,
+ vendorInfoFromHal(hwAnnouncement.vendorInfo)
+ );
+ }
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index c8e15c1..4dff9e0 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -21,7 +21,10 @@
import android.hardware.radio.ITuner;
import android.hardware.radio.RadioManager;
import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
+import android.hardware.broadcastradio.V2_0.Announcement;
+import android.hardware.broadcastradio.V2_0.IAnnouncementListener;
import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
+import android.hardware.broadcastradio.V2_0.ICloseHandle;
import android.hardware.broadcastradio.V2_0.ITunerSession;
import android.hardware.broadcastradio.V2_0.Result;
import android.os.ParcelableException;
@@ -29,7 +32,11 @@
import android.util.MutableInt;
import android.util.Slog;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.Objects;
+import java.util.stream.Collectors;
class RadioModule {
private static final String TAG = "BcRadio2Srv.module";
@@ -79,4 +86,37 @@
return new TunerSession(hwSession.value, cb);
}
+
+ public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
+ @NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException {
+ ArrayList<Byte> enabledList = new ArrayList<>();
+ for (int type : enabledTypes) {
+ enabledList.add((byte)type);
+ }
+
+ MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
+ Mutable<ICloseHandle> hwCloseHandle = new Mutable<>();
+ IAnnouncementListener hwListener = new IAnnouncementListener.Stub() {
+ public void onListUpdated(ArrayList<Announcement> hwAnnouncements)
+ throws RemoteException {
+ listener.onListUpdated(hwAnnouncements.stream().
+ map(a -> Convert.announcementFromHal(a)).collect(Collectors.toList()));
+ }
+ };
+ mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHandle) -> {
+ halResult.value = result;
+ hwCloseHandle.value = closeHandle;
+ });
+ Convert.throwOnError("addAnnouncementListener", halResult.value);
+
+ return new android.hardware.radio.ICloseHandle.Stub() {
+ public void close() {
+ try {
+ hwCloseHandle.value.close();
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed closing announcement listener", ex);
+ }
+ }
+ };
+ }
}