/** * 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 android.hardware.radio; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Bitmap; import android.os.RemoteException; import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.util.List; import java.util.Map; import java.util.Objects; /** * Implements the RadioTuner interface by forwarding calls to radio service. */ final class TunerAdapter extends RadioTuner { private static final String TAG = "BroadcastRadio.TunerAdapter"; private final ITuner mTuner; private final TunerCallbackAdapter mCallback; private final Object mLock = new Object(); @GuardedBy("mLock") private boolean mIsClosed; @GuardedBy("mLock") @RadioManager.Band private int mBand; @GuardedBy("mLock") private ProgramList mLegacyListProxy; @GuardedBy("mLock") private Map mLegacyListFilter; TunerAdapter(ITuner tuner, TunerCallbackAdapter callback, @RadioManager.Band int band) { mTuner = Objects.requireNonNull(tuner, "Tuner cannot be null"); mCallback = Objects.requireNonNull(callback, "Callback cannot be null"); mBand = band; } @Override public void close() { synchronized (mLock) { if (mIsClosed) { Log.v(TAG, "Tuner is already closed"); return; } mIsClosed = true; if (mLegacyListProxy != null) { mLegacyListProxy.close(); mLegacyListProxy = null; } } mCallback.close(); try { mTuner.close(); } catch (RemoteException e) { Log.e(TAG, "Exception trying to close tuner", e); } } @Override public int setConfiguration(RadioManager.BandConfig config) { if (config == null) { return RadioManager.STATUS_BAD_VALUE; } try { mTuner.setConfiguration(config); synchronized (mLock) { mBand = config.getType(); } return RadioManager.STATUS_OK; } catch (IllegalArgumentException e) { Log.e(TAG, "Can't set configuration", e); return RadioManager.STATUS_BAD_VALUE; } catch (RemoteException e) { Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } } @Override public int getConfiguration(RadioManager.BandConfig[] config) { if (config == null || config.length != 1) { throw new IllegalArgumentException("The argument must be an array of length 1"); } try { config[0] = mTuner.getConfiguration(); return RadioManager.STATUS_OK; } catch (RemoteException e) { Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } } @Override public int setMute(boolean mute) { try { mTuner.setMuted(mute); } catch (IllegalStateException e) { Log.e(TAG, "Can't set muted", e); return RadioManager.STATUS_ERROR; } catch (RemoteException e) { Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } return RadioManager.STATUS_OK; } @Override public boolean getMute() { try { return mTuner.isMuted(); } catch (RemoteException e) { Log.e(TAG, "Service died", e); return true; } } @Override public int step(int direction, boolean skipSubChannel) { try { mTuner.step(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN, skipSubChannel); } catch (IllegalStateException e) { Log.e(TAG, "Can't step", e); return RadioManager.STATUS_INVALID_OPERATION; } catch (RemoteException e) { Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } return RadioManager.STATUS_OK; } @Override public int scan(int direction, boolean skipSubChannel) { try { mTuner.seek(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN, skipSubChannel); } catch (IllegalStateException e) { Log.e(TAG, "Can't scan", e); return RadioManager.STATUS_INVALID_OPERATION; } catch (RemoteException e) { Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } return RadioManager.STATUS_OK; } @Override public int seek(int direction, boolean skipSubChannel) { try { mTuner.seek(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN, skipSubChannel); } catch (IllegalStateException e) { Log.e(TAG, "Can't seek", e); return RadioManager.STATUS_INVALID_OPERATION; } catch (RemoteException e) { Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } return RadioManager.STATUS_OK; } @Override public int tune(int channel, int subChannel) { try { int band; synchronized (mLock) { band = mBand; } mTuner.tune(ProgramSelector.createAmFmSelector(band, channel, subChannel)); } catch (IllegalStateException e) { Log.e(TAG, "Can't tune", e); return RadioManager.STATUS_INVALID_OPERATION; } catch (IllegalArgumentException e) { Log.e(TAG, "Can't tune", e); return RadioManager.STATUS_BAD_VALUE; } catch (RemoteException e) { Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } return RadioManager.STATUS_OK; } @Override public void tune(ProgramSelector selector) { try { mTuner.tune(selector); } catch (RemoteException e) { throw new RuntimeException("Service died", e); } } @Override public int cancel() { try { mTuner.cancel(); } catch (IllegalStateException e) { Log.e(TAG, "Can't cancel", e); return RadioManager.STATUS_INVALID_OPERATION; } catch (RemoteException e) { Log.e(TAG, "Service died", e); return RadioManager.STATUS_DEAD_OBJECT; } return RadioManager.STATUS_OK; } @Override public void cancelAnnouncement() { try { mTuner.cancelAnnouncement(); } catch (RemoteException e) { throw new RuntimeException("Service died", e); } } @Override public int getProgramInformation(RadioManager.ProgramInfo[] info) { if (info == null || info.length != 1) { Log.e(TAG, "The argument must be an array of length 1"); return RadioManager.STATUS_BAD_VALUE; } RadioManager.ProgramInfo current = mCallback.getCurrentProgramInformation(); if (current == null) { Log.w(TAG, "Didn't get program info yet"); return RadioManager.STATUS_INVALID_OPERATION; } info[0] = current; return RadioManager.STATUS_OK; } @Override @NonNull public Bitmap getMetadataImage(int id) { if (id == 0) { throw new IllegalArgumentException("Invalid metadata image id 0"); } try { Bitmap bitmap = mTuner.getImage(id); if (bitmap == null) { throw new IllegalArgumentException("Metadata image with id " + id + " is not available"); } return bitmap; } catch (RemoteException e) { throw new RuntimeException("Service died", e); } } @Override public boolean startBackgroundScan() { try { return mTuner.startBackgroundScan(); } catch (RemoteException e) { throw new RuntimeException("Service died", e); } } @Override public List getProgramList(@Nullable Map vendorFilter) { synchronized (mLock) { if (mLegacyListProxy == null || !Objects.equals(mLegacyListFilter, vendorFilter)) { Log.i(TAG, "Program list filter has changed, requesting new list"); mLegacyListProxy = new ProgramList(); mLegacyListFilter = vendorFilter; mCallback.clearLastCompleteList(); mCallback.setProgramListObserver(mLegacyListProxy, () -> { Log.i(TAG, "Empty closeListener in programListObserver"); }); } } try { mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter)); } catch (RemoteException ex) { throw new RuntimeException("Service died", ex); } List list = mCallback.getLastCompleteList(); if (list == null) { throw new IllegalStateException("Program list is not ready yet"); } return list; } @Override @Nullable public ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) { synchronized (mLock) { if (mLegacyListProxy != null) { mLegacyListProxy.close(); mLegacyListProxy = null; } mLegacyListFilter = null; } ProgramList list = new ProgramList(); mCallback.setProgramListObserver(list, () -> { try { mTuner.stopProgramListUpdates(); } catch (IllegalStateException ex) { // it's fine to not stop updates if tuner is already closed Log.e(TAG, "Tuner may already be closed", ex); } catch (RemoteException ex) { Log.e(TAG, "Couldn't stop program list updates", ex); } }); try { mTuner.startProgramListUpdates(filter); } catch (UnsupportedOperationException ex) { Log.i(TAG, "Program list is not supported with this hardware"); return null; } catch (RemoteException ex) { mCallback.setProgramListObserver(null, () -> { Log.i(TAG, "Empty closeListener in programListObserver"); }); throw new RuntimeException("Service died", ex); } return list; } @Override public boolean isAnalogForced() { try { return isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG); } catch (UnsupportedOperationException ex) { throw new IllegalStateException(ex); } } @Override public void setAnalogForced(boolean isForced) { try { setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, isForced); } catch (UnsupportedOperationException ex) { throw new IllegalStateException(ex); } } @Override public boolean isConfigFlagSupported(@RadioManager.ConfigFlag int flag) { try { return mTuner.isConfigFlagSupported(flag); } catch (RemoteException e) { throw new RuntimeException("Service died", e); } } @Override public boolean isConfigFlagSet(@RadioManager.ConfigFlag int flag) { try { return mTuner.isConfigFlagSet(convertForceAnalogConfigFlag(flag)); } catch (RemoteException e) { throw new RuntimeException("Service died", e); } } @Override public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) { try { mTuner.setConfigFlag(convertForceAnalogConfigFlag(flag), value); } catch (RemoteException e) { throw new RuntimeException("Service died", e); } } @Override public Map setParameters(Map parameters) { try { return mTuner.setParameters(Objects.requireNonNull(parameters, "Parameters cannot be null")); } catch (RemoteException e) { throw new RuntimeException("Service died", e); } } @Override public Map getParameters(List keys) { try { return mTuner.getParameters(Objects.requireNonNull(keys, "Keys cannot be null")); } catch (RemoteException e) { throw new RuntimeException("Service died", e); } } @Override public boolean isAntennaConnected() { return mCallback.isAntennaConnected(); } @Override public boolean hasControl() { try { // don't rely on mIsClosed, as tuner might get closed internally return !mTuner.isClosed(); } catch (RemoteException e) { return false; } } private @RadioManager.ConfigFlag int convertForceAnalogConfigFlag( @RadioManager.ConfigFlag int flag) throws RemoteException { if (Flags.hdRadioImproved() && flag == RadioManager.CONFIG_FORCE_ANALOG && mTuner.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) { flag = RadioManager.CONFIG_FORCE_ANALOG_FM; } return flag; } }