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);
}
@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
*/
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;
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;
}
#include "com_android_server_radio_Tuner.h"
+#include "com_android_server_radio_convert.h"
#include "com_android_server_radio_Tuner_TunerCallback.h"
#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
ctx.mHalTuner = halTuner;
}
+sp<ITuner> getHalTuner(jlong nativeContext) {
+ AutoMutex _l(gContextMutex);
+ auto tuner = getNativeContext(nativeContext).mHalTuner;
+ LOG_ALWAYS_FATAL_IF(tuner == nullptr, "HAL tuner not set");
+ return tuner;
+}
+
sp<ITunerCallback> getNativeCallback(JNIEnv *env, jobject obj) {
AutoMutex _l(gContextMutex);
auto& ctx = getNativeContext(env, obj);
return static_cast<Region>(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());
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
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 {
} BandDescriptor;
} gjni;
+template <typename T>
+bool ThrowIfFailedCommon(JNIEnv *env, const hardware::Return<T> &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<void> &hidlResult) {
+ return ThrowIfFailedCommon(env, hidlResult);
+}
+
+bool ThrowIfFailed(JNIEnv *env, const hardware::Return<V1_0::Result> &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;
}
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;
}
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");
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<V1_0::Result> &hidlResult);
+bool ThrowIfFailed(JNIEnv *env, const hardware::Return<void> &hidlResult);
+
} // namespace convert
} // namespace radio
} // namespace server
* 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,
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
+import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
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.*;
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;
public final Context mContext = InstrumentationRegistry.getContext();
+ private final int kConfigCallbacktimeoutNs = 10000;
+
private RadioManager mRadioManager;
private RadioTuner mRadioTuner;
private final List<RadioManager.ModuleProperties> mModules = new ArrayList<>();
RadioManager.AmBandDescriptor mAmBandDescriptor;
RadioManager.FmBandDescriptor mFmBandDescriptor;
+ RadioManager.BandConfig mAmBandConfig;
+ RadioManager.BandConfig mFmBandConfig;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
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
}
@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<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);
+ 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());
}
}