diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl index 0eded8f4bc7cf..4e6a1e701d2b1 100644 --- a/core/java/android/hardware/radio/ITuner.aidl +++ b/core/java/android/hardware/radio/ITuner.aidl @@ -29,8 +29,6 @@ interface ITuner { RadioManager.BandConfig getConfiguration(); - int getProgramInformation(out RadioManager.ProgramInfo[] infoOut); - /** * @throws IllegalStateException if tuner was opened without audio */ @@ -58,4 +56,6 @@ interface ITuner { * @throws IllegalStateException if called out of sequence */ void cancel(); + + RadioManager.ProgramInfo getProgramInformation(); } diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java index 3a097a5f43517..bffca61062c3a 100644 --- a/core/java/android/hardware/radio/TunerAdapter.java +++ b/core/java/android/hardware/radio/TunerAdapter.java @@ -137,14 +137,33 @@ class TunerAdapter extends RadioTuner { @Override public int tune(int channel, int subChannel) { - // TODO(b/36863239): forward to mTuner - throw new RuntimeException("Not implemented"); + try { + mTuner.tune(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 int cancel() { - // TODO(b/36863239): forward to mTuner - throw new RuntimeException("Not implemented"); + 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 @@ -153,7 +172,8 @@ class TunerAdapter extends RadioTuner { throw new IllegalArgumentException("The argument must be an array of length 1"); } try { - return mTuner.getProgramInformation(info); + info[0] = mTuner.getProgramInformation(); + return RadioManager.STATUS_OK; } catch (RemoteException e) { Log.e(TAG, "service died", e); return RadioManager.STATUS_DEAD_OBJECT; diff --git a/services/core/java/com/android/server/radio/Tuner.java b/services/core/java/com/android/server/radio/Tuner.java index 85f5e56790b0a..5d87aa5d876c2 100644 --- a/services/core/java/com/android/server/radio/Tuner.java +++ b/services/core/java/com/android/server/radio/Tuner.java @@ -61,6 +61,8 @@ class Tuner extends ITuner.Stub { private native void nativeTune(long nativeContext, int channel, int subChannel); private native void nativeCancel(long nativeContext); + private native RadioManager.ProgramInfo nativeGetProgramInformation(long nativeContext); + @Override public void close() { synchronized (mLock) { @@ -86,15 +88,6 @@ class Tuner extends ITuner.Stub { } } - @Override - public int getProgramInformation(RadioManager.ProgramInfo[] infoOut) { - if (infoOut == null || infoOut.length != 1) { - throw new IllegalArgumentException("The argument must be an array of length 1"); - } - Slog.d(TAG, "getProgramInformation()"); - return RadioManager.STATUS_INVALID_OPERATION; - } - @Override public void setMuted(boolean mute) { if (!mWithAudio) { @@ -147,4 +140,11 @@ class Tuner extends ITuner.Stub { nativeCancel(mNativeContext); } } + + @Override + public RadioManager.ProgramInfo getProgramInformation() { + synchronized (mLock) { + return nativeGetProgramInformation(mNativeContext); + } + } } diff --git a/services/core/jni/com_android_server_radio_Tuner.cpp b/services/core/jni/com_android_server_radio_Tuner.cpp index 5a91c372ee445..052823ffa4db2 100644 --- a/services/core/jni/com_android_server_radio_Tuner.cpp +++ b/services/core/jni/com_android_server_radio_Tuner.cpp @@ -39,7 +39,6 @@ namespace V1_0 = hardware::broadcastradio::V1_0; namespace V1_1 = hardware::broadcastradio::V1_1; using V1_0::BandConfig; -using V1_0::ITuner; using V1_0::MetaData; using V1_0::Result; using V1_1::ITunerCallback; @@ -57,7 +56,9 @@ static struct { struct TunerContext { TunerContext() {} - sp mHalTuner; + HalRevision mHalRev; + sp mHalTuner; + sp mHalTuner11; sp mNativeCallback; private: @@ -82,8 +83,8 @@ static jlong nativeInit(JNIEnv *env, jobject obj, jobject clientCallback, jint h AutoMutex _l(gContextMutex); auto ctx = new TunerContext(); - ctx->mNativeCallback = new TunerCallback(env, obj, - clientCallback, static_cast(halRev)); + ctx->mHalRev = static_cast(halRev); + ctx->mNativeCallback = new TunerCallback(env, obj, clientCallback, ctx->mHalRev); static_assert(sizeof(jlong) >= sizeof(ctx), "jlong is smaller than a pointer"); return reinterpret_cast(ctx); @@ -97,22 +98,33 @@ static void nativeFinalize(JNIEnv *env, jobject obj, jlong nativeContext) { delete ctx; } -void setHalTuner(JNIEnv *env, jobject obj, sp halTuner) { +void setHalTuner(JNIEnv *env, jobject obj, sp halTuner) { ALOGV("setHalTuner(%p)", halTuner.get()); ALOGE_IF(halTuner == nullptr, "HAL tuner is a nullptr"); AutoMutex _l(gContextMutex); auto& ctx = getNativeContext(env, obj); + ctx.mHalTuner = halTuner; + ctx.mHalTuner11 = V1_1::ITuner::castFrom(halTuner).withDefault(nullptr); + ALOGW_IF(ctx.mHalRev >= HalRevision::V1_1 && ctx.mHalTuner11 == nullptr, + "Provided tuner does not implement 1.1 HAL"); } -sp getHalTuner(jlong nativeContext) { +sp getHalTuner(jlong nativeContext) { AutoMutex _l(gContextMutex); auto tuner = getNativeContext(nativeContext).mHalTuner; LOG_ALWAYS_FATAL_IF(tuner == nullptr, "HAL tuner not set"); return tuner; } +sp getHalTuner11(jlong nativeContext) { + AutoMutex _l(gContextMutex); + auto tuner = getNativeContext(nativeContext).mHalTuner11; + LOG_ALWAYS_FATAL_IF(tuner == nullptr, "HAL tuner not set"); + return tuner; +} + sp getNativeCallback(JNIEnv *env, jobject obj) { AutoMutex _l(gContextMutex); auto& ctx = getNativeContext(env, obj); @@ -128,6 +140,7 @@ static void nativeClose(JNIEnv *env, jobject obj, jlong nativeContext) { auto& ctx = getNativeContext(nativeContext); ALOGI("Closing tuner %p", ctx.mHalTuner.get()); ctx.mNativeCallback->detach(); + ctx.mHalTuner11 = nullptr; ctx.mHalTuner = nullptr; ctx.mNativeCallback = nullptr; } @@ -153,7 +166,7 @@ static jobject nativeGetConfiguration(JNIEnv *env, jobject obj, jlong nativeCont halResult = result; halConfig = config; }); - if (convert::ThrowIfFailed(env, hidlResult)) { + if (convert::ThrowIfFailed(env, hidlResult, halResult)) { return nullptr; } @@ -180,13 +193,44 @@ static void nativeScan(JNIEnv *env, jobject obj, jlong nativeContext, static void nativeTune(JNIEnv *env, jobject obj, jlong nativeContext, jint channel, jint subChannel) { - // TODO(b/36863239): implement - jniThrowException(env, "java/lang/RuntimeException", "not implemented yet"); + ALOGV("nativeTune(%d, %d)", channel, subChannel); + auto halTuner = getHalTuner(nativeContext); + + convert::ThrowIfFailed(env, halTuner->tune(channel, subChannel)); } static void nativeCancel(JNIEnv *env, jobject obj, jlong nativeContext) { - // TODO(b/36863239): implement - jniThrowException(env, "java/lang/RuntimeException", "not implemented yet"); + ALOGV("nativeCancel()"); + auto halTuner = getHalTuner(nativeContext); + + convert::ThrowIfFailed(env, halTuner->cancel()); +} + +static jobject nativeGetProgramInformation(JNIEnv *env, jobject obj, jlong nativeContext) { + ALOGV("nativeGetProgramInformation()"); + auto halTuner10 = getHalTuner(nativeContext); + auto halTuner11 = getHalTuner11(nativeContext); + + V1_1::ProgramInfo halInfo; + Result halResult; + Return hidlResult; + if (halTuner11 != nullptr) { + hidlResult = halTuner11->getProgramInformation_1_1([&](Result result, + const V1_1::ProgramInfo& info) { + halResult = result; + halInfo = info; + }); + } else { + hidlResult = halTuner10->getProgramInformation([&](Result result, + const V1_0::ProgramInfo& info) { + halResult = result; + halInfo.base = info; + }); + } + + if (convert::ThrowIfFailed(env, hidlResult, halResult)) return nullptr; + + return convert::ProgramInfoFromHal(env, halInfo).release(); } static const JNINativeMethod gTunerMethods[] = { @@ -201,6 +245,8 @@ static const JNINativeMethod gTunerMethods[] = { { "nativeScan", "(JZZ)V", (void*)nativeScan }, { "nativeTune", "(JII)V", (void*)nativeTune }, { "nativeCancel", "(J)V", (void*)nativeCancel }, + { "nativeGetProgramInformation", "(J)Landroid/hardware/radio/RadioManager$ProgramInfo;", + (void*)nativeGetProgramInformation }, }; } // namespace Tuner diff --git a/services/core/jni/com_android_server_radio_convert.cpp b/services/core/jni/com_android_server_radio_convert.cpp index 683f0eeca9bb1..8ad899addbd01 100644 --- a/services/core/jni/com_android_server_radio_convert.cpp +++ b/services/core/jni/com_android_server_radio_convert.cpp @@ -81,7 +81,7 @@ static struct { } gjni; template -bool ThrowIfFailedCommon(JNIEnv *env, const hardware::Return &hidlResult) { +static bool ThrowIfFailedHidl(JNIEnv *env, const Return &hidlResult) { if (hidlResult.isOk()) return false; jniThrowExceptionFmt(env, "java/lang/RuntimeException", @@ -89,15 +89,8 @@ bool ThrowIfFailedCommon(JNIEnv *env, const hardware::Return &hidlResult) { return true; } -bool ThrowIfFailed(JNIEnv *env, const hardware::Return &hidlResult) { - return ThrowIfFailedCommon(env, hidlResult); -} - -bool ThrowIfFailed(JNIEnv *env, const hardware::Return &hidlResult) { - if (ThrowIfFailedCommon(env, hidlResult)) return true; - - Result result = hidlResult; - switch (result) { +static bool ThrowIfFailed(JNIEnv *env, const Result halResult) { + switch (halResult) { case Result::OK: return false; case Result::NOT_INITIALIZED: @@ -116,11 +109,20 @@ bool ThrowIfFailed(JNIEnv *env, const hardware::Return &hidlResult return true; default: jniThrowExceptionFmt(env, "java/lang/RuntimeException", - "Unknown failure, result: %d", result); + "Unknown failure, result: %d", halResult); return true; } } +bool ThrowIfFailed(JNIEnv *env, const Return &hidlResult, Result halResult) { + return ThrowIfFailedHidl(env, hidlResult) || ThrowIfFailed(env, halResult); +} + +bool ThrowIfFailed(JNIEnv *env, const Return &hidlResult) { + return ThrowIfFailedHidl(env, hidlResult) + || ThrowIfFailed(env, static_cast(hidlResult)); +} + static Rds RdsForRegion(bool rds, Region region) { if (!rds) return Rds::NONE; diff --git a/services/core/jni/com_android_server_radio_convert.h b/services/core/jni/com_android_server_radio_convert.h index 7b6e38da9ca31..811f6539d4ffe 100644 --- a/services/core/jni/com_android_server_radio_convert.h +++ b/services/core/jni/com_android_server_radio_convert.h @@ -35,7 +35,7 @@ namespace V1_0 = hardware::broadcastradio::V1_0; namespace V1_1 = hardware::broadcastradio::V1_1; bool ThrowIfFailed(JNIEnv *env, const hardware::Return &hidlResult); -bool ThrowIfFailed(JNIEnv *env, const hardware::Return &hidlResult); +bool ThrowIfFailed(JNIEnv *env, const hardware::Return &hidlResult, V1_0::Result halResult); JavaRef BandConfigFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region region); V1_0::BandConfig BandConfigToHal(JNIEnv *env, jobject jConfig, Region ®ion); diff --git a/tests/radio/src/android/hardware/radio/tests/RadioTest.java b/tests/radio/src/android/hardware/radio/tests/RadioTest.java index fd69383ce60e9..25432e35c770b 100644 --- a/tests/radio/src/android/hardware/radio/tests/RadioTest.java +++ b/tests/radio/src/android/hardware/radio/tests/RadioTest.java @@ -28,8 +28,10 @@ import java.util.List; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -38,6 +40,8 @@ import static org.junit.Assert.*; import static org.junit.Assume.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.after; +import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; @@ -52,8 +56,9 @@ public class RadioTest { public final Context mContext = InstrumentationRegistry.getContext(); - private final int kConfigCallbacktimeoutNs = 10000; - private final int kTuneCallbacktimeoutNs = 30000; + private final int kConfigCallbackTimeoutMs = 10000; + private final int kCancelTimeoutMs = 1000; + private final int kTuneCallbackTimeoutMs = 30000; private RadioManager mRadioManager; private RadioTuner mRadioTuner; @@ -124,10 +129,16 @@ public class RadioTest { mRadioTuner = mRadioManager.openTuner(module.getId(), mFmBandConfig, withAudio, mCallback, null); assertNotNull(mRadioTuner); - verify(mCallback, timeout(kConfigCallbacktimeoutNs).times(1)).onConfigurationChanged(any()); + verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any()); resetCallback(); } + private void checkAntenna() { + // TODO(b/36863239): enable check when isAntennaConnected is implemented + //boolean isConnected = mRadioTuner.isAntennaConnected(); + //assertTrue(isConnected); + } + @Test public void testOpenTuner() { openTuner(); @@ -149,7 +160,7 @@ public class RadioTest { // set int ret = mRadioTuner.setConfiguration(mAmBandConfig); assertEquals(RadioManager.STATUS_OK, ret); - verify(mCallback, timeout(kConfigCallbacktimeoutNs).times(1)).onConfigurationChanged(any()); + verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any()); // get RadioManager.BandConfig[] config = new RadioManager.BandConfig[1]; @@ -183,7 +194,7 @@ public class RadioTest { // setting good config should recover ret = mRadioTuner.setConfiguration(mAmBandConfig); assertEquals(RadioManager.STATUS_OK, ret); - verify(mCallback, timeout(kConfigCallbacktimeoutNs).times(1)).onConfigurationChanged(any()); + verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any()); } @Test @@ -218,15 +229,79 @@ public class RadioTest { @Test public void testStep() { openTuner(); + checkAntenna(); int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, true); assertEquals(RadioManager.STATUS_OK, ret); - verify(mCallback, timeout(kTuneCallbacktimeoutNs).times(1)).onProgramInfoChanged(any()); + verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any()); resetCallback(); ret = mRadioTuner.step(RadioTuner.DIRECTION_UP, false); assertEquals(RadioManager.STATUS_OK, ret); - verify(mCallback, timeout(kTuneCallbacktimeoutNs).times(1)).onProgramInfoChanged(any()); + verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any()); + } + + @Test + public void testTuneAndGetPI() { + openTuner(); + checkAntenna(); + + int channel = mFmBandConfig.getLowerLimit() + mFmBandConfig.getSpacing(); + + // test tune + int ret = mRadioTuner.tune(channel, 0); + assertEquals(RadioManager.STATUS_OK, ret); + ArgumentCaptor infoc = + ArgumentCaptor.forClass(RadioManager.ProgramInfo.class); + verify(mCallback, timeout(kTuneCallbackTimeoutMs)) + .onProgramInfoChanged(infoc.capture()); + assertEquals(channel, infoc.getValue().getChannel()); + + // test getProgramInformation + RadioManager.ProgramInfo[] info = new RadioManager.ProgramInfo[1]; + ret = mRadioTuner.getProgramInformation(info); + assertEquals(RadioManager.STATUS_OK, ret); + assertNotNull(info[0]); + assertEquals(channel, info[0].getChannel()); + } + + @Test + public void testDummyCancel() { + openTuner(); + + int ret = mRadioTuner.cancel(); + assertEquals(RadioManager.STATUS_OK, ret); + } + + @Test + public void testLateCancel() { + openTuner(); + checkAntenna(); + + int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, false); + assertEquals(RadioManager.STATUS_OK, ret); + verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any()); + + int cancelRet = mRadioTuner.cancel(); + assertEquals(RadioManager.STATUS_OK, cancelRet); + } + + @Test + public void testScanAndCancel() { + openTuner(); + checkAntenna(); + + /* There is a possible race condition between scan and cancel commands - the scan may finish + * before cancel command is issued. Thus we accept both outcomes in this test. + */ + int scanRet = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, true); + int cancelRet = mRadioTuner.cancel(); + + assertEquals(RadioManager.STATUS_OK, scanRet); + assertEquals(RadioManager.STATUS_OK, cancelRet); + + verify(mCallback, after(kCancelTimeoutMs).atMost(1)).onError(RadioTuner.ERROR_CANCELLED); + verify(mCallback, atMost(1)).onProgramInfoChanged(any()); } }