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.hal1;
  
  import android.annotation.NonNull;
  import android.graphics.Bitmap;
  import android.graphics.BitmapFactory;
  import android.hardware.radio.ITuner;
  import android.hardware.radio.ITunerCallback;
  import android.hardware.radio.ProgramList;
  import android.hardware.radio.ProgramSelector;
  import android.hardware.radio.RadioManager;
  import android.os.IBinder;
  import android.os.RemoteException;
  import android.util.Slog;
  
  import java.util.Collections;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  
  class Tuner extends ITuner.Stub {
      private static final String TAG = "BroadcastRadioService.Tuner";
  
      /**
       * This field is used by native code, do not access or modify.
       */
      private final long mNativeContext;
  
      private final Object mLock = new Object();
      @NonNull private final TunerCallback mTunerCallback;
      @NonNull private final ITunerCallback mClientCallback;
      @NonNull private final IBinder.DeathRecipient mDeathRecipient;
  
      private boolean mIsClosed = false;
      private boolean mIsMuted = false;
      private int mRegion;
      private final boolean mWithAudio;
  
      Tuner(@NonNull ITunerCallback clientCallback, int halRev,
              int region, boolean withAudio, int band) {
          mClientCallback = clientCallback;
          mTunerCallback = new TunerCallback(this, clientCallback, halRev);
          mRegion = region;
          mWithAudio = withAudio;
          mNativeContext = nativeInit(halRev, withAudio, band);
          mDeathRecipient = this::close;
          try {
              mClientCallback.asBinder().linkToDeath(mDeathRecipient, 0);
          } catch (RemoteException ex) {
              close();
          }
      }
  
      @Override
      protected void finalize() throws Throwable {
          nativeFinalize(mNativeContext);
          super.finalize();
      }
  
      private native long nativeInit(int halRev, boolean withAudio, int band);
      private native void nativeFinalize(long nativeContext);
      private native void nativeClose(long nativeContext);
  
      private native void nativeSetConfiguration(long nativeContext,
              @NonNull RadioManager.BandConfig config);
      private native RadioManager.BandConfig nativeGetConfiguration(long nativeContext, int region);
  
      private native void nativeStep(long nativeContext, boolean directionDown, boolean skipSubChannel);
      private native void nativeScan(long nativeContext, boolean directionDown, boolean skipSubChannel);
      private native void nativeTune(long nativeContext, @NonNull ProgramSelector selector);
      private native void nativeCancel(long nativeContext);
  
      private native void nativeCancelAnnouncement(long nativeContext);
  
      private native boolean nativeStartBackgroundScan(long nativeContext);
      private native List<RadioManager.ProgramInfo> nativeGetProgramList(long nativeContext,
              Map<String, String> vendorFilter);
  
      private native byte[] nativeGetImage(long nativeContext, int id);
  
      private native boolean nativeIsAnalogForced(long nativeContext);
      private native void nativeSetAnalogForced(long nativeContext, boolean isForced);
  
      @Override
      public void close() {
          synchronized (mLock) {
              if (mIsClosed) return;
              mIsClosed = true;
              mTunerCallback.detach();
              mClientCallback.asBinder().unlinkToDeath(mDeathRecipient, 0);
              nativeClose(mNativeContext);
          }
      }
  
      @Override
      public boolean isClosed() {
          return mIsClosed;
      }
  
      private void checkNotClosedLocked() {
          if (mIsClosed) {
              throw new IllegalStateException("Tuner is closed, no further operations are allowed");
          }
      }
  
      private boolean checkConfiguredLocked() {
          if (mTunerCallback.isInitialConfigurationDone()) return true;
          Slog.w(TAG, "Initial configuration is still pending, skipping the operation");
          return false;
      }
  
      @Override
      public void setConfiguration(RadioManager.BandConfig config) {
          if (config == null) {
              throw new IllegalArgumentException("The argument must not be a null pointer");
          }
          synchronized (mLock) {
              checkNotClosedLocked();
              nativeSetConfiguration(mNativeContext, config);
              mRegion = config.getRegion();
          }
      }
  
      @Override
      public RadioManager.BandConfig getConfiguration() {
          synchronized (mLock) {
              checkNotClosedLocked();
              return nativeGetConfiguration(mNativeContext, mRegion);
          }
      }
  
      @Override
      public void setMuted(boolean mute) {
          if (!mWithAudio) {
              throw new IllegalStateException("Can't operate on mute - no audio requested");
          }
          synchronized (mLock) {
              checkNotClosedLocked();
              if (mIsMuted == mute) return;
              mIsMuted = mute;
              Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app");
          }
      }
  
      @Override
      public boolean isMuted() {
          if (!mWithAudio) {
              Slog.w(TAG, "Tuner did not request audio, pretending it was muted");
              return true;
          }
          synchronized (mLock) {
              checkNotClosedLocked();
              return mIsMuted;
          }
      }
  
      @Override
      public void step(boolean directionDown, boolean skipSubChannel) {
          synchronized (mLock) {
              checkNotClosedLocked();
              if (!checkConfiguredLocked()) return;
              nativeStep(mNativeContext, directionDown, skipSubChannel);
          }
      }
  
      @Override
      public void scan(boolean directionDown, boolean skipSubChannel) {
          synchronized (mLock) {
              checkNotClosedLocked();
              if (!checkConfiguredLocked()) return;
              nativeScan(mNativeContext, directionDown, skipSubChannel);
          }
      }
  
      @Override
      public void tune(ProgramSelector selector) {
          if (selector == null) {
              throw new IllegalArgumentException("The argument must not be a null pointer");
          }
          Slog.i(TAG, "Tuning to " + selector);
          synchronized (mLock) {
              checkNotClosedLocked();
              if (!checkConfiguredLocked()) return;
              nativeTune(mNativeContext, selector);
          }
      }
  
      @Override
      public void cancel() {
          synchronized (mLock) {
              checkNotClosedLocked();
              nativeCancel(mNativeContext);
          }
      }
  
      @Override
      public void cancelAnnouncement() {
          synchronized (mLock) {
              checkNotClosedLocked();
              nativeCancelAnnouncement(mNativeContext);
          }
      }
  
      @Override
      public Bitmap getImage(int id) {
          if (id == 0) {
              throw new IllegalArgumentException("Image ID is missing");
          }
  
          byte[] rawImage;
          synchronized (mLock) {
              rawImage = nativeGetImage(mNativeContext, id);
          }
          if (rawImage == null || rawImage.length == 0) {
              return null;
          }
  
          return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
      }
  
      @Override
      public boolean startBackgroundScan() {
          synchronized (mLock) {
              checkNotClosedLocked();
              return nativeStartBackgroundScan(mNativeContext);
          }
      }
  
      List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) {
          Map<String, String> sFilter = vendorFilter;
          synchronized (mLock) {
              checkNotClosedLocked();
              List<RadioManager.ProgramInfo> list = nativeGetProgramList(mNativeContext, sFilter);
              if (list == null) {
                  throw new IllegalStateException("Program list is not ready");
              }
              return list;
          }
      }
  
      @Override
      public void startProgramListUpdates(ProgramList.Filter filter) {
          mTunerCallback.startProgramListUpdates(filter);
      }
  
      @Override
      public void stopProgramListUpdates() {
          mTunerCallback.stopProgramListUpdates();
      }
  
      @Override
      public boolean isConfigFlagSupported(int flag) {
          return flag == RadioManager.CONFIG_FORCE_ANALOG;
      }
  
      @Override
      public boolean isConfigFlagSet(int flag) {
          if (flag == RadioManager.CONFIG_FORCE_ANALOG) {
              synchronized (mLock) {
                  checkNotClosedLocked();
                  return nativeIsAnalogForced(mNativeContext);
              }
          }
          throw new UnsupportedOperationException("Not supported by HAL 1.x");
      }
  
      @Override
      public void setConfigFlag(int flag, boolean value) {
          if (flag == RadioManager.CONFIG_FORCE_ANALOG) {
              synchronized (mLock) {
                  checkNotClosedLocked();
                  nativeSetAnalogForced(mNativeContext, value);
                  return;
              }
          }
          throw new UnsupportedOperationException("Not supported by HAL 1.x");
      }
  
      @Override
      public Map setParameters(Map parameters) {
          throw new UnsupportedOperationException("Not supported by HAL 1.x");
      }
  
      @Override
      public Map getParameters(List<String> keys) {
          throw new UnsupportedOperationException("Not supported by HAL 1.x");
      }
  }
  