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 = new Object();
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);
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)119     public BroadcastRadioService(int nextModuleId) {
120         mNextModuleId = nextModuleId;
121         try {
122             IServiceManager manager = IServiceManager.getService();
123             if (manager == null) {
124                 Slog.e(TAG, "failed to get HIDL Service Manager");
125                 return;
126             }
127             manager.registerForNotifications(IBroadcastRadio.kInterfaceName, "", mServiceListener);
128         } catch (RemoteException ex) {
129             Slog.e(TAG, "failed to register for service notifications: ", ex);
130         }
131     }
132 
listModules()133     public @NonNull Collection<RadioManager.ModuleProperties> listModules() {
134         synchronized (mLock) {
135             return mModules.values().stream().map(module -> module.mProperties)
136                     .collect(Collectors.toList());
137         }
138     }
139 
hasModule(int id)140     public boolean hasModule(int id) {
141         synchronized (mLock) {
142             return mModules.containsKey(id);
143         }
144     }
145 
hasAnyModules()146     public boolean hasAnyModules() {
147         synchronized (mLock) {
148             return !mModules.isEmpty();
149         }
150     }
151 
openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig, boolean withAudio, @NonNull ITunerCallback callback)152     public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
153         boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
154         Objects.requireNonNull(callback);
155 
156         if (!withAudio) {
157             throw new IllegalArgumentException("Non-audio sessions not supported with HAL 2.x");
158         }
159 
160         RadioModule module = null;
161         synchronized (mLock) {
162             module = mModules.get(moduleId);
163             if (module == null) {
164                 throw new IllegalArgumentException("Invalid module ID");
165             }
166         }
167 
168         TunerSession tunerSession = module.openSession(callback);
169         if (legacyConfig != null) {
170             tunerSession.setConfiguration(legacyConfig);
171         }
172         return tunerSession;
173     }
174 
addAnnouncementListener(@onNull int[] enabledTypes, @NonNull IAnnouncementListener listener)175     public ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
176             @NonNull IAnnouncementListener listener) {
177         AnnouncementAggregator aggregator = new AnnouncementAggregator(listener);
178         boolean anySupported = false;
179         synchronized (mLock) {
180             for (RadioModule module : mModules.values()) {
181                 try {
182                     aggregator.watchModule(module, enabledTypes);
183                     anySupported = true;
184                 } catch (UnsupportedOperationException ex) {
185                     Slog.v(TAG, "Announcements not supported for this module", ex);
186                 }
187             }
188         }
189         if (!anySupported) {
190             Slog.i(TAG, "There are no HAL modules that support announcements");
191         }
192         return aggregator;
193     }
194 }
195