Implement get/setConfiguration calls of ITuner.

Test: instrumentation
Bug: b/36863239
Change-Id: I0954f8f837c342b35873d3ae834bab83bc3cb04c
This commit is contained in:
Tomasz Wasilczyk
2017-05-01 09:28:36 -07:00
parent 31c8df0abd
commit 8b6db4f2eb
8 changed files with 212 additions and 19 deletions

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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 <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
@@ -104,6 +105,13 @@ void setHalTuner(JNIEnv *env, jobject obj, sp<ITuner> halTuner) {
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);
@@ -114,7 +122,7 @@ Region getRegion(JNIEnv *env, jobject 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());
@@ -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

View File

@@ -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 <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;
@@ -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 &region) {
ALOGV("BandConfigToHal()");
auto jDescriptor = env->GetObjectField(jConfig, gjni.BandConfig.descriptor);
if (jDescriptor == nullptr) {
ALOGE("Descriptor is missing");

View File

@@ -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 &region);
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

View File

@@ -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,

View File

@@ -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<RadioManager.ModuleProperties> 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<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());
}
}