Merge "Pass radio HAL callbacks back to Java client."
This commit is contained in:
committed by
Android (Google) Code Review
commit
22eb0e316d
@@ -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 \
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
25
core/java/android/hardware/radio/ITunerCallback.aidl
Normal file
25
core/java/android/hardware/radio/ITunerCallback.aidl
Normal 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);
|
||||
}
|
||||
@@ -18,3 +18,6 @@ package android.hardware.radio;
|
||||
|
||||
/** @hide */
|
||||
parcelable RadioManager.ProgramInfo;
|
||||
|
||||
/** @hide */
|
||||
parcelable RadioManager.BandConfig;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
56
core/java/android/hardware/radio/TunerCallbackAdapter.java
Normal file
56
core/java/android/hardware/radio/TunerCallbackAdapter.java
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 \
|
||||
|
||||
116
services/core/jni/NativeCallbackThread.cpp
Normal file
116
services/core/jni/NativeCallbackThread.cpp
Normal 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
|
||||
56
services/core/jni/NativeCallbackThread.h
Normal file
56
services/core/jni/NativeCallbackThread.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#ifndef _ANDROID_SERVER_RADIO_RADIOSERVICE_H
|
||||
#define _ANDROID_SERVER_RADIO_RADIOSERVICE_H
|
||||
|
||||
#include "jni.h"
|
||||
#include <jni.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user