diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl index 68257ff940a3c..c08f41ff17ed5 100644 --- a/core/java/android/hardware/radio/ITuner.aidl +++ b/core/java/android/hardware/radio/ITuner.aidl @@ -22,5 +22,12 @@ import android.hardware.radio.RadioManager; interface ITuner { void close(); + /** + * @throws IllegalArgumentException if config is not valid or null + */ + void setConfiguration(in RadioManager.BandConfig config); + + RadioManager.BandConfig getConfiguration(); + int getProgramInformation(out RadioManager.ProgramInfo[] infoOut); } diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java index 10bbf9f35aa17..1156fe808719a 100644 --- a/core/java/android/hardware/radio/TunerAdapter.java +++ b/core/java/android/hardware/radio/TunerAdapter.java @@ -58,14 +58,28 @@ class TunerAdapter extends RadioTuner { @Override public int setConfiguration(RadioManager.BandConfig config) { - // TODO(b/36863239): forward to mTuner - throw new RuntimeException("Not implemented"); + try { + mTuner.setConfiguration(config); + return RadioManager.STATUS_OK; + } catch (IllegalArgumentException e) { + Log.e(TAG, "Can't set configuration", e); + return RadioManager.STATUS_BAD_VALUE; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } @Override public int getConfiguration(RadioManager.BandConfig[] config) { - // TODO(b/36863239): forward to mTuner - throw new RuntimeException("Not implemented"); + 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) { + throw e.rethrowFromSystemServer(); + } } @Override diff --git a/services/core/java/com/android/server/radio/Tuner.java b/services/core/java/com/android/server/radio/Tuner.java index 248a139748ef8..a06d8c6a8c8da 100644 --- a/services/core/java/com/android/server/radio/Tuner.java +++ b/services/core/java/com/android/server/radio/Tuner.java @@ -31,7 +31,8 @@ class Tuner extends ITuner.Stub { */ private final long mNativeContext; - private int mRegion; + private final Object mLock = new Object(); + private int mRegion; // TODO(b/36863239): find better solution to manage regions Tuner(@NonNull ITunerCallback clientCallback, int region) { mRegion = region; @@ -44,17 +45,44 @@ class Tuner extends ITuner.Stub { super.finalize(); } - private native long nativeInit(ITunerCallback clientCallback); + private native long nativeInit(@NonNull ITunerCallback clientCallback); 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); + @Override public void close() { - nativeClose(mNativeContext); + synchronized (mLock) { + nativeClose(mNativeContext); + } + } + + @Override + public void setConfiguration(RadioManager.BandConfig config) { + if (config == null) { + throw new IllegalArgumentException("The argument must not be a null pointer"); + } + synchronized (mLock) { + nativeSetConfiguration(mNativeContext, config); + mRegion = config.getRegion(); + } + } + + @Override + public RadioManager.BandConfig getConfiguration() { + synchronized (mLock) { + return nativeGetConfiguration(mNativeContext, mRegion); + } } @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; } diff --git a/services/core/jni/com_android_server_radio_Tuner.cpp b/services/core/jni/com_android_server_radio_Tuner.cpp index 3245bff91a5db..96e122d99800a 100644 --- a/services/core/jni/com_android_server_radio_Tuner.cpp +++ b/services/core/jni/com_android_server_radio_Tuner.cpp @@ -19,6 +19,7 @@ #include "com_android_server_radio_Tuner.h" +#include "com_android_server_radio_convert.h" #include "com_android_server_radio_Tuner_TunerCallback.h" #include @@ -104,6 +105,13 @@ void setHalTuner(JNIEnv *env, jobject obj, sp halTuner) { ctx.mHalTuner = halTuner; } +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 getNativeCallback(JNIEnv *env, jobject obj) { AutoMutex _l(gContextMutex); auto& ctx = getNativeContext(env, obj); @@ -114,7 +122,7 @@ Region getRegion(JNIEnv *env, jobject obj) { return static_cast(env->GetIntField(obj, gjni.Tuner.region)); } -static void close(JNIEnv *env, jobject obj, jlong nativeContext) { +static void nativeClose(JNIEnv *env, jobject obj, jlong nativeContext) { AutoMutex _l(gContextMutex); auto& ctx = getNativeContext(nativeContext); ALOGI("Closing tuner %p", ctx.mHalTuner.get()); @@ -123,10 +131,42 @@ static void close(JNIEnv *env, jobject obj, jlong nativeContext) { ctx.mNativeCallback = nullptr; } +static void nativeSetConfiguration(JNIEnv *env, jobject obj, jlong nativeContext, jobject config) { + ALOGV("nativeSetConfiguration()"); + auto halTuner = getHalTuner(nativeContext); + + Region region_unused; + BandConfig bandConfigHal = convert::BandConfigToHal(env, config, region_unused); + + convert::ThrowIfFailed(env, halTuner->setConfiguration(bandConfigHal)); +} + +static jobject nativeGetConfiguration(JNIEnv *env, jobject obj, jlong nativeContext, + Region region) { + ALOGV("nativeSetConfiguration()"); + auto halTuner = getHalTuner(nativeContext); + + BandConfig halConfig; + Result halResult; + auto hidlResult = halTuner->getConfiguration([&](Result result, const BandConfig& config) { + halResult = result; + halConfig = config; + }); + if (convert::ThrowIfFailed(env, hidlResult)) { + return nullptr; + } + + return convert::BandConfigFromHal(env, halConfig, region).release(); +} + static const JNINativeMethod gTunerMethods[] = { { "nativeInit", "(Landroid/hardware/radio/ITunerCallback;)J", (void*)nativeInit }, { "nativeFinalize", "(J)V", (void*)nativeFinalize }, - { "nativeClose", "(J)V", (void*)close }, + { "nativeClose", "(J)V", (void*)nativeClose }, + { "nativeSetConfiguration", "(JLandroid/hardware/radio/RadioManager$BandConfig;)V", + (void*)nativeSetConfiguration }, + { "nativeGetConfiguration", "(JI)Landroid/hardware/radio/RadioManager$BandConfig;", + (void*)nativeGetConfiguration }, }; } // 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 afa3539bcfdb9..419e6d6af2789 100644 --- a/services/core/jni/com_android_server_radio_convert.cpp +++ b/services/core/jni/com_android_server_radio_convert.cpp @@ -28,10 +28,12 @@ namespace server { namespace radio { namespace convert { +using hardware::Return; using hardware::hidl_vec; using V1_0::Band; using V1_0::Deemphasis; +using V1_0::Result; using V1_0::Rds; static struct { @@ -62,6 +64,47 @@ static struct { } BandDescriptor; } gjni; +template +bool ThrowIfFailedCommon(JNIEnv *env, const hardware::Return &hidlResult) { + if (hidlResult.isOk()) return false; + + jniThrowExceptionFmt(env, "java/lang/RuntimeException", + "HIDL call failed: %s", hidlResult.description().c_str()); + 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) { + case Result::OK: + return false; + case Result::NOT_INITIALIZED: + jniThrowException(env, "java/lang/RuntimeException", "Result::NOT_INITIALIZED"); + return true; + case Result::INVALID_ARGUMENTS: + jniThrowException(env, "java/lang/IllegalArgumentException", + "Result::INVALID_ARGUMENTS"); + return true; + case Result::INVALID_STATE: + jniThrowException(env, "java/lang/IllegalStateException", "Result::INVALID_STATE"); + return true; + case Result::TIMEOUT: + jniThrowException(env, "java/lang/RuntimeException", + "Result::TIMEOUT (unexpected here)"); + return true; + default: + jniThrowExceptionFmt(env, "java/lang/RuntimeException", + "Unknown failure, result: %d", result); + return true; + } +} + static Rds RdsForRegion(bool rds, Region region) { if (!rds) return Rds::NONE; @@ -95,6 +138,7 @@ static Deemphasis DeemphasisForRegion(Region region) { } JavaRef BandConfigFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region region) { + ALOGV("BandConfigFromHal()"); EnvWrapper wrap(env); jint spacing = config.spacings.size() > 0 ? config.spacings[0] : 0; @@ -122,6 +166,7 @@ JavaRef BandConfigFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region re } V1_0::BandConfig BandConfigToHal(JNIEnv *env, jobject jConfig, Region ®ion) { + ALOGV("BandConfigToHal()"); auto jDescriptor = env->GetObjectField(jConfig, gjni.BandConfig.descriptor); if (jDescriptor == nullptr) { ALOGE("Descriptor is missing"); diff --git a/services/core/jni/com_android_server_radio_convert.h b/services/core/jni/com_android_server_radio_convert.h index b7e5b9c053c7a..dba948eb54eb2 100644 --- a/services/core/jni/com_android_server_radio_convert.h +++ b/services/core/jni/com_android_server_radio_convert.h @@ -37,6 +37,9 @@ namespace V1_1 = hardware::broadcastradio::V1_1; JavaRef BandConfigFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region region); V1_0::BandConfig BandConfigToHal(JNIEnv *env, jobject jConfig, Region ®ion); +bool ThrowIfFailed(JNIEnv *env, const hardware::Return &hidlResult); +bool ThrowIfFailed(JNIEnv *env, const hardware::Return &hidlResult); + } // namespace convert } // namespace radio } // namespace server diff --git a/services/core/jni/com_android_server_radio_types.h b/services/core/jni/com_android_server_radio_types.h index f2f253ce30e74..8430e05859b5e 100644 --- a/services/core/jni/com_android_server_radio_types.h +++ b/services/core/jni/com_android_server_radio_types.h @@ -27,6 +27,18 @@ namespace radio { * frameworks/base/core/java/android/hardware/radio/RadioManager.java. */ +// Keep in sync with STATUS_* constants from RadioManager.java. +enum class Status : jint { + OK = 0, + ERROR = -0x80000000ll, // Integer.MIN_VALUE + PERMISSION_DENIED = -1, // -EPERM + NO_INIT = -19, // -ENODEV + BAD_VALUE = -22, // -EINVAL + DEAD_OBJECT = -32, // -EPIPE + INVALID_OPERATION = -38, // -ENOSYS + TIMED_OUT = -110, // -ETIMEDOUT +}; + // Keep in sync with REGION_* constants from RadioManager.java. enum class Region : jint { ITU_1 = 0, diff --git a/tests/radio/src/android/hardware/radio/tests/RadioTest.java b/tests/radio/src/android/hardware/radio/tests/RadioTest.java index e9c9b8c185838..7ab5618e7e516 100644 --- a/tests/radio/src/android/hardware/radio/tests/RadioTest.java +++ b/tests/radio/src/android/hardware/radio/tests/RadioTest.java @@ -22,6 +22,7 @@ import android.hardware.radio.RadioTuner; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.List; @@ -30,6 +31,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import static org.junit.Assert.*; @@ -37,6 +39,7 @@ import static org.junit.Assume.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -48,6 +51,8 @@ public class RadioTest { public final Context mContext = InstrumentationRegistry.getContext(); + private final int kConfigCallbacktimeoutNs = 10000; + private RadioManager mRadioManager; private RadioTuner mRadioTuner; private final List mModules = new ArrayList<>(); @@ -56,6 +61,9 @@ public class RadioTest { RadioManager.AmBandDescriptor mAmBandDescriptor; RadioManager.FmBandDescriptor mFmBandDescriptor; + RadioManager.BandConfig mAmBandConfig; + RadioManager.BandConfig mFmBandConfig; + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -98,11 +106,14 @@ public class RadioTest { } assertNotNull(mAmBandDescriptor); assertNotNull(mFmBandDescriptor); - RadioManager.BandConfig fmBandConfig = - new RadioManager.FmBandConfig.Builder(mFmBandDescriptor).build(); + mAmBandConfig = new RadioManager.AmBandConfig.Builder(mAmBandDescriptor).build(); + mFmBandConfig = new RadioManager.FmBandConfig.Builder(mFmBandDescriptor).build(); - mRadioTuner = mRadioManager.openTuner(module.getId(), fmBandConfig, true, mCallback, null); + mRadioTuner = mRadioManager.openTuner(module.getId(), mFmBandConfig, true, mCallback, null); assertNotNull(mRadioTuner); + verify(mCallback, timeout(kConfigCallbacktimeoutNs).times(1)).onConfigurationChanged(any()); + verify(mCallback, never()).onError(anyInt()); + Mockito.reset(mCallback); } @Test @@ -112,26 +123,59 @@ public class RadioTest { } @Test - public void testReopenTuner() { + public void testReopenTuner() throws Throwable { openTuner(); mRadioTuner.close(); mRadioTuner = null; + Thread.sleep(100); // TODO(b/36122635): force reopen openTuner(); verify(mCallback, never()).onError(anyInt()); } @Test - @org.junit.Ignore("setConfiguration is not implemented yet") public void testSetAndGetConfiguration() { openTuner(); - RadioManager.BandConfig amBandConfig = - new RadioManager.AmBandConfig.Builder(mAmBandDescriptor).build(); - mRadioTuner.setConfiguration(amBandConfig); + // set + int ret = mRadioTuner.setConfiguration(mAmBandConfig); + assertEquals(RadioManager.STATUS_OK, ret); + verify(mCallback, timeout(kConfigCallbacktimeoutNs).times(1)).onConfigurationChanged(any()); + + // get + RadioManager.BandConfig[] config = new RadioManager.BandConfig[1]; + ret = mRadioTuner.getConfiguration(config); + assertEquals(RadioManager.STATUS_OK, ret); - verify(mCallback, times(1)).onConfigurationChanged(any()); verify(mCallback, never()).onError(anyInt()); + assertEquals(mAmBandConfig, config[0]); + } - // TODO(b/36863239): implement "get" too + @Test + public void testSetBadConfiguration() throws Throwable { + openTuner(); + + // set bad config + Constructor 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); + assertEquals(RadioManager.STATUS_BAD_VALUE, ret); + verify(mCallback, never()).onConfigurationChanged(any()); + + // setting good config should recover + ret = mRadioTuner.setConfiguration(mAmBandConfig); + assertEquals(RadioManager.STATUS_OK, ret); + verify(mCallback, timeout(kConfigCallbacktimeoutNs).times(1)).onConfigurationChanged(any()); + + verify(mCallback, never()).onError(anyInt()); } }