Make RadioService actually talking to the HAL.
For now, only opening and closing a tuner is implemented.
Test: instrumentation, KitchenSink
Bug: b/36863239
Change-Id: Ib2e14c0108c0714524d50b9557f24465c68f5ef2
diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl
index 3c83114..90bb746 100644
--- a/core/java/android/hardware/radio/IRadioService.aidl
+++ b/core/java/android/hardware/radio/IRadioService.aidl
@@ -24,5 +24,5 @@
* {@hide}
*/
interface IRadioService {
- ITuner openTuner();
+ ITuner openTuner(boolean withAudio);
}
diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl
index 73f6dc2..68257ff 100644
--- a/core/java/android/hardware/radio/ITuner.aidl
+++ b/core/java/android/hardware/radio/ITuner.aidl
@@ -20,5 +20,7 @@
/** {@hide} */
interface ITuner {
+ void close();
+
int getProgramInformation(out RadioManager.ProgramInfo[] infoOut);
}
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 99412de..0ca868a 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -1448,10 +1448,14 @@
if (mService != null) {
ITuner tuner;
try {
- tuner = mService.openTuner();
+ tuner = mService.openTuner(withAudio);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+ if (tuner == null) {
+ Log.e(TAG, "Failed to open tuner");
+ return null;
+ }
return new TunerAdapter(tuner);
}
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
index 1822e07b..10bbf9f 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -31,6 +31,7 @@
private static final String TAG = "radio.TunerAdapter";
@NonNull private final ITuner mTuner;
+ private boolean mIsClosed = false;
TunerAdapter(ITuner tuner) {
if (tuner == null) {
@@ -41,8 +42,18 @@
@Override
public void close() {
- // TODO(b/36863239): forward to mTuner
- Log.w(TAG, "Close call not implemented");
+ synchronized (mTuner) {
+ if (mIsClosed) {
+ Log.d(TAG, "Tuner is already closed");
+ return;
+ }
+ mIsClosed = true;
+ }
+ try {
+ mTuner.close();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception trying to close tuner", e);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/radio/RadioService.java b/services/core/java/com/android/server/radio/RadioService.java
index 327e98f..fb93384 100644
--- a/services/core/java/com/android/server/radio/RadioService.java
+++ b/services/core/java/com/android/server/radio/RadioService.java
@@ -28,29 +28,37 @@
// TODO(b/36863239): rename to RadioService when native service goes away
private static final String TAG = "RadioServiceJava";
+ private final RadioServiceImpl mServiceImpl = new RadioServiceImpl();
+
+ /**
+ * This field is used by native code, do not access or modify.
+ */
+ private final long mNativeContext = nativeInit();
+
public RadioService(Context context) {
super(context);
}
@Override
+ protected void finalize() throws Throwable {
+ nativeFinalize(mNativeContext);
+ super.finalize();
+ }
+
+ private native long nativeInit();
+ private native void nativeFinalize(long nativeContext);
+ private native Tuner openTunerNative(long nativeContext, boolean withAudio);
+
+ @Override
public void onStart() {
- publishBinderService(Context.RADIO_SERVICE, new RadioServiceImpl());
+ publishBinderService(Context.RADIO_SERVICE, mServiceImpl);
Slog.v(TAG, "RadioService started");
}
- private static class RadioServiceImpl extends IRadioService.Stub {
+ private class RadioServiceImpl extends IRadioService.Stub {
@Override
- public ITuner openTuner() {
- Slog.d(TAG, "openTuner()");
- return new TunerImpl();
- }
- }
-
- private static class TunerImpl extends ITuner.Stub {
- @Override
- public int getProgramInformation(RadioManager.ProgramInfo[] infoOut) {
- Slog.d(TAG, "getProgramInformation()");
- return RadioManager.STATUS_INVALID_OPERATION;
+ public ITuner openTuner(boolean withAudio) {
+ return openTunerNative(mNativeContext, withAudio);
}
}
}
diff --git a/services/core/java/com/android/server/radio/Tuner.java b/services/core/java/com/android/server/radio/Tuner.java
new file mode 100644
index 0000000..6de9c99
--- /dev/null
+++ b/services/core/java/com/android/server/radio/Tuner.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.radio;
+
+import android.hardware.radio.ITuner;
+import android.hardware.radio.RadioManager;
+import android.util.Slog;
+
+class Tuner extends ITuner.Stub {
+ // TODO(b/36863239): rename to RadioService.Tuner when native service goes away
+ private static final String TAG = "RadioServiceJava.Tuner";
+
+ /**
+ * This field is used by native code, do not access or modify.
+ */
+ private final long mNativeContext = nativeInit();
+
+ @Override
+ protected void finalize() throws Throwable {
+ nativeFinalize(mNativeContext);
+ super.finalize();
+ }
+
+ private native long nativeInit();
+ private native void nativeFinalize(long nativeContext);
+
+ @Override
+ public native void close();
+
+ @Override
+ public int getProgramInformation(RadioManager.ProgramInfo[] infoOut) {
+ Slog.d(TAG, "getProgramInformation()");
+ return RadioManager.STATUS_INVALID_OPERATION;
+ }
+}
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 23637de..3e167e4 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -18,6 +18,8 @@
$(LOCAL_REL_DIR)/com_android_server_location_ContextHubService.cpp \
$(LOCAL_REL_DIR)/com_android_server_location_GnssLocationProvider.cpp \
$(LOCAL_REL_DIR)/com_android_server_power_PowerManagerService.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_radio_RadioService.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_radio_Tuner.cpp \
$(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \
$(LOCAL_REL_DIR)/com_android_server_SyntheticPasswordManager.cpp \
$(LOCAL_REL_DIR)/com_android_server_storage_AppFuseBridge.cpp \
@@ -80,6 +82,8 @@
libutils \
libhwui \
android.hardware.audio.common@2.0 \
+ android.hardware.broadcastradio@1.0 \
+ android.hardware.broadcastradio@1.1 \
android.hardware.contexthub@1.0 \
android.hardware.gnss@1.0 \
android.hardware.ir@1.0 \
diff --git a/services/core/jni/com_android_server_radio_RadioService.cpp b/services/core/jni/com_android_server_radio_RadioService.cpp
new file mode 100644
index 0000000..34dbf0c
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_RadioService.cpp
@@ -0,0 +1,173 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "radio.RadioService.jni"
+#define LOG_NDEBUG 0
+
+#include "com_android_server_radio_RadioService.h"
+
+#include "com_android_server_radio_Tuner.h"
+
+#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
+#include <core_jni_helpers.h>
+#include <utils/Log.h>
+#include <JNIHelp.h>
+
+namespace android {
+
+using hardware::Return;
+using hardware::hidl_vec;
+
+namespace V1_0 = hardware::broadcastradio::V1_0;
+namespace V1_1 = hardware::broadcastradio::V1_1;
+
+using V1_0::Class;
+using V1_0::IBroadcastRadio;
+using V1_0::Result;
+
+using V1_0::BandConfig;
+using V1_0::ProgramInfo;
+using V1_0::MetaData;
+using V1_0::ITuner;
+
+static Mutex gContextMutex;
+
+static jclass gTunerClass;
+static jmethodID gTunerCstor;
+
+static jclass gServiceClass;
+
+struct ServiceContext {
+ ServiceContext() {}
+
+ sp<IBroadcastRadio> mModule;
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(ServiceContext);
+};
+
+
+/**
+ * Always lock gContextMutex when using native context.
+ */
+static ServiceContext& getNativeContext(jlong nativeContextHandle) {
+ auto nativeContext = reinterpret_cast<ServiceContext*>(nativeContextHandle);
+ LOG_ALWAYS_FATAL_IF(nativeContext == nullptr, "Native context not initialized");
+ return *nativeContext;
+}
+
+static jlong nativeInit(JNIEnv *env, jobject obj) {
+ ALOGV("nativeInit()");
+ AutoMutex _l(gContextMutex);
+
+ auto nativeContext = new ServiceContext();
+ static_assert(sizeof(jlong) >= sizeof(nativeContext), "jlong is smaller than a pointer");
+ return reinterpret_cast<jlong>(nativeContext);
+}
+
+static void nativeFinalize(JNIEnv *env, jobject obj, jlong nativeContext) {
+ ALOGV("nativeFinalize()");
+ AutoMutex _l(gContextMutex);
+
+ auto ctx = reinterpret_cast<ServiceContext*>(nativeContext);
+ delete ctx;
+}
+
+static sp<IBroadcastRadio> getModule(jlong nativeContext) {
+ ALOGV("getModule()");
+ AutoMutex _l(gContextMutex);
+ auto& ctx = getNativeContext(nativeContext);
+
+ if (ctx.mModule != nullptr) {
+ return ctx.mModule;
+ }
+
+ // TODO(b/36863239): what about other HAL implementations?
+ auto factory = V1_0::IBroadcastRadioFactory::getService();
+ if (factory == nullptr) {
+ ALOGE("Can't retrieve radio HAL implementation");
+ return nullptr;
+ }
+
+ sp<IBroadcastRadio> module = nullptr;
+ // TODO(b/36863239): not only AM/FM
+ factory->connectModule(Class::AM_FM, [&](Result retval, const sp<IBroadcastRadio>& result) {
+ if (retval == Result::OK) {
+ module = result;
+ }
+ });
+
+ ALOGE_IF(module == nullptr, "Couldn't connect module");
+ ctx.mModule = module;
+ return module;
+}
+
+static jobject openTunerNative(JNIEnv *env, jobject obj, long nativeContext, bool withAudio) {
+ ALOGV("openTunerNative()");
+
+ auto module = getModule(nativeContext);
+ if (module == nullptr) {
+ return nullptr;
+ }
+
+ jobject tuner = env->NewObject(gTunerClass, gTunerCstor);
+ if (tuner == nullptr) {
+ ALOGE("Unable to create new tuner object.");
+ return nullptr;
+ }
+
+ BandConfig bandConfig = {}; // TODO(b/36863239): convert from parameters
+ auto tunerCb = android_server_radio_Tuner_getCallback(env, tuner);
+ Result halResult;
+ sp<ITuner> halTuner = nullptr;
+
+ auto hidlResult = module->openTuner(bandConfig, withAudio, tunerCb,
+ [&](Result result, const sp<ITuner>& tuner) {
+ halResult = result;
+ halTuner = tuner;
+ });
+ if (!hidlResult.isOk() || halResult != Result::OK || halTuner == nullptr) {
+ ALOGE("Couldn't open tuner");
+ ALOGE_IF(hidlResult.isOk(), "halResult = %d", halResult);
+ ALOGE_IF(!hidlResult.isOk(), "hidlResult = %s", hidlResult.description().c_str());
+ env->DeleteLocalRef(tuner);
+ return nullptr;
+ }
+
+ android_server_radio_Tuner_setHalTuner(env, tuner, halTuner);
+ return tuner;
+}
+
+static const JNINativeMethod gRadioServiceMethods[] = {
+ { "nativeInit", "()J", (void*)nativeInit },
+ { "nativeFinalize", "(J)V", (void*)nativeFinalize },
+ { "openTunerNative", "(JZ)Lcom/android/server/radio/Tuner;", (void*)openTunerNative },
+};
+
+void register_android_server_radio_RadioService(JNIEnv *env) {
+ auto tunerClass = FindClassOrDie(env, "com/android/server/radio/Tuner");
+ gTunerClass = MakeGlobalRefOrDie(env, tunerClass);
+ gTunerCstor = GetMethodIDOrDie(env, tunerClass, "<init>", "()V");
+
+ auto serviceClass = FindClassOrDie(env, "com/android/server/radio/RadioService");
+ gServiceClass = MakeGlobalRefOrDie(env, serviceClass);
+
+ auto res = jniRegisterNativeMethods(env, "com/android/server/radio/RadioService",
+ gRadioServiceMethods, NELEM(gRadioServiceMethods));
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+}
+
+} /* namespace android */
diff --git a/services/core/jni/com_android_server_radio_RadioService.h b/services/core/jni/com_android_server_radio_RadioService.h
new file mode 100644
index 0000000..1fef9e6
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_RadioService.h
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_RADIO_RADIOSERVICE_H
+#define _ANDROID_SERVER_RADIO_RADIOSERVICE_H
+
+#include "jni.h"
+
+namespace android {
+
+void register_android_server_radio_RadioService(JNIEnv *env);
+
+} // namespace android
+
+#endif // _ANDROID_SERVER_RADIO_RADIOSERVICE_H
diff --git a/services/core/jni/com_android_server_radio_Tuner.cpp b/services/core/jni/com_android_server_radio_Tuner.cpp
new file mode 100644
index 0000000..f3180a0
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_Tuner.cpp
@@ -0,0 +1,138 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "radio.Tuner.jni"
+#define LOG_NDEBUG 0
+
+#include "com_android_server_radio_Tuner.h"
+
+#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
+#include <core_jni_helpers.h>
+#include <utils/Log.h>
+#include <JNIHelp.h>
+
+namespace android {
+
+using hardware::Return;
+using hardware::hidl_vec;
+
+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;
+using V1_1::ProgramListResult;
+
+static Mutex gContextMutex;
+
+static jclass gTunerClass;
+static jfieldID gNativeContextId;
+
+struct TunerContext {
+ TunerContext() {}
+
+ sp<ITuner> mHalTuner;
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(TunerContext);
+};
+
+// TODO(b/36863239): implement actual callback class which forwards calls to Java code.
+class DummyTunerCallback : public ITunerCallback {
+ virtual Return<void> hardwareFailure() { return Return<void>(); }
+ virtual Return<void> configChange(Result result, const BandConfig& config) {
+ return Return<void>();
+ }
+ virtual Return<void> tuneComplete(Result result, const V1_0::ProgramInfo& info) {
+ return Return<void>();
+ }
+ virtual Return<void> afSwitch(const V1_0::ProgramInfo& info) { return Return<void>(); }
+ virtual Return<void> antennaStateChange(bool connected) { return Return<void>(); }
+ virtual Return<void> trafficAnnouncement(bool active) { return Return<void>(); }
+ virtual Return<void> emergencyAnnouncement(bool active) { return Return<void>(); }
+ virtual Return<void> newMetadata(uint32_t channel, uint32_t subChannel,
+ const hidl_vec<MetaData>& metadata) { return Return<void>(); }
+ virtual Return<void> tuneComplete_1_1(Result result, const V1_1::ProgramInfo& info) {
+ return Return<void>();
+ }
+ virtual Return<void> afSwitch_1_1(const V1_1::ProgramInfo& info) { return Return<void>(); }
+ virtual Return<void> backgroundScanAvailable(bool isAvailable) { return Return<void>(); }
+ virtual Return<void> backgroundScanComplete(ProgramListResult result) { return Return<void>(); }
+ virtual Return<void> programListChanged() { return Return<void>(); }
+};
+
+/**
+ * Always lock gContextMutex when using native context.
+ */
+static TunerContext& getNativeContext(JNIEnv *env, jobject obj) {
+ auto nativeContext = reinterpret_cast<TunerContext*>(env->GetLongField(obj, gNativeContextId));
+ LOG_ALWAYS_FATAL_IF(nativeContext == nullptr, "Native context not initialized");
+ return *nativeContext;
+}
+
+static jlong nativeInit(JNIEnv *env, jobject obj) {
+ ALOGV("nativeInit()");
+ AutoMutex _l(gContextMutex);
+
+ auto nativeContext = new TunerContext();
+ static_assert(sizeof(jlong) >= sizeof(nativeContext), "jlong is smaller than a pointer");
+ return reinterpret_cast<jlong>(nativeContext);
+}
+
+static void nativeFinalize(JNIEnv *env, jobject obj, jlong nativeContext) {
+ ALOGV("nativeFinalize()");
+ AutoMutex _l(gContextMutex);
+
+ auto ctx = reinterpret_cast<TunerContext*>(nativeContext);
+ delete ctx;
+}
+
+void android_server_radio_Tuner_setHalTuner(JNIEnv *env, jobject obj, sp<ITuner> halTuner) {
+ ALOGV("setHalTuner(%p)", halTuner.get());
+ AutoMutex _l(gContextMutex);
+
+ auto& ctx = getNativeContext(env, obj);
+ ctx.mHalTuner = halTuner;
+}
+
+sp<ITunerCallback> android_server_radio_Tuner_getCallback(JNIEnv *env, jobject obj) {
+ return new DummyTunerCallback();
+}
+
+static void close(JNIEnv *env, jobject obj) {
+ android_server_radio_Tuner_setHalTuner(env, obj, nullptr);
+}
+
+static const JNINativeMethod gTunerMethods[] = {
+ { "nativeInit", "()J", (void*)nativeInit },
+ { "nativeFinalize", "(J)V", (void*)nativeFinalize },
+ { "close", "()V", (void*)close },
+};
+
+void register_android_server_radio_Tuner(JNIEnv *env) {
+ auto tunerClass = FindClassOrDie(env, "com/android/server/radio/Tuner");
+ gTunerClass = MakeGlobalRefOrDie(env, tunerClass);
+ gNativeContextId = GetFieldIDOrDie(env, gTunerClass, "mNativeContext", "J");
+
+ auto res = jniRegisterNativeMethods(env, "com/android/server/radio/Tuner",
+ gTunerMethods, NELEM(gTunerMethods));
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+}
+
+} /* namespace android */
diff --git a/services/core/jni/com_android_server_radio_Tuner.h b/services/core/jni/com_android_server_radio_Tuner.h
new file mode 100644
index 0000000..ec16a18
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_Tuner.h
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_RADIO_TUNER_H
+#define _ANDROID_SERVER_RADIO_TUNER_H
+
+#include "jni.h"
+
+#include <android/hardware/broadcastradio/1.1/ITuner.h>
+#include <android/hardware/broadcastradio/1.1/ITunerCallback.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+void register_android_server_radio_Tuner(JNIEnv *env);
+
+void android_server_radio_Tuner_setHalTuner(JNIEnv *env, jobject obj,
+ sp<hardware::broadcastradio::V1_0::ITuner> halTuner);
+
+sp<hardware::broadcastradio::V1_1::ITunerCallback>
+android_server_radio_Tuner_getCallback(JNIEnv *env, jobject obj);
+
+} // namespace android
+
+#endif // _ANDROID_SERVER_RADIO_TUNER_H
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index f22b330..13ff28c 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -19,6 +19,9 @@
#include "utils/Log.h"
#include "utils/misc.h"
+#include "com_android_server_radio_RadioService.h"
+#include "com_android_server_radio_Tuner.h"
+
namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_BatteryStatsService(JNIEnv* env);
@@ -62,6 +65,8 @@
}
ALOG_ASSERT(env, "Could not retrieve the env!");
+ register_android_server_radio_RadioService(env);
+ register_android_server_radio_Tuner(env);
register_android_server_PowerManagerService(env);
register_android_server_SerialService(env);
register_android_server_InputApplicationHandle(env);
diff --git a/tests/radio/src/android/hardware/radio/tests/RadioTest.java b/tests/radio/src/android/hardware/radio/tests/RadioTest.java
index 47fbe74..384ddbc 100644
--- a/tests/radio/src/android/hardware/radio/tests/RadioTest.java
+++ b/tests/radio/src/android/hardware/radio/tests/RadioTest.java
@@ -94,4 +94,12 @@
public void testOpenTuner() {
openTuner(new RadioTuner.Callback() {});
}
+
+ @Test
+ public void testReopenTuner() {
+ openTuner(new RadioTuner.Callback() {});
+ mRadioTuner.close();
+ mRadioTuner = null;
+ openTuner(new RadioTuner.Callback() {});
+ }
}