From f151a7b3377e956fedd4f3e63afc880c35310219 Mon Sep 17 00:00:00 2001 From: Tomasz Wasilczyk Date: Thu, 11 Jan 2018 16:03:46 -0800 Subject: [PATCH] Implement front-end APIs for announcements. Bug: 68045105 Test: instrumentation (none added) Change-Id: I602e8bb0c40516a732d606f745c8f7721583155f --- Android.bp | 2 + api/system-current.txt | 24 ++++ .../android/hardware/radio/Announcement.aidl | 20 +++ .../android/hardware/radio/Announcement.java | 133 ++++++++++++++++++ .../hardware/radio/IAnnouncementListener.aidl | 24 ++++ .../android/hardware/radio/ICloseHandle.aidl | 22 +++ .../android/hardware/radio/IRadioService.aidl | 5 + .../android/hardware/radio/RadioManager.java | 64 +++++++++ core/java/android/hardware/radio/Utils.java | 11 ++ .../broadcastradio/BroadcastRadioService.java | 28 +++- .../hal2/AnnouncementAggregator.java | 128 +++++++++++++++++ .../hal2/BroadcastRadioService.java | 24 ++++ .../server/broadcastradio/hal2/Convert.java | 10 ++ .../broadcastradio/hal2/RadioModule.java | 40 ++++++ 14 files changed, 534 insertions(+), 1 deletion(-) create mode 100644 core/java/android/hardware/radio/Announcement.aidl create mode 100644 core/java/android/hardware/radio/Announcement.java create mode 100644 core/java/android/hardware/radio/IAnnouncementListener.aidl create mode 100644 core/java/android/hardware/radio/ICloseHandle.aidl create mode 100644 services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java diff --git a/Android.bp b/Android.bp index d1332bb4fe87..c571c802eeba 100644 --- a/Android.bp +++ b/Android.bp @@ -177,6 +177,8 @@ java_library { "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 a1ec2c44cd49..9e5c30b94040 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1739,6 +1739,27 @@ package android.hardware.location { 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 getVendorInfo(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator 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); + } + 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 @@ package android.hardware.radio { } public class RadioManager { + method public void addAnnouncementListener(java.util.Set, android.hardware.radio.Announcement.OnListUpdatedListener); + method public void addAnnouncementListener(java.util.concurrent.Executor, java.util.Set, android.hardware.radio.Announcement.OnListUpdatedListener); method public int listModules(java.util.List); 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 000000000000..eeb5951ee782 --- /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 000000000000..166fe6043f3d --- /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 activeAnnouncements); + } + + @NonNull private final ProgramSelector mSelector; + @Type private final int mType; + @NonNull private final Map mVendorInfo; + + /** @hide */ + public Announcement(@NonNull ProgramSelector selector, @Type int type, + @NonNull Map 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 CREATOR = + new Parcelable.Creator() { + 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 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 000000000000..b4d974a37ede --- /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 activeAnnouncements); +} diff --git a/core/java/android/hardware/radio/ICloseHandle.aidl b/core/java/android/hardware/radio/ICloseHandle.aidl new file mode 100644 index 000000000000..576c03b0db84 --- /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 c43fd2615e44..9349cf7d0c32 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 @@ interface IRadioService { 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 56668ac00527..c2197593b413 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.List; 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 @@ public class RadioManager { config != null ? config.getType() : BAND_INVALID); } + private final Map 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 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 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 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 09bf8feb30c2..a80bbf208fe3 100644 --- a/core/java/android/hardware/radio/Utils.java +++ b/core/java/android/hardware/radio/Utils.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; 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.Objects; import java.util.Set; final class Utils { + private static final String TAG = "BroadcastRadio.utils"; + static void writeStringMap(@NonNull Parcel dest, @Nullable Map map) { if (map == null) { dest.writeInt(0); @@ -89,4 +92,12 @@ final class Utils { } }); } + + 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 10e6cad70bcd..4289a25e5b3a 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.annotation.NonNull; 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.ParcelableException; 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 @@ public class BroadcastRadioService extends SystemService { @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 @@ public class BroadcastRadioService extends SystemService { } } } + + @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 000000000000..0bbaf25aaa12 --- /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 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 currentList = new ArrayList<>(); + + public void onListUpdated(List 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 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 fc9a5d61704d..406231ae289f 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 @@ package com.android.server.broadcastradio.hal2; 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 @@ public class BroadcastRadioService { 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 @@ public class BroadcastRadioService { 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 60a927c56959..f1e36bb52d50 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.NonNull; 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 @@ class Convert { 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 c8e15c1a2f4f..4dff9e06000a 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.annotation.Nullable; 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.os.RemoteException; 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 @@ class RadioModule { return new TunerSession(hwSession.value, cb); } + + public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes, + @NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException { + ArrayList enabledList = new ArrayList<>(); + for (int type : enabledTypes) { + enabledList.add((byte)type); + } + + MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR); + Mutable hwCloseHandle = new Mutable<>(); + IAnnouncementListener hwListener = new IAnnouncementListener.Stub() { + public void onListUpdated(ArrayList 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); + } + } + }; + } }