mRadioTuner = mRadioManager.openTuner(mModule.getId(),
mFmBandConfig, withAudio, mCallback, null);
+ if (!withAudio) {
+ // non-audio sessions might not be supported - if so, then skip the test
+ assumeNotNull(mRadioTuner);
+ }
assertNotNull(mRadioTuner);
verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
resetCallback();
public void testSetBadConfiguration() throws Throwable {
openTuner();
- // set bad config
- Constructor<RadioManager.AmBandConfig> configConstr =
- RadioManager.AmBandConfig.class.getDeclaredConstructor(
- int.class, int.class, int.class, int.class, int.class, boolean.class);
- configConstr.setAccessible(true);
- RadioManager.AmBandConfig badConfig = configConstr.newInstance(
- 0 /*region*/, RadioManager.BAND_AM /*type*/,
- 10000 /*lowerLimit*/, 1 /*upperLimit*/, 100 /*spacing*/, false /*stereo*/);
- int ret = mRadioTuner.setConfiguration(badConfig);
- assertEquals(RadioManager.STATUS_BAD_VALUE, ret);
- verify(mCallback, never()).onConfigurationChanged(any());
-
// set null config
- ret = mRadioTuner.setConfiguration(null);
+ int ret = mRadioTuner.setConfiguration(null);
assertEquals(RadioManager.STATUS_BAD_VALUE, ret);
verify(mCallback, never()).onConfigurationChanged(any());
}
}
- public @NonNull ITuner openSession(@NonNull android.hardware.radio.ITunerCallback userCb) {
+ public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb) {
TunerCallback cb = new TunerCallback(Objects.requireNonNull(userCb));
Mutable<ITunerSession> hwSession = new Mutable<>();
MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
Convert.throwOnError("openSession", halResult.value);
Objects.requireNonNull(hwSession.value);
- TunerSession session = new TunerSession(hwSession.value, cb);
-
- // send out legacy callback about band configuration
- RadioManager.BandDescriptor[] bands = mProperties.getBands();
- if (bands != null && bands.length > 0) {
- RadioManager.BandDescriptor descr = bands[0]; // just pick first
- Mutable<RadioManager.BandConfig> config = new Mutable<>();
- if (descr instanceof RadioManager.FmBandDescriptor) {
- config.value = new RadioManager.FmBandConfig((RadioManager.FmBandDescriptor)descr);
- } else if (descr instanceof RadioManager.AmBandDescriptor) {
- config.value = new RadioManager.AmBandConfig((RadioManager.AmBandDescriptor)descr);
- } else {
- Slog.w(TAG, "Descriptor is neither AM nor FM");
- }
- if (config.value != null) {
- TunerCallback.dispatch(() -> userCb.onConfigurationChanged(config.value));
- }
- }
-
- return session;
+ return new TunerSession(hwSession.value, cb);
}
}
import android.annotation.NonNull;
import android.graphics.Bitmap;
+import android.hardware.broadcastradio.V2_0.ConfigFlag;
import android.hardware.broadcastradio.V2_0.ITunerSession;
+import android.hardware.broadcastradio.V2_0.Result;
import android.hardware.radio.ITuner;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
+import android.media.AudioSystem;
+import android.os.RemoteException;
+import android.util.MutableBoolean;
+import android.util.MutableInt;
import android.util.Slog;
import java.util.List;
class TunerSession extends ITuner.Stub {
private static final String TAG = "BcRadio2Srv.session";
+ private static final String kAudioDeviceName = "Radio tuner source";
private final Object mLock = new Object();
private final ITunerSession mHwSession;
private final TunerCallback mCallback;
private boolean mIsClosed = false;
+ private boolean mIsAudioConnected = false;
+ private boolean mIsMuted = false;
+
+ // necessary only for older APIs compatibility
+ private RadioManager.BandConfig mDummyConfig = null;
TunerSession(@NonNull ITunerSession hwSession, @NonNull TunerCallback callback) {
mHwSession = Objects.requireNonNull(hwSession);
mCallback = Objects.requireNonNull(callback);
+ notifyAudioServiceLocked(true);
}
@Override
synchronized (mLock) {
if (mIsClosed) return;
mIsClosed = true;
+ notifyAudioServiceLocked(false);
}
}
}
}
+ private void notifyAudioServiceLocked(boolean connected) {
+ if (mIsAudioConnected == connected) return;
+
+ Slog.d(TAG, "Notifying AudioService about new state: " + connected);
+ int ret = AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_FM_TUNER,
+ connected ? AudioSystem.DEVICE_STATE_AVAILABLE : AudioSystem.DEVICE_STATE_UNAVAILABLE,
+ null, kAudioDeviceName);
+
+ if (ret == AudioSystem.AUDIO_STATUS_OK) {
+ mIsAudioConnected = connected;
+ } else {
+ Slog.e(TAG, "Failed to notify AudioService about new state: " + connected);
+ }
+ }
+
@Override
- public void setConfiguration(RadioManager.BandConfig config) {}
+ public void setConfiguration(RadioManager.BandConfig config) {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ mDummyConfig = Objects.requireNonNull(config);
+ Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.x");
+ TunerCallback.dispatch(() -> mCallback.mClientCb.onConfigurationChanged(config));
+ }
+ }
@Override
public RadioManager.BandConfig getConfiguration() {
- return null;
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return mDummyConfig;
+ }
}
@Override
- public void setMuted(boolean mute) {}
+ public void setMuted(boolean mute) {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ if (mIsMuted == mute) return;
+ mIsMuted = mute;
+ notifyAudioServiceLocked(!mute);
+ }
+ }
@Override
public boolean isMuted() {
- return false;
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return mIsMuted;
+ }
}
@Override
- public void step(boolean directionDown, boolean skipSubChannel) {}
+ public void step(boolean directionDown, boolean skipSubChannel) {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ }
+ }
@Override
- public void scan(boolean directionDown, boolean skipSubChannel) {}
+ public void scan(boolean directionDown, boolean skipSubChannel) {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ }
+ }
@Override
- public void tune(ProgramSelector selector) {}
+ public void tune(ProgramSelector selector) {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ }
+ }
@Override
- public void cancel() {}
+ public void cancel() {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ }
+ }
@Override
- public void cancelAnnouncement() {}
+ public void cancelAnnouncement() {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ }
+ }
@Override
public RadioManager.ProgramInfo getProgramInformation() {
- return null;
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return null;
+ }
}
@Override
public Bitmap getImage(int id) {
- return null;
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return null;
+ }
}
@Override
public boolean startBackgroundScan() {
+ Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.x");
return false;
}
@Override
public List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) {
- return null;
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return null;
+ }
+ }
+
+ private boolean getConfigFlag(int flag) {
+ Slog.v(TAG, "getConfigFlag " + ConfigFlag.toString(flag));
+ synchronized (mLock) {
+ checkNotClosedLocked();
+
+ MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
+ MutableBoolean flagState = new MutableBoolean(false);
+ try {
+ mHwSession.getConfigFlag(flag, (int result, boolean value) -> {
+ halResult.value = result;
+ flagState.value = value;
+ });
+ } catch (RemoteException ex) {
+ throw new RuntimeException("Failed to get flag " + ConfigFlag.toString(flag), ex);
+ }
+ Convert.throwOnError("getConfigFlag", halResult.value);
+
+ return flagState.value;
+ }
+ }
+
+ private void setConfigFlag(int flag, boolean value) {
+ Slog.v(TAG, "setConfigFlag " + ConfigFlag.toString(flag) + " = " + value);
+ synchronized (mLock) {
+ checkNotClosedLocked();
+
+ int halResult;
+ try {
+ halResult = mHwSession.setConfigFlag(flag, value);
+ } catch (RemoteException ex) {
+ throw new RuntimeException("Failed to set flag " + ConfigFlag.toString(flag), ex);
+ }
+ Convert.throwOnError("setConfigFlag", halResult);
+ }
}
@Override
public boolean isAnalogForced() {
- return false;
+ try {
+ return getConfigFlag(ConfigFlag.FORCE_ANALOG);
+ } catch (UnsupportedOperationException ex) {
+ throw new IllegalStateException(ex);
+ }
}
@Override
- public void setAnalogForced(boolean isForced) {}
+ public void setAnalogForced(boolean isForced) {
+ try {
+ setConfigFlag(ConfigFlag.FORCE_ANALOG, isForced);
+ } catch (UnsupportedOperationException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
@Override
public Map setParameters(Map parameters) {
- return null;
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return null;
+ }
}
@Override
public Map getParameters(List<String> keys) {
- return null;
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return null;
+ }
}
@Override
public boolean isAntennaConnected() {
- return true;
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ return true;
+ }
}
}