Merge "Pass radio HAL callbacks back to Java client."

This commit is contained in:
Tomasz Wasilczyk
2017-04-24 20:19:05 +00:00
committed by Android (Google) Code Review
19 changed files with 667 additions and 74 deletions

View File

@@ -218,6 +218,7 @@ LOCAL_SRC_FILES += \
core/java/android/hardware/location/IContextHubService.aidl \
core/java/android/hardware/radio/IRadioService.aidl \
core/java/android/hardware/radio/ITuner.aidl \
core/java/android/hardware/radio/ITunerCallback.aidl \
core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl \
core/java/android/hardware/usb/IUsbManager.aidl \
core/java/android/net/ICaptivePortal.aidl \

View File

@@ -17,6 +17,8 @@
package android.hardware.radio;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
/**
* API to the broadcast radio service.
@@ -24,5 +26,6 @@ import android.hardware.radio.ITuner;
* {@hide}
*/
interface IRadioService {
ITuner openTuner(boolean withAudio);
ITuner openTuner(int moduleId, in RadioManager.BandConfig bandConfig, boolean withAudio,
in ITunerCallback callback);
}

View File

@@ -0,0 +1,25 @@
/**
* 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 android.hardware.radio;
import android.hardware.radio.RadioManager;
/** {@hide} */
oneway interface ITunerCallback {
void onError(int status);
void onConfigurationChanged(in RadioManager.BandConfig config);
}

View File

@@ -18,3 +18,6 @@ package android.hardware.radio;
/** @hide */
parcelable RadioManager.ProgramInfo;
/** @hide */
parcelable RadioManager.BandConfig;

View File

@@ -1446,9 +1446,13 @@ public class RadioManager {
}
if (mService != null) {
Log.d(TAG, "Opening tuner...");
ITuner tuner;
ITunerCallback halCallback = new TunerCallbackAdapter(callback, handler);
try {
tuner = mService.openTuner(withAudio);
// TODO(b/36863239): pass bandConfig too, after fixing deserialization bug
tuner = mService.openTuner(moduleId, null, withAudio, halCallback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

View File

@@ -0,0 +1,56 @@
/**
* 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 android.hardware.radio;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback.
*/
class TunerCallbackAdapter extends ITunerCallback.Stub {
private static final String TAG = "radio.TunerCallbackAdapter";
@NonNull private final RadioTuner.Callback mCallback;
@NonNull private final Handler mHandler;
TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) {
mCallback = callback;
if (handler == null) {
mHandler = new Handler(Looper.getMainLooper());
} else {
mHandler = handler;
}
}
@Override
public void onError(int status) {
mHandler.post(() -> mCallback.onError(status));
}
@Override
public void onConfigurationChanged(RadioManager.BandConfig config) {
mHandler.post(() -> mCallback.onConfigurationChanged(config));
}
}

View File

@@ -19,6 +19,7 @@ package com.android.server.radio;
import android.content.Context;
import android.hardware.radio.IRadioService;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
import android.util.Slog;
@@ -47,7 +48,8 @@ public class RadioService extends SystemService {
private native long nativeInit();
private native void nativeFinalize(long nativeContext);
private native Tuner openTunerNative(long nativeContext, boolean withAudio);
private native Tuner nativeOpenTuner(long nativeContext, int moduleId,
RadioManager.BandConfig config, boolean withAudio, ITunerCallback callback);
@Override
public void onStart() {
@@ -57,8 +59,13 @@ public class RadioService extends SystemService {
private class RadioServiceImpl extends IRadioService.Stub {
@Override
public ITuner openTuner(boolean withAudio) {
return openTunerNative(mNativeContext, withAudio);
public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
boolean withAudio, ITunerCallback callback) {
// TODO(b/36863239): add death monitoring for binder
if (callback == null) {
throw new IllegalArgumentException("Callback must not be empty");
}
return nativeOpenTuner(mNativeContext, moduleId, bandConfig, withAudio, callback);
}
}
}

View File

@@ -16,7 +16,9 @@
package com.android.server.radio;
import android.annotation.NonNull;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
import android.util.Slog;
@@ -27,7 +29,11 @@ class Tuner extends ITuner.Stub {
/**
* This field is used by native code, do not access or modify.
*/
private final long mNativeContext = nativeInit();
private final long mNativeContext;
Tuner(@NonNull ITunerCallback clientCallback) {
mNativeContext = nativeInit(clientCallback);
}
@Override
protected void finalize() throws Throwable {
@@ -35,11 +41,14 @@ class Tuner extends ITuner.Stub {
super.finalize();
}
private native long nativeInit();
private native long nativeInit(ITunerCallback clientCallback);
private native void nativeFinalize(long nativeContext);
private native void nativeClose(long nativeContext);
@Override
public native void close();
public void close() {
nativeClose(mNativeContext);
}
@Override
public int getProgramInformation(RadioManager.ProgramInfo[] infoOut) {

View File

@@ -5,6 +5,7 @@ LOCAL_REL_DIR := core/jni
LOCAL_CFLAGS += -Wall -Werror -Wno-unused-parameter
LOCAL_SRC_FILES += \
$(LOCAL_REL_DIR)/NativeCallbackThread.cpp \
$(LOCAL_REL_DIR)/com_android_server_AlarmManagerService.cpp \
$(LOCAL_REL_DIR)/com_android_server_am_BatteryStatsService.cpp \
$(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \
@@ -20,6 +21,7 @@ LOCAL_SRC_FILES += \
$(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_radio_Tuner_TunerCallback.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 \

View File

@@ -0,0 +1,116 @@
/**
* 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 "NativeCallbackThread"
//#define LOG_NDEBUG 0
#include "NativeCallbackThread.h"
#include <utils/Log.h>
namespace android {
NativeCallbackThread::NativeCallbackThread(JavaVM *vm) : mExitting(false), mvm(vm) {
auto res = pthread_create(&mThread, nullptr, main, this);
if (res != 0) {
ALOGE("Couldn't start NativeCallbackThread");
mThread = 0;
return;
}
ALOGD("Started native callback thread %p", this);
}
NativeCallbackThread::~NativeCallbackThread() {
ALOGV("~NativeCallbackThread %p", this);
stop();
}
void* NativeCallbackThread::main(void *args) {
auto self = reinterpret_cast<NativeCallbackThread*>(args);
self->main();
return nullptr;
}
void NativeCallbackThread::main() {
ALOGV("NativeCallbackThread::main()");
JNIEnv *env = nullptr;
JavaVMAttachArgs aargs = {JNI_VERSION_1_4, "NativeCallbackThread", nullptr};
if (mvm->AttachCurrentThread(&env, &aargs) != JNI_OK || env == nullptr) {
ALOGE("Couldn't attach thread");
return;
}
while (!mExitting) {
ALOGV("Waiting for task...");
Task task;
{
AutoMutex _l(mQueueMutex);
auto res = mQueueCond.wait(mQueueMutex);
ALOGE_IF(res != 0, "Wait failed: %d", res);
if (mExitting || res != 0) break;
if (mQueue.empty()) continue;
task = mQueue.front();
mQueue.pop();
}
ALOGV("Executing task...");
task(env);
if (env->ExceptionCheck()) {
ALOGE("Unexpected exception:");
env->ExceptionDescribe();
env->ExceptionClear();
}
}
auto res = mvm->DetachCurrentThread();
ALOGE_IF(res != JNI_OK, "Couldn't detach thread");
ALOGV("Native callback thread %p finished", this);
}
void NativeCallbackThread::enqueue(const Task &task) {
AutoMutex _l(mQueueMutex);
if (mThread == 0 || mExitting) {
ALOGW("Callback thread %p is not serving calls", this);
return;
}
mQueue.push(task);
mQueueCond.signal();
}
void NativeCallbackThread::stop() {
ALOGV("stop() %p", this);
{
AutoMutex _l(mQueueMutex);
if (mThread == 0 || mExitting) return;
mExitting = true;
mQueueCond.signal();
}
auto ret = pthread_join(mThread, nullptr);
ALOGE_IF(ret != 0, "Couldn't join thread: %d", ret);
ALOGD("Stopped native callback thread %p", this);
}
} // namespace android

View File

@@ -0,0 +1,56 @@
/**
* 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_NATIVE_CALLBACK_THREAD_H
#define _ANDROID_NATIVE_CALLBACK_THREAD_H
#include <android-base/macros.h>
#include <functional>
#include <jni.h>
#include <pthread.h>
#include <queue>
#include <utils/Condition.h>
#include <utils/Mutex.h>
namespace android {
class NativeCallbackThread {
typedef std::function<void(JNIEnv*)> Task;
pthread_t mThread;
Mutex mQueueMutex;
Condition mQueueCond;
std::atomic<bool> mExitting;
JavaVM *mvm;
std::queue<Task> mQueue;
static void* main(void *args);
void main();
DISALLOW_COPY_AND_ASSIGN(NativeCallbackThread);
public:
NativeCallbackThread(JavaVM *vm);
virtual ~NativeCallbackThread();
void enqueue(const Task &task);
void stop();
};
} // namespace android
#endif // _ANDROID_NATIVE_CALLBACK_THREAD_H

View File

@@ -27,6 +27,9 @@
#include <JNIHelp.h>
namespace android {
namespace server {
namespace radio {
namespace RadioService {
using hardware::Return;
using hardware::hidl_vec;
@@ -115,26 +118,32 @@ static sp<IBroadcastRadio> getModule(jlong nativeContext) {
return module;
}
static jobject openTunerNative(JNIEnv *env, jobject obj, long nativeContext, bool withAudio) {
ALOGV("openTunerNative()");
static jobject nativeOpenTuner(JNIEnv *env, jobject obj, long nativeContext, jint moduleId,
jobject bandConfig, bool withAudio, jobject callback) {
ALOGV("nativeOpenTuner()");
if (callback == nullptr) {
ALOGE("Callback is empty");
return nullptr;
}
// TODO(b/36863239): use moduleId
auto module = getModule(nativeContext);
if (module == nullptr) {
return nullptr;
}
jobject tuner = env->NewObject(gTunerClass, gTunerCstor);
jobject tuner = env->NewObject(gTunerClass, gTunerCstor, callback);
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);
BandConfig bandConfigHal = {}; // TODO(b/36863239): convert from bandConfig
auto tunerCb = Tuner::getNativeCallback(env, tuner);
Result halResult;
sp<ITuner> halTuner = nullptr;
auto hidlResult = module->openTuner(bandConfig, withAudio, tunerCb,
auto hidlResult = module->openTuner(bandConfigHal, withAudio, tunerCb,
[&](Result result, const sp<ITuner>& tuner) {
halResult = result;
halTuner = tuner;
@@ -147,20 +156,30 @@ static jobject openTunerNative(JNIEnv *env, jobject obj, long nativeContext, boo
return nullptr;
}
android_server_radio_Tuner_setHalTuner(env, tuner, halTuner);
Tuner::setHalTuner(env, tuner, halTuner);
ALOGI("Opened tuner %p", halTuner.get());
return tuner;
}
static const JNINativeMethod gRadioServiceMethods[] = {
{ "nativeInit", "()J", (void*)nativeInit },
{ "nativeFinalize", "(J)V", (void*)nativeFinalize },
{ "openTunerNative", "(JZ)Lcom/android/server/radio/Tuner;", (void*)openTunerNative },
{ "nativeOpenTuner", "(JILandroid/hardware/radio/RadioManager$BandConfig;Z"
"Landroid/hardware/radio/ITunerCallback;)Lcom/android/server/radio/Tuner;",
(void*)nativeOpenTuner },
};
} // namespace RadioService
} // namespace radio
} // namespace server
void register_android_server_radio_RadioService(JNIEnv *env) {
using namespace server::radio::RadioService;
auto tunerClass = FindClassOrDie(env, "com/android/server/radio/Tuner");
gTunerClass = MakeGlobalRefOrDie(env, tunerClass);
gTunerCstor = GetMethodIDOrDie(env, tunerClass, "<init>", "()V");
gTunerCstor = GetMethodIDOrDie(env, tunerClass, "<init>",
"(Landroid/hardware/radio/ITunerCallback;)V");
auto serviceClass = FindClassOrDie(env, "com/android/server/radio/RadioService");
gServiceClass = MakeGlobalRefOrDie(env, serviceClass);
@@ -170,4 +189,4 @@ void register_android_server_radio_RadioService(JNIEnv *env) {
LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
}
} /* namespace android */
} // namespace android

View File

@@ -17,7 +17,7 @@
#ifndef _ANDROID_SERVER_RADIO_RADIOSERVICE_H
#define _ANDROID_SERVER_RADIO_RADIOSERVICE_H
#include "jni.h"
#include <jni.h>
namespace android {

View File

@@ -19,12 +19,17 @@
#include "com_android_server_radio_Tuner.h"
#include "com_android_server_radio_Tuner_TunerCallback.h"
#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
#include <core_jni_helpers.h>
#include <utils/Log.h>
#include <JNIHelp.h>
namespace android {
namespace server {
namespace radio {
namespace Tuner {
using hardware::Return;
using hardware::hidl_vec;
@@ -48,51 +53,34 @@ struct TunerContext {
TunerContext() {}
sp<ITuner> mHalTuner;
sp<TunerCallback> mNativeCallback;
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>(); }
};
static TunerContext& getNativeContext(jlong nativeContextHandle) {
auto nativeContext = reinterpret_cast<TunerContext*>(nativeContextHandle);
LOG_ALWAYS_FATAL_IF(nativeContext == nullptr, "Native context not initialized");
return *nativeContext;
}
/**
* 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;
return getNativeContext(env->GetLongField(obj, gNativeContextId));
}
static jlong nativeInit(JNIEnv *env, jobject obj) {
static jlong nativeInit(JNIEnv *env, jobject obj, jobject clientCallback) {
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);
auto ctx = new TunerContext();
ctx->mNativeCallback = new TunerCallback(env, obj, clientCallback);
static_assert(sizeof(jlong) >= sizeof(ctx), "jlong is smaller than a pointer");
return reinterpret_cast<jlong>(ctx);
}
static void nativeFinalize(JNIEnv *env, jobject obj, jlong nativeContext) {
@@ -103,29 +91,43 @@ static void nativeFinalize(JNIEnv *env, jobject obj, jlong nativeContext) {
delete ctx;
}
void android_server_radio_Tuner_setHalTuner(JNIEnv *env, jobject obj, sp<ITuner> halTuner) {
void setHalTuner(JNIEnv *env, jobject obj, sp<ITuner> halTuner) {
ALOGV("setHalTuner(%p)", halTuner.get());
AutoMutex _l(gContextMutex);
ALOGE_IF(halTuner == nullptr, "HAL tuner is a nullptr");
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();
sp<ITunerCallback> getNativeCallback(JNIEnv *env, jobject obj) {
AutoMutex _l(gContextMutex);
auto& ctx = getNativeContext(env, obj);
return ctx.mNativeCallback;
}
static void close(JNIEnv *env, jobject obj) {
android_server_radio_Tuner_setHalTuner(env, obj, nullptr);
static void close(JNIEnv *env, jobject obj, jlong nativeContext) {
AutoMutex _l(gContextMutex);
auto& ctx = getNativeContext(nativeContext);
ALOGI("Closing tuner %p", ctx.mHalTuner.get());
ctx.mNativeCallback->detach();
ctx.mHalTuner = nullptr;
ctx.mNativeCallback = nullptr;
}
static const JNINativeMethod gTunerMethods[] = {
{ "nativeInit", "()J", (void*)nativeInit },
{ "nativeInit", "(Landroid/hardware/radio/ITunerCallback;)J", (void*)nativeInit },
{ "nativeFinalize", "(J)V", (void*)nativeFinalize },
{ "close", "()V", (void*)close },
{ "nativeClose", "(J)V", (void*)close },
};
} // namespace Tuner
} // namespace radio
} // namespace server
void register_android_server_radio_Tuner(JNIEnv *env) {
using namespace server::radio::Tuner;
auto tunerClass = FindClassOrDie(env, "com/android/server/radio/Tuner");
gTunerClass = MakeGlobalRefOrDie(env, tunerClass);
gNativeContextId = GetFieldIDOrDie(env, gTunerClass, "mNativeContext", "J");
@@ -135,4 +137,4 @@ void register_android_server_radio_Tuner(JNIEnv *env) {
LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
}
} /* namespace android */
} // namespace android

View File

@@ -17,22 +17,26 @@
#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 <jni.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);
namespace server {
namespace radio {
namespace Tuner {
sp<hardware::broadcastradio::V1_1::ITunerCallback>
android_server_radio_Tuner_getCallback(JNIEnv *env, jobject obj);
void setHalTuner(JNIEnv *env, jobject obj, sp<hardware::broadcastradio::V1_0::ITuner> halTuner);
sp<hardware::broadcastradio::V1_1::ITunerCallback> getNativeCallback(JNIEnv *env, jobject obj);
} // namespace Tuner
} // namespace radio
} // namespace server
} // namespace android
#endif // _ANDROID_SERVER_RADIO_TUNER_H

View File

@@ -0,0 +1,180 @@
/**
* 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.TunerCallback.jni"
#define LOG_NDEBUG 0
#include "com_android_server_radio_Tuner_TunerCallback.h"
#include <core_jni_helpers.h>
#include <utils/Log.h>
#include <JNIHelp.h>
namespace android {
namespace server {
namespace radio {
namespace Tuner {
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::MetaData;
using V1_0::Result;
using V1_1::ITunerCallback;
using V1_1::ProgramListResult;
static JavaVM *gvm = nullptr;
static jclass gITunerCallbackClass;
static struct {
jmethodID onError;
jmethodID onConfigurationChanged;
} gITunerCallbackMethods;
// from frameworks/base/core/java/android/hardware/radio/RadioTuner.java
enum class TunerError : int {
HARDWARE_FAILURE = 0,
SERVER_DIED = 1,
CANCELLED = 2,
SCAN_TIMEOUT = 3,
CONFIG = 4,
};
TunerCallback::TunerCallback(JNIEnv *env, jobject tuner, jobject clientCallback)
: mCallbackThread(gvm) {
ALOGV("TunerCallback()");
mTuner = env->NewGlobalRef(tuner);
mClientCallback = env->NewGlobalRef(clientCallback);
}
TunerCallback::~TunerCallback() {
ALOGV("~TunerCallback()");
// stop callback thread before dereferencing client callback
mCallbackThread.stop();
JNIEnv *env = nullptr;
gvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4);
if (env != nullptr) {
env->DeleteGlobalRef(mTuner);
env->DeleteGlobalRef(mClientCallback);
}
}
void TunerCallback::detach() {
// stop callback thread to ignore further calls
mCallbackThread.stop();
}
Return<void> TunerCallback::hardwareFailure() {
ALOGE("Not implemented: hardwareFailure");
return Return<void>();
}
Return<void> TunerCallback::configChange(Result result, const BandConfig& config) {
ALOGV("configChange(%d)", result);
mCallbackThread.enqueue([result, this](JNIEnv *env) {
if (result == Result::OK) {
// TODO(b/36863239): convert parameter
env->CallVoidMethod(mClientCallback, gITunerCallbackMethods.onConfigurationChanged,
nullptr);
} else {
env->CallVoidMethod(mClientCallback, gITunerCallbackMethods.onError,
TunerError::CONFIG);
}
});
return Return<void>();
}
Return<void> TunerCallback::tuneComplete(Result result, const V1_0::ProgramInfo& info) {
ALOGE("Not implemented: tuneComplete");
return Return<void>();
}
Return<void> TunerCallback::afSwitch(const V1_0::ProgramInfo& info) {
ALOGE("Not implemented: afSwitch");
return Return<void>();
}
Return<void> TunerCallback::antennaStateChange(bool connected) {
ALOGE("Not implemented: antennaStateChange");
return Return<void>();
}
Return<void> TunerCallback::trafficAnnouncement(bool active) {
ALOGE("Not implemented: trafficAnnouncement");
return Return<void>();
}
Return<void> TunerCallback::emergencyAnnouncement(bool active) {
ALOGE("Not implemented: emergencyAnnouncement");
return Return<void>();
}
Return<void> TunerCallback::newMetadata(uint32_t channel, uint32_t subChannel,
const hidl_vec<MetaData>& metadata) {
ALOGE("Not implemented: newMetadata");
return Return<void>();
}
Return<void> TunerCallback::tuneComplete_1_1(Result result, const V1_1::ProgramInfo& info) {
ALOGE("Not implemented: tuneComplete_1_1");
return Return<void>();
}
Return<void> TunerCallback::afSwitch_1_1(const V1_1::ProgramInfo& info) {
ALOGE("Not implemented: afSwitch_1_1afSwitch_1_1");
return Return<void>();
}
Return<void> TunerCallback::backgroundScanAvailable(bool isAvailable) {
ALOGE("Not implemented: backgroundScanAvailable");
return Return<void>();
}
Return<void> TunerCallback::backgroundScanComplete(ProgramListResult result) {
ALOGE("Not implemented: backgroundScanComplete");
return Return<void>();
}
Return<void> TunerCallback::programListChanged() {
ALOGE("Not implemented: programListChanged");
return Return<void>();
}
} // namespace Tuner
} // namespace radio
} // namespace server
void register_android_server_radio_Tuner_TunerCallback(JavaVM *vm, JNIEnv *env) {
using namespace server::radio::Tuner;
gvm = vm;
auto iTunerCallbackClass = FindClassOrDie(env, "android/hardware/radio/ITunerCallback");
gITunerCallbackClass = MakeGlobalRefOrDie(env, iTunerCallbackClass);
gITunerCallbackMethods.onError = GetMethodIDOrDie(env, gITunerCallbackClass, "onError", "(I)V");
gITunerCallbackMethods.onConfigurationChanged = GetMethodIDOrDie(env, gITunerCallbackClass,
"onConfigurationChanged", "(Landroid/hardware/radio/RadioManager$BandConfig;)V");
}
} // namespace android

View File

@@ -0,0 +1,72 @@
/**
* 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_TUNERCALLBACK_H
#define _ANDROID_SERVER_RADIO_TUNER_TUNERCALLBACK_H
#include "NativeCallbackThread.h"
#include <android/hardware/broadcastradio/1.1/ITunerCallback.h>
#include <jni.h>
namespace android {
void register_android_server_radio_Tuner_TunerCallback(JavaVM *vm, JNIEnv *env);
namespace server {
namespace radio {
namespace Tuner {
class TunerCallback : public hardware::broadcastradio::V1_1::ITunerCallback {
jobject mTuner;
jobject mClientCallback;
NativeCallbackThread mCallbackThread;
DISALLOW_COPY_AND_ASSIGN(TunerCallback);
public:
TunerCallback(JNIEnv *env, jobject tuner, jobject clientCallback);
virtual ~TunerCallback();
void detach();
virtual hardware::Return<void> hardwareFailure();
virtual hardware::Return<void> configChange(hardware::broadcastradio::V1_0::Result result,
const hardware::broadcastradio::V1_0::BandConfig& config);
virtual hardware::Return<void> tuneComplete(hardware::broadcastradio::V1_0::Result result,
const hardware::broadcastradio::V1_0::ProgramInfo& info);
virtual hardware::Return<void> afSwitch(
const hardware::broadcastradio::V1_0::ProgramInfo& info);
virtual hardware::Return<void> antennaStateChange(bool connected);
virtual hardware::Return<void> trafficAnnouncement(bool active);
virtual hardware::Return<void> emergencyAnnouncement(bool active);
virtual hardware::Return<void> newMetadata(uint32_t channel, uint32_t subChannel,
const hardware::hidl_vec<hardware::broadcastradio::V1_0::MetaData>& metadata);
virtual hardware::Return<void> tuneComplete_1_1(hardware::broadcastradio::V1_0::Result result,
const hardware::broadcastradio::V1_1::ProgramInfo& info);
virtual hardware::Return<void> afSwitch_1_1(const hardware::broadcastradio::V1_1::ProgramInfo& info);
virtual hardware::Return<void> backgroundScanAvailable(bool isAvailable);
virtual hardware::Return<void> backgroundScanComplete(
hardware::broadcastradio::V1_1::ProgramListResult result);
virtual hardware::Return<void> programListChanged();
};
} // namespace Tuner
} // namespace radio
} // namespace server
} // namespace android
#endif // _ANDROID_SERVER_RADIO_TUNER_TUNERCALLBACK_H

View File

@@ -21,6 +21,7 @@
#include "com_android_server_radio_RadioService.h"
#include "com_android_server_radio_Tuner.h"
#include "com_android_server_radio_Tuner_TunerCallback.h"
namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
@@ -67,6 +68,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_radio_RadioService(env);
register_android_server_radio_Tuner(env);
register_android_server_radio_Tuner_TunerCallback(vm, env);
register_android_server_PowerManagerService(env);
register_android_server_SerialService(env);
register_android_server_InputApplicationHandle(env);

View File

@@ -29,9 +29,16 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
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.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* A test for broadcast radio API.
@@ -44,9 +51,15 @@ public class RadioTest {
private RadioManager mRadioManager;
private RadioTuner mRadioTuner;
private final List<RadioManager.ModuleProperties> mModules = new ArrayList<>();
@Mock private RadioTuner.Callback mCallback;
RadioManager.AmBandDescriptor mAmBandDescriptor;
RadioManager.FmBandDescriptor mFmBandDescriptor;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
// check if radio is supported and skip the test if it's not
PackageManager packageManager = mContext.getPackageManager();
boolean isRadioSupported = packageManager.hasSystemFeature(PackageManager.FEATURE_RADIO);
@@ -70,36 +83,55 @@ public class RadioTest {
}
}
private void openTuner(RadioTuner.Callback callback) {
private void openTuner() {
assertNull(mRadioTuner);
// find FM band and build its config
RadioManager.ModuleProperties module = mModules.get(0);
RadioManager.FmBandDescriptor fmBandDescriptor = null;
for (RadioManager.BandDescriptor band : module.getBands()) {
if (band.getType() == RadioManager.BAND_AM) {
mAmBandDescriptor = (RadioManager.AmBandDescriptor)band;
}
if (band.getType() == RadioManager.BAND_FM) {
fmBandDescriptor = (RadioManager.FmBandDescriptor)band;
break;
mFmBandDescriptor = (RadioManager.FmBandDescriptor)band;
}
}
assertNotNull(fmBandDescriptor);
assertNotNull(mAmBandDescriptor);
assertNotNull(mFmBandDescriptor);
RadioManager.BandConfig fmBandConfig =
new RadioManager.FmBandConfig.Builder(fmBandDescriptor).build();
new RadioManager.FmBandConfig.Builder(mFmBandDescriptor).build();
mRadioTuner = mRadioManager.openTuner(module.getId(), fmBandConfig, true, callback, null);
mRadioTuner = mRadioManager.openTuner(module.getId(), fmBandConfig, true, mCallback, null);
assertNotNull(mRadioTuner);
}
@Test
public void testOpenTuner() {
openTuner(new RadioTuner.Callback() {});
openTuner();
verify(mCallback, never()).onError(anyInt());
}
@Test
public void testReopenTuner() {
openTuner(new RadioTuner.Callback() {});
openTuner();
mRadioTuner.close();
mRadioTuner = null;
openTuner(new RadioTuner.Callback() {});
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);
verify(mCallback, times(1)).onConfigurationChanged(any());
verify(mCallback, never()).onError(anyInt());
// TODO(b/36863239): implement "get" too
}
}