1 /**
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.broadcastradio.hal2;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
22 import android.hardware.radio.IAnnouncementListener;
23 import android.hardware.radio.ICloseHandle;
24 import android.hardware.radio.ITuner;
25 import android.hardware.radio.ITunerCallback;
26 import android.hardware.radio.RadioManager;
27 import android.hardware.radio.RadioTuner;
28 import android.hidl.manager.V1_0.IServiceManager;
29 import android.hidl.manager.V1_0.IServiceNotification;
30 import android.os.IHwBinder.DeathRecipient;
31 import android.os.RemoteException;
32 import android.util.Slog;
33 
34 import com.android.internal.annotations.GuardedBy;
35 
36 import java.util.Collection;
37 import java.util.HashMap;
38 import java.util.Map;
39 import java.util.Objects;
40 import java.util.stream.Collectors;
41 
42 public class BroadcastRadioService {
43     private static final String TAG = "BcRadio2Srv";
44 
45     private final Object mLock;
46 
47     @GuardedBy("mLock")
48     private int mNextModuleId = 0;
49 
50     @GuardedBy("mLock")
51     private final Map<String, Integer> mServiceNameToModuleIdMap = new HashMap<>();
52 
53     // Map from module ID to RadioModule created by mServiceListener.onRegistration().
54     @GuardedBy("mLock")
55     private final Map<Integer, RadioModule> mModules = new HashMap<>();
56 
57     private IServiceNotification.Stub mServiceListener = new IServiceNotification.Stub() {
58         @Override
59         public void onRegistration(String fqName, String serviceName, boolean preexisting) {
60             Slog.v(TAG, "onRegistration(" + fqName + ", " + serviceName + ", " + preexisting + ")");
61             Integer moduleId;
62             synchronized (mLock) {
63                 // If the service has been registered before, reuse its previous module ID.
64                 moduleId = mServiceNameToModuleIdMap.get(serviceName);
65                 boolean newService = false;
66                 if (moduleId == null) {
67                     newService = true;
68                     moduleId = mNextModuleId;
69                 }
70 
71                 RadioModule module = RadioModule.tryLoadingModule(moduleId, serviceName, mLock);
72                 if (module == null) {
73                     return;
74                 }
75                 Slog.v(TAG, "loaded broadcast radio module " + moduleId + ": " + serviceName
76                         + " (HAL 2.0)");
77                 RadioModule prevModule = mModules.put(moduleId, module);
78                 if (prevModule != null) {
79                     prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE);
80                 }
81 
82                 if (newService) {
83                     mServiceNameToModuleIdMap.put(serviceName, moduleId);
84                     mNextModuleId++;
85                 }
86 
87                 try {
88                     module.getService().linkToDeath(mDeathRecipient, moduleId);
89                 } catch (RemoteException ex) {
90                     // Service has already died, so remove its entry from mModules.
91                     mModules.remove(moduleId);
92                 }
93             }
94         }
95     };
96 
97     private DeathRecipient mDeathRecipient = new DeathRecipient() {
98         @Override
99         public void serviceDied(long cookie) {
100             Slog.v(TAG, "serviceDied(" + cookie + ")");
101             synchronized (mLock) {
102                 int moduleId = (int) cookie;
103                 RadioModule prevModule = mModules.remove(moduleId);
104                 if (prevModule != null) {
105                     prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE);
106                 }
107 
108                 for (Map.Entry<String, Integer> entry : mServiceNameToModuleIdMap.entrySet()) {
109                     if (entry.getValue() == moduleId) {
110                         Slog.i(TAG, "service " + entry.getKey()
111                                 + " died; removed RadioModule with ID " + moduleId);
112                         return;
113                     }
114                 }
115             }
116         }
117     };
118 
BroadcastRadioService(int nextModuleId, Object lock)119     public BroadcastRadioService(int nextModuleId, Object lock) {
120         mNextModuleId = nextModuleId;
121         mLock = lock;
122         try {
123             IServiceManager manager = IServiceManager.getService();
124             if (manager == null) {
125                 Slog.e(TAG, "failed to get HIDL Service Manager");
126                 return;
127             }
128             manager.registerForNotifications(IBroadcastRadio.kInterfaceName, "", mServiceListener);
129         } catch (RemoteException ex) {
130             Slog.e(TAG, "failed to register for service notifications: ", ex);
131         }
132     }
133 
listModules()134     public @NonNull Collection<RadioManager.ModuleProperties> listModules() {
135         synchronized (mLock) {
136             return mModules.values().stream().map(module -> module.mProperties)
137                     .collect(Collectors.toList());
138         }
139     }
140 
hasModule(int id)141     public boolean hasModule(int id) {
142         synchronized (mLock) {
143             return mModules.containsKey(id);
144         }
145     }
146 
hasAnyModules()147     public boolean hasAnyModules() {
148         synchronized (mLock) {
149             return !mModules.isEmpty();
150         }
151     }
152 
openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig, boolean withAudio, @NonNull ITunerCallback callback)153     public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
154         boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
155         Objects.requireNonNull(callback);
156 
157         if (!withAudio) {
158             throw new IllegalArgumentException("Non-audio sessions not supported with HAL 2.x");
159         }
160 
161         RadioModule module = null;
162         synchronized (mLock) {
163             module = mModules.get(moduleId);
164             if (module == null) {
165                 throw new IllegalArgumentException("Invalid module ID");
166             }
167         }
168 
169         TunerSession tunerSession = module.openSession(callback);
170         if (legacyConfig != null) {
171             tunerSession.setConfiguration(legacyConfig);
172         }
173         return tunerSession;
174     }
175 
addAnnouncementListener(@onNull int[] enabledTypes, @NonNull IAnnouncementListener listener)176     public ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
177             @NonNull IAnnouncementListener listener) {
178         AnnouncementAggregator aggregator = new AnnouncementAggregator(listener, mLock);
179         boolean anySupported = false;
180         synchronized (mLock) {
181             for (RadioModule module : mModules.values()) {
182                 try {
183                     aggregator.watchModule(module, enabledTypes);
184                     anySupported = true;
185                 } catch (UnsupportedOperationException ex) {
186                     Slog.v(TAG, "Announcements not supported for this module", ex);
187                 }
188             }
189         }
190         if (!anySupported) {
191             Slog.i(TAG, "There are no HAL modules that support announcements");
192         }
193         return aggregator;
194     }
195 }
196