content: /**
   * 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;
      }
  }
  