blob: 5e79c5943d7b129cc5ba41a3f48530de1b3e4cb9 [file] [log] [blame]
/**
* Copyright (C) 2017 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.broadcastradio.V2_0.IBroadcastRadio;
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;
import android.hardware.radio.RadioTuner;
import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
import android.os.IHwBinder.DeathRecipient;
import android.os.RemoteException;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
public class BroadcastRadioService {
private static final String TAG = "BcRadio2Srv";
private final Object mLock = new Object();
@GuardedBy("mLock")
private int mNextModuleId = 0;
@GuardedBy("mLock")
private final Map<String, Integer> mServiceNameToModuleIdMap = new HashMap<>();
// Map from module ID to RadioModule created by mServiceListener.onRegistration().
@GuardedBy("mLock")
private final Map<Integer, RadioModule> mModules = new HashMap<>();
private IServiceNotification.Stub mServiceListener = new IServiceNotification.Stub() {
@Override
public void onRegistration(String fqName, String serviceName, boolean preexisting) {
Slog.v(TAG, "onRegistration(" + fqName + ", " + serviceName + ", " + preexisting + ")");
Integer moduleId;
synchronized (mLock) {
// If the service has been registered before, reuse its previous module ID.
moduleId = mServiceNameToModuleIdMap.get(serviceName);
boolean newService = false;
if (moduleId == null) {
newService = true;
moduleId = mNextModuleId;
}
RadioModule module = RadioModule.tryLoadingModule(moduleId, serviceName);
if (module == null) {
return;
}
Slog.v(TAG, "loaded broadcast radio module " + moduleId + ": " + serviceName
+ " (HAL 2.0)");
RadioModule prevModule = mModules.put(moduleId, module);
if (prevModule != null) {
prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE);
}
if (newService) {
mServiceNameToModuleIdMap.put(serviceName, moduleId);
mNextModuleId++;
}
try {
module.getService().linkToDeath(mDeathRecipient, moduleId);
} catch (RemoteException ex) {
// Service has already died, so remove its entry from mModules.
mModules.remove(moduleId);
}
}
}
};
private DeathRecipient mDeathRecipient = new DeathRecipient() {
@Override
public void serviceDied(long cookie) {
Slog.v(TAG, "serviceDied(" + cookie + ")");
synchronized (mLock) {
int moduleId = (int) cookie;
RadioModule prevModule = mModules.remove(moduleId);
if (prevModule != null) {
prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE);
}
for (Map.Entry<String, Integer> entry : mServiceNameToModuleIdMap.entrySet()) {
if (entry.getValue() == moduleId) {
Slog.i(TAG, "service " + entry.getKey()
+ " died; removed RadioModule with ID " + moduleId);
return;
}
}
}
}
};
public BroadcastRadioService(int nextModuleId) {
mNextModuleId = nextModuleId;
try {
IServiceManager manager = IServiceManager.getService();
if (manager == null) {
Slog.e(TAG, "failed to get HIDL Service Manager");
return;
}
manager.registerForNotifications(IBroadcastRadio.kInterfaceName, "", mServiceListener);
} catch (RemoteException ex) {
Slog.e(TAG, "failed to register for service notifications: ", ex);
}
}
public @NonNull Collection<RadioManager.ModuleProperties> listModules() {
synchronized (mLock) {
return mModules.values().stream().map(module -> module.mProperties)
.collect(Collectors.toList());
}
}
public boolean hasModule(int id) {
synchronized (mLock) {
return mModules.containsKey(id);
}
}
public boolean hasAnyModules() {
synchronized (mLock) {
return !mModules.isEmpty();
}
}
public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
Objects.requireNonNull(callback);
if (!withAudio) {
throw new IllegalArgumentException("Non-audio sessions not supported with HAL 2.x");
}
RadioModule module = null;
synchronized (mLock) {
module = mModules.get(moduleId);
if (module == null) {
throw new IllegalArgumentException("Invalid module ID");
}
}
TunerSession tunerSession = module.openSession(callback);
if (legacyConfig != null) {
tunerSession.setConfiguration(legacyConfig);
}
return tunerSession;
}
public ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
@NonNull IAnnouncementListener listener) {
AnnouncementAggregator aggregator = new AnnouncementAggregator(listener);
boolean anySupported = false;
synchronized (mLock) {
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;
}
}