diff options
11 files changed, 262 insertions, 6 deletions
diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl index 3aaeb5061de3..18287fae1b9b 100644 --- a/core/java/android/hardware/radio/ITuner.aidl +++ b/core/java/android/hardware/radio/ITuner.aidl @@ -94,5 +94,17 @@ interface ITuner { */ void setAnalogForced(boolean isForced); + /** + * @param parameters Vendor-specific key-value pairs, must be Map<String, String> + * @return Vendor-specific key-value pairs, must be Map<String, String> + */ + Map setParameters(in Map parameters); + + /** + * @param keys Parameter keys to fetch + * @return Vendor-specific key-value pairs, must be Map<String, String> + */ + Map getParameters(in List<String> keys); + boolean isAntennaConnected(); } diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl index 6ed171bbb8a9..775e25c7e7cf 100644 --- a/core/java/android/hardware/radio/ITunerCallback.aidl +++ b/core/java/android/hardware/radio/ITunerCallback.aidl @@ -30,4 +30,9 @@ oneway interface ITunerCallback { void onBackgroundScanAvailabilityChange(boolean isAvailable); void onBackgroundScanComplete(); void onProgramListChanged(); + + /** + * @param parameters Vendor-specific key-value pairs, must be Map<String, String> + */ + void onParametersUpdated(in Map parameters); } diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java index 6e8991aa4a4a..e93fd5f1b86b 100644 --- a/core/java/android/hardware/radio/RadioTuner.java +++ b/core/java/android/hardware/radio/RadioTuner.java @@ -309,6 +309,58 @@ public abstract class RadioTuner { public abstract void setAnalogForced(boolean isForced); /** + * Generic method for setting vendor-specific parameter values. + * The framework does not interpret the parameters, they are passed + * in an opaque manner between a vendor application and HAL. + * + * Framework does not make any assumptions on the keys or values, other than + * ones stated in VendorKeyValue documentation (a requirement of key + * prefixes). + * + * For each pair in the result map, the key will be one of the keys + * contained in the input (possibly with wildcards expanded), and the value + * will be a vendor-specific result status (such as "OK" or an error code). + * The implementation may choose to return an empty map, or only return + * a status for a subset of the provided inputs, at its discretion. + * + * Application and HAL must not use keys with unknown prefix. In particular, + * it must not place a key-value pair in results vector for unknown key from + * parameters vector - instead, an unknown key should simply be ignored. + * In other words, results vector may contain a subset of parameter keys + * (however, the framework doesn't enforce a strict subset - the only + * formal requirement is vendor domain prefix for keys). + * + * @param parameters Vendor-specific key-value pairs. + * @return Operation completion status for parameters being set. + * @hide FutureFeature + */ + public abstract @NonNull Map<String, String> + setParameters(@NonNull Map<String, String> parameters); + + /** + * Generic method for retrieving vendor-specific parameter values. + * The framework does not interpret the parameters, they are passed + * in an opaque manner between a vendor application and HAL. + * + * Framework does not cache set/get requests, so it's possible for + * getParameter to return a different value than previous setParameter call. + * + * The syntax and semantics of keys are up to the vendor (as long as prefix + * rules are obeyed). For instance, vendors may include some form of + * wildcard support. In such case, result vector may be of different size + * than requested keys vector. However, wildcards are not recognized by + * framework and they are passed as-is to the HAL implementation. + * + * Unknown keys must be ignored and not placed into results vector. + * + * @param keys Parameter keys to fetch. + * @return Vendor-specific key-value pairs. + * @hide FutureFeature + */ + public abstract @NonNull Map<String, String> + getParameters(@NonNull List<String> keys); + + /** * Get current antenna connection state for current configuration. * Only valid if a configuration has been applied. * @return {@code true} if the antenna is connected, {@code false} otherwise. @@ -429,6 +481,22 @@ public abstract class RadioTuner { * Use {@link RadioTuner#getProgramList(String)} to get an actual list. */ public void onProgramListChanged() {} + + /** + * Generic callback for passing updates to vendor-specific parameter values. + * The framework does not interpret the parameters, they are passed + * in an opaque manner between a vendor application and HAL. + * + * It's up to the HAL implementation if and how to implement this callback, + * as long as it obeys the prefix rule. In particular, only selected keys + * may be notified this way. However, setParameters must not trigger + * this callback, while an internal event can change parameters + * asynchronously. + * + * @param parameters Vendor-specific key-value pairs. + * @hide FutureFeature + */ + public void onParametersUpdated(@NonNull Map<String, String> parameters) {} } } diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java index b62196902570..864d17c2de9f 100644 --- a/core/java/android/hardware/radio/TunerAdapter.java +++ b/core/java/android/hardware/radio/TunerAdapter.java @@ -24,6 +24,7 @@ import android.util.Log; import java.util.List; import java.util.Map; +import java.util.Objects; /** * Implements the RadioTuner interface by forwarding calls to radio service. @@ -251,6 +252,24 @@ class TunerAdapter extends RadioTuner { } @Override + public @NonNull Map<String, String> setParameters(@NonNull Map<String, String> parameters) { + try { + return mTuner.setParameters(Objects.requireNonNull(parameters)); + } catch (RemoteException e) { + throw new RuntimeException("service died", e); + } + } + + @Override + public @NonNull Map<String, String> getParameters(@NonNull List<String> keys) { + try { + return mTuner.getParameters(Objects.requireNonNull(keys)); + } catch (RemoteException e) { + throw new RuntimeException("service died", e); + } + } + + @Override public boolean isAntennaConnected() { try { return mTuner.isAntennaConnected(); diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java index ffd5b30fa15c..a01f658e80f6 100644 --- a/core/java/android/hardware/radio/TunerCallbackAdapter.java +++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java @@ -22,6 +22,8 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; +import java.util.Map; + /** * Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback. */ @@ -94,4 +96,9 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { public void onProgramListChanged() { mHandler.post(() -> mCallback.onProgramListChanged()); } + + @Override + public void onParametersUpdated(Map parameters) { + mHandler.post(() -> mCallback.onParametersUpdated(parameters)); + } } diff --git a/services/core/java/com/android/server/broadcastradio/Tuner.java b/services/core/java/com/android/server/broadcastradio/Tuner.java index e6ae320cf38d..2ea4271864f2 100644 --- a/services/core/java/com/android/server/broadcastradio/Tuner.java +++ b/services/core/java/com/android/server/broadcastradio/Tuner.java @@ -27,8 +27,10 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; class Tuner extends ITuner.Stub { private static final String TAG = "BroadcastRadioService.Tuner"; @@ -96,6 +98,10 @@ class Tuner extends ITuner.Stub { private native boolean nativeIsAnalogForced(long nativeContext); private native void nativeSetAnalogForced(long nativeContext, boolean isForced); + private native Map<String, String> nativeSetParameters(long nativeContext, + Map<String, String> parameters); + private native Map<String, String> nativeGetParameters(long nativeContext, List<String> keys); + private native boolean nativeIsAntennaConnected(long nativeContext); @Override @@ -273,6 +279,31 @@ class Tuner extends ITuner.Stub { } @Override + public Map setParameters(Map parameters) { + Map<String, String> results; + synchronized (mLock) { + checkNotClosedLocked(); + results = nativeSetParameters(mNativeContext, Objects.requireNonNull(parameters)); + } + if (results == null) return Collections.emptyMap(); + return results; + } + + @Override + public Map getParameters(List<String> keys) { + if (keys == null) { + throw new IllegalArgumentException("The argument must not be a null pointer"); + } + Map<String, String> results; + synchronized (mLock) { + checkNotClosedLocked(); + results = nativeGetParameters(mNativeContext, keys); + } + if (results == null) return Collections.emptyMap(); + return results; + } + + @Override public boolean isAntennaConnected() { synchronized (mLock) { checkNotClosedLocked(); diff --git a/services/core/java/com/android/server/broadcastradio/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/TunerCallback.java index a87ae8d65bf8..2460c67a64a2 100644 --- a/services/core/java/com/android/server/broadcastradio/TunerCallback.java +++ b/services/core/java/com/android/server/broadcastradio/TunerCallback.java @@ -26,6 +26,9 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import java.util.List; +import java.util.Map; + class TunerCallback implements ITunerCallback { private static final String TAG = "BroadcastRadioService.TunerCallback"; @@ -121,6 +124,11 @@ class TunerCallback implements ITunerCallback { } @Override + public void onParametersUpdated(Map parameters) { + dispatch(() -> mClientCallback.onParametersUpdated(parameters)); + } + + @Override public IBinder asBinder() { throw new RuntimeException("Not a binder"); } diff --git a/services/core/jni/BroadcastRadio/Tuner.cpp b/services/core/jni/BroadcastRadio/Tuner.cpp index 6403a5a0f35a..df53feeaf8fa 100644 --- a/services/core/jni/BroadcastRadio/Tuner.cpp +++ b/services/core/jni/BroadcastRadio/Tuner.cpp @@ -51,8 +51,9 @@ using V1_0::Band; using V1_0::BandConfig; using V1_0::MetaData; using V1_0::Result; -using V1_2::ITunerCallback; using V1_1::ProgramListResult; +using V1_1::VendorKeyValue; +using V1_2::ITunerCallback; using utils::HalRevision; static mutex gContextMutex; @@ -93,6 +94,7 @@ struct TunerContext { wp<V1_1::IBroadcastRadio> mHalModule11; sp<V1_0::ITuner> mHalTuner; sp<V1_1::ITuner> mHalTuner11; + sp<V1_2::ITuner> mHalTuner12; sp<HalDeathRecipient> mHalDeathRecipient; private: @@ -179,8 +181,11 @@ void assignHalInterfaces(JNIEnv *env, JavaRef<jobject> const &jTuner, ctx.mHalTuner = halTuner; ctx.mHalTuner11 = V1_1::ITuner::castFrom(halTuner).withDefault(nullptr); + ctx.mHalTuner12 = V1_2::ITuner::castFrom(halTuner).withDefault(nullptr); ALOGW_IF(ctx.mHalRev >= HalRevision::V1_1 && ctx.mHalTuner11 == nullptr, "Provided tuner does not implement 1.1 HAL"); + ALOGW_IF(ctx.mHalRev >= HalRevision::V1_2 && ctx.mHalTuner12 == nullptr, + "Provided tuner does not implement 1.2 HAL"); ctx.mHalDeathRecipient = new HalDeathRecipient(getNativeCallback(env, jTuner)); halTuner->linkToDeath(ctx.mHalDeathRecipient, 0); @@ -194,16 +199,21 @@ static sp<V1_0::ITuner> getHalTuner(const TunerContext& ctx) { return tuner; } -sp<V1_0::ITuner> getHalTuner(jlong nativeContext) { +static sp<V1_0::ITuner> getHalTuner(jlong nativeContext) { lock_guard<mutex> lk(gContextMutex); return getHalTuner(getNativeContext(nativeContext)); } -sp<V1_1::ITuner> getHalTuner11(jlong nativeContext) { +static sp<V1_1::ITuner> getHalTuner11(jlong nativeContext) { lock_guard<mutex> lk(gContextMutex); return getNativeContext(nativeContext).mHalTuner11; } +static sp<V1_2::ITuner> getHalTuner12(jlong nativeContext) { + lock_guard<mutex> lk(gContextMutex); + return getNativeContext(nativeContext).mHalTuner12; +} + sp<ITunerCallback> getNativeCallback(JNIEnv *env, JavaRef<jobject> const &tuner) { return TunerCallback::getNativeCallback(env, env->GetObjectField(tuner.get(), gjni.Tuner.tunerCallback)); @@ -233,6 +243,7 @@ static void nativeClose(JNIEnv *env, jobject obj, jlong nativeContext) { ctx.mHalDeathRecipient = nullptr; ctx.mHalTuner11 = nullptr; + ctx.mHalTuner12 = nullptr; ctx.mHalTuner = nullptr; } @@ -488,6 +499,48 @@ static void nativeSetAnalogForced(JNIEnv *env, jobject obj, jlong nativeContext, convert::ThrowIfFailed(env, halResult); } +static jobject nativeSetParameters(JNIEnv *env, jobject obj, jlong nativeContext, jobject jParameters) { + ALOGV("%s", __func__); + + auto halTuner = getHalTuner12(nativeContext); + if (halTuner == nullptr) { + ALOGI("Parameters are not supported with HAL < 1.2"); + return nullptr; + } + + JavaRef<jobject> jResults = nullptr; + auto parameters = convert::VendorInfoToHal(env, jParameters); + auto hidlResult = halTuner->setParameters(parameters, + [&](const hidl_vec<VendorKeyValue> results) { + jResults = convert::VendorInfoFromHal(env, results); + }); + + if (convert::ThrowIfFailed(env, hidlResult)) return nullptr; + + return jResults.release(); +} + +static jobject nativeGetParameters(JNIEnv *env, jobject obj, jlong nativeContext, jobject jKeys) { + ALOGV("%s", __func__); + + auto halTuner = getHalTuner12(nativeContext); + if (halTuner == nullptr) { + ALOGI("Parameters are not supported with HAL < 1.2"); + return nullptr; + } + + JavaRef<jobject> jResults = nullptr; + auto keys = convert::StringListToHal(env, jKeys); + auto hidlResult = halTuner->getParameters(keys, + [&](const hidl_vec<VendorKeyValue> parameters) { + jResults = convert::VendorInfoFromHal(env, parameters); + }); + + if (convert::ThrowIfFailed(env, hidlResult)) return nullptr; + + return jResults.release(); +} + static bool nativeIsAntennaConnected(JNIEnv *env, jobject obj, jlong nativeContext) { ALOGV("%s", __func__); auto halTuner = getHalTuner(nativeContext); @@ -525,6 +578,8 @@ static const JNINativeMethod gTunerMethods[] = { { "nativeGetImage", "(JI)[B", (void*)nativeGetImage}, { "nativeIsAnalogForced", "(J)Z", (void*)nativeIsAnalogForced }, { "nativeSetAnalogForced", "(JZ)V", (void*)nativeSetAnalogForced }, + { "nativeSetParameters", "(JLjava/util/Map;)Ljava/util/Map;", (void*)nativeSetParameters }, + { "nativeGetParameters", "(JLjava/util/List;)Ljava/util/Map;", (void*)nativeGetParameters }, { "nativeIsAntennaConnected", "(J)Z", (void*)nativeIsAntennaConnected }, }; diff --git a/services/core/jni/BroadcastRadio/TunerCallback.cpp b/services/core/jni/BroadcastRadio/TunerCallback.cpp index ed7c9c4749c8..d624df69a7ff 100644 --- a/services/core/jni/BroadcastRadio/TunerCallback.cpp +++ b/services/core/jni/BroadcastRadio/TunerCallback.cpp @@ -70,6 +70,7 @@ static struct { jmethodID onBackgroundScanAvailabilityChange; jmethodID onBackgroundScanComplete; jmethodID onProgramListChanged; + jmethodID onParametersUpdated; } TunerCallback; } gjni; @@ -346,7 +347,10 @@ Return<void> NativeCallback::currentProgramInfoChanged(const ProgramInfo& info) Return<void> NativeCallback::parametersUpdated(const hidl_vec<VendorKeyValue>& parameters) { ALOGV("%s", __func__); - // TODO(b/65862441): pass this callback to the front-end + mCallbackThread.enqueue([this, parameters](JNIEnv *env) { + auto jParameters = convert::VendorInfoFromHal(env, parameters); + env->CallVoidMethod(mJCallback, gjni.TunerCallback.onParametersUpdated, jParameters.get()); + }); return {}; } @@ -437,6 +441,8 @@ void register_android_server_broadcastradio_TunerCallback(JavaVM *vm, JNIEnv *en "onBackgroundScanComplete", "()V"); gjni.TunerCallback.onProgramListChanged = GetMethodIDOrDie(env, tunerCbClass, "onProgramListChanged", "()V"); + gjni.TunerCallback.onParametersUpdated = GetMethodIDOrDie(env, tunerCbClass, + "onParametersUpdated", "(Ljava/util/Map;)V"); auto res = jniRegisterNativeMethods(env, "com/android/server/broadcastradio/TunerCallback", gTunerCallbackMethods, NELEM(gTunerCallbackMethods)); diff --git a/services/core/jni/BroadcastRadio/convert.cpp b/services/core/jni/BroadcastRadio/convert.cpp index 8dfa14f126f7..734ce793e833 100644 --- a/services/core/jni/BroadcastRadio/convert.cpp +++ b/services/core/jni/BroadcastRadio/convert.cpp @@ -34,6 +34,7 @@ namespace convert { namespace utils = hardware::broadcastradio::utils; using hardware::Return; +using hardware::hidl_string; using hardware::hidl_vec; using regions::RegionalBandConfig; @@ -98,6 +99,11 @@ static struct { } HashMap; struct { + jmethodID get; + jmethodID size; + } List; + + struct { jmethodID put; } Map; @@ -145,8 +151,21 @@ static struct { jclass clazz; jmethodID cstor; } ParcelableException; + + struct { + jclass clazz; + } String; } gjni; +static jstring CastToString(JNIEnv *env, jobject obj) { + if (env->IsInstanceOf(obj, gjni.String.clazz)) { + return static_cast<jstring>(obj); + } else { + ALOGE("Cast failed, object is not a string"); + return nullptr; + } +} + template <> bool ThrowIfFailed(JNIEnv *env, const hardware::Return<void> &hidlResult) { return __ThrowIfFailedHidl(env, hidlResult); @@ -250,12 +269,26 @@ static JavaRef<jobjectArray> ArrayFromHal(JNIEnv *env, const hidl_vec<T>& vec, } static std::string StringFromJava(JNIEnv *env, JavaRef<jstring> &jStr) { - auto cstr = (jStr == nullptr) ? nullptr : env->GetStringUTFChars(jStr.get(), nullptr); + if (jStr == nullptr) return {}; + auto cstr = env->GetStringUTFChars(jStr.get(), nullptr); std::string str(cstr); env->ReleaseStringUTFChars(jStr.get(), cstr); return str; } +hidl_vec<hidl_string> StringListToHal(JNIEnv *env, jobject jList) { + auto len = (jList == nullptr) ? 0 : env->CallIntMethod(jList, gjni.List.size); + hidl_vec<hidl_string> list(len); + + for (decltype(len) i = 0; i < len; i++) { + auto jString = make_javaref(env, CastToString(env, env->CallObjectMethod( + jList, gjni.List.get, i))); + list[i] = StringFromJava(env, jString); + } + + return list; +} + JavaRef<jobject> VendorInfoFromHal(JNIEnv *env, const hidl_vec<VendorKeyValue> &info) { ALOGV("%s(%s)", __func__, toString(info).substr(0, 100).c_str()); @@ -275,7 +308,10 @@ hidl_vec<VendorKeyValue> VendorInfoToHal(JNIEnv *env, jobject jInfo) { auto jInfoArr = make_javaref(env, static_cast<jobjectArray>(env->CallStaticObjectMethod( gjni.Convert.clazz, gjni.Convert.stringMapToNative, jInfo))); - LOG_FATAL_IF(jInfoArr == nullptr, "Converted array is null"); + if (jInfoArr == nullptr) { + ALOGE("Converted array is null"); + return {}; + } auto len = env->GetArrayLength(jInfoArr.get()); hidl_vec<VendorKeyValue> vec; @@ -651,6 +687,10 @@ void register_android_server_broadcastradio_convert(JNIEnv *env) { gjni.HashMap.clazz = MakeGlobalRefOrDie(env, hashMapClass); gjni.HashMap.cstor = GetMethodIDOrDie(env, hashMapClass, "<init>", "()V"); + auto listClass = FindClassOrDie(env, "java/util/List"); + gjni.List.get = GetMethodIDOrDie(env, listClass, "get", "(I)Ljava/lang/Object;"); + gjni.List.size = GetMethodIDOrDie(env, listClass, "size", "()I"); + auto mapClass = FindClassOrDie(env, "java/util/Map"); gjni.Map.put = GetMethodIDOrDie(env, mapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); @@ -713,6 +753,9 @@ void register_android_server_broadcastradio_convert(JNIEnv *env) { gjni.ParcelableException.clazz = MakeGlobalRefOrDie(env, parcelableExcClass); gjni.ParcelableException.cstor = GetMethodIDOrDie(env, parcelableExcClass, "<init>", "(Ljava/lang/Throwable;)V"); + + auto stringClass = FindClassOrDie(env, "java/lang/String"); + gjni.String.clazz = MakeGlobalRefOrDie(env, stringClass); } } // namespace android diff --git a/services/core/jni/BroadcastRadio/convert.h b/services/core/jni/BroadcastRadio/convert.h index 1fc75f06f38d..b8c55c1f0c62 100644 --- a/services/core/jni/BroadcastRadio/convert.h +++ b/services/core/jni/BroadcastRadio/convert.h @@ -35,6 +35,8 @@ namespace convert { namespace V1_0 = hardware::broadcastradio::V1_0; namespace V1_1 = hardware::broadcastradio::V1_1; +hardware::hidl_vec<hardware::hidl_string> StringListToHal(JNIEnv *env, jobject jList); + JavaRef<jobject> VendorInfoFromHal(JNIEnv *env, const hardware::hidl_vec<V1_1::VendorKeyValue> &info); hardware::hidl_vec<V1_1::VendorKeyValue> VendorInfoToHal(JNIEnv *env, jobject jInfo); |