OSDN Git Service

Implement get/setConfiguration calls of ITuner.
authorTomasz Wasilczyk <twasilczyk@google.com>
Mon, 1 May 2017 16:28:36 +0000 (09:28 -0700)
committerTomasz Wasilczyk <twasilczyk@google.com>
Tue, 2 May 2017 22:33:52 +0000 (15:33 -0700)
Test: instrumentation
Bug: b/36863239
Change-Id: I0954f8f837c342b35873d3ae834bab83bc3cb04c

core/java/android/hardware/radio/ITuner.aidl
core/java/android/hardware/radio/TunerAdapter.java
services/core/java/com/android/server/radio/Tuner.java
services/core/jni/com_android_server_radio_Tuner.cpp
services/core/jni/com_android_server_radio_convert.cpp
services/core/jni/com_android_server_radio_convert.h
services/core/jni/com_android_server_radio_types.h
tests/radio/src/android/hardware/radio/tests/RadioTest.java

index 68257ff..c08f41f 100644 (file)
@@ -22,5 +22,12 @@ import android.hardware.radio.RadioManager;
 interface ITuner {
     void close();
 
+    /**
+     * @throws IllegalArgumentException if config is not valid or null
+     */
+    void setConfiguration(in RadioManager.BandConfig config);
+
+    RadioManager.BandConfig getConfiguration();
+
     int getProgramInformation(out RadioManager.ProgramInfo[] infoOut);
 }
index 10bbf9f..1156fe8 100644 (file)
@@ -58,14 +58,28 @@ class TunerAdapter extends RadioTuner {
 
     @Override
     public int setConfiguration(RadioManager.BandConfig config) {
-        // TODO(b/36863239): forward to mTuner
-        throw new RuntimeException("Not implemented");
+        try {
+            mTuner.setConfiguration(config);
+            return RadioManager.STATUS_OK;
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Can't set configuration", e);
+            return RadioManager.STATUS_BAD_VALUE;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     @Override
     public int getConfiguration(RadioManager.BandConfig[] config) {
-        // TODO(b/36863239): forward to mTuner
-        throw new RuntimeException("Not implemented");
+        if (config == null || config.length != 1) {
+            throw new IllegalArgumentException("The argument must be an array of length 1");
+        }
+        try {
+            config[0] = mTuner.getConfiguration();
+            return RadioManager.STATUS_OK;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     @Override
index 248a139..a06d8c6 100644 (file)
@@ -31,7 +31,8 @@ class Tuner extends ITuner.Stub {
      */
     private final long mNativeContext;
 
-    private int mRegion;
+    private final Object mLock = new Object();
+    private int mRegion;  // TODO(b/36863239): find better solution to manage regions
 
     Tuner(@NonNull ITunerCallback clientCallback, int region) {
         mRegion = region;
@@ -44,17 +45,44 @@ class Tuner extends ITuner.Stub {
         super.finalize();
     }
 
-    private native long nativeInit(ITunerCallback clientCallback);
+    private native long nativeInit(@NonNull ITunerCallback clientCallback);
     private native void nativeFinalize(long nativeContext);
     private native void nativeClose(long nativeContext);
 
+    private native void nativeSetConfiguration(long nativeContext,
+            @NonNull RadioManager.BandConfig config);
+    private native RadioManager.BandConfig nativeGetConfiguration(long nativeContext, int region);
+
     @Override
     public void close() {
-        nativeClose(mNativeContext);
+        synchronized (mLock) {
+            nativeClose(mNativeContext);
+        }
+    }
+
+    @Override
+    public void setConfiguration(RadioManager.BandConfig config) {
+        if (config == null) {
+            throw new IllegalArgumentException("The argument must not be a null pointer");
+        }
+        synchronized (mLock) {
+            nativeSetConfiguration(mNativeContext, config);
+            mRegion = config.getRegion();
+        }
+    }
+
+    @Override
+    public RadioManager.BandConfig getConfiguration() {
+        synchronized (mLock) {
+            return nativeGetConfiguration(mNativeContext, mRegion);
+        }
     }
 
     @Override
     public int getProgramInformation(RadioManager.ProgramInfo[] infoOut) {
+        if (infoOut == null || infoOut.length != 1) {
+            throw new IllegalArgumentException("The argument must be an array of length 1");
+        }
         Slog.d(TAG, "getProgramInformation()");
         return RadioManager.STATUS_INVALID_OPERATION;
     }
index 3245bff..96e122d 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "com_android_server_radio_Tuner.h"
 
+#include "com_android_server_radio_convert.h"
 #include "com_android_server_radio_Tuner_TunerCallback.h"
 
 #include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
@@ -104,6 +105,13 @@ void setHalTuner(JNIEnv *env, jobject obj, sp<ITuner> halTuner) {
     ctx.mHalTuner = halTuner;
 }
 
+sp<ITuner> getHalTuner(jlong nativeContext) {
+    AutoMutex _l(gContextMutex);
+    auto tuner = getNativeContext(nativeContext).mHalTuner;
+    LOG_ALWAYS_FATAL_IF(tuner == nullptr, "HAL tuner not set");
+    return tuner;
+}
+
 sp<ITunerCallback> getNativeCallback(JNIEnv *env, jobject obj) {
     AutoMutex _l(gContextMutex);
     auto& ctx = getNativeContext(env, obj);
@@ -114,7 +122,7 @@ Region getRegion(JNIEnv *env, jobject obj) {
     return static_cast<Region>(env->GetIntField(obj, gjni.Tuner.region));
 }
 
-static void close(JNIEnv *env, jobject obj, jlong nativeContext) {
+static void nativeClose(JNIEnv *env, jobject obj, jlong nativeContext) {
     AutoMutex _l(gContextMutex);
     auto& ctx = getNativeContext(nativeContext);
     ALOGI("Closing tuner %p", ctx.mHalTuner.get());
@@ -123,10 +131,42 @@ static void close(JNIEnv *env, jobject obj, jlong nativeContext) {
     ctx.mNativeCallback = nullptr;
 }
 
+static void nativeSetConfiguration(JNIEnv *env, jobject obj, jlong nativeContext, jobject config) {
+    ALOGV("nativeSetConfiguration()");
+    auto halTuner = getHalTuner(nativeContext);
+
+    Region region_unused;
+    BandConfig bandConfigHal = convert::BandConfigToHal(env, config, region_unused);
+
+    convert::ThrowIfFailed(env, halTuner->setConfiguration(bandConfigHal));
+}
+
+static jobject nativeGetConfiguration(JNIEnv *env, jobject obj, jlong nativeContext,
+        Region region) {
+    ALOGV("nativeSetConfiguration()");
+    auto halTuner = getHalTuner(nativeContext);
+
+    BandConfig halConfig;
+    Result halResult;
+    auto hidlResult = halTuner->getConfiguration([&](Result result, const BandConfig& config) {
+        halResult = result;
+        halConfig = config;
+    });
+    if (convert::ThrowIfFailed(env, hidlResult)) {
+        return nullptr;
+    }
+
+    return convert::BandConfigFromHal(env, halConfig, region).release();
+}
+
 static const JNINativeMethod gTunerMethods[] = {
     { "nativeInit", "(Landroid/hardware/radio/ITunerCallback;)J", (void*)nativeInit },
     { "nativeFinalize", "(J)V", (void*)nativeFinalize },
-    { "nativeClose", "(J)V", (void*)close },
+    { "nativeClose", "(J)V", (void*)nativeClose },
+    { "nativeSetConfiguration", "(JLandroid/hardware/radio/RadioManager$BandConfig;)V",
+            (void*)nativeSetConfiguration },
+    { "nativeGetConfiguration", "(JI)Landroid/hardware/radio/RadioManager$BandConfig;",
+            (void*)nativeGetConfiguration },
 };
 
 } // namespace Tuner
index afa3539..419e6d6 100644 (file)
@@ -28,10 +28,12 @@ namespace server {
 namespace radio {
 namespace convert {
 
+using hardware::Return;
 using hardware::hidl_vec;
 
 using V1_0::Band;
 using V1_0::Deemphasis;
+using V1_0::Result;
 using V1_0::Rds;
 
 static struct {
@@ -62,6 +64,47 @@ static struct {
     } BandDescriptor;
 } gjni;
 
+template <typename T>
+bool ThrowIfFailedCommon(JNIEnv *env, const hardware::Return<T> &hidlResult) {
+    if (hidlResult.isOk()) return false;
+
+    jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+            "HIDL call failed: %s", hidlResult.description().c_str());
+    return true;
+}
+
+bool ThrowIfFailed(JNIEnv *env, const hardware::Return<void> &hidlResult) {
+    return ThrowIfFailedCommon(env, hidlResult);
+}
+
+bool ThrowIfFailed(JNIEnv *env, const hardware::Return<V1_0::Result> &hidlResult) {
+    if (ThrowIfFailedCommon(env, hidlResult)) return true;
+
+    Result result = hidlResult;
+    switch (result) {
+        case Result::OK:
+            return false;
+        case Result::NOT_INITIALIZED:
+            jniThrowException(env, "java/lang/RuntimeException", "Result::NOT_INITIALIZED");
+            return true;
+        case Result::INVALID_ARGUMENTS:
+            jniThrowException(env, "java/lang/IllegalArgumentException",
+                    "Result::INVALID_ARGUMENTS");
+            return true;
+        case Result::INVALID_STATE:
+            jniThrowException(env, "java/lang/IllegalStateException", "Result::INVALID_STATE");
+            return true;
+        case Result::TIMEOUT:
+            jniThrowException(env, "java/lang/RuntimeException",
+                    "Result::TIMEOUT (unexpected here)");
+            return true;
+        default:
+            jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                    "Unknown failure, result: %d", result);
+            return true;
+    }
+}
+
 static Rds RdsForRegion(bool rds, Region region) {
     if (!rds) return Rds::NONE;
 
@@ -95,6 +138,7 @@ static Deemphasis DeemphasisForRegion(Region region) {
 }
 
 JavaRef BandConfigFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region region) {
+    ALOGV("BandConfigFromHal()");
     EnvWrapper wrap(env);
 
     jint spacing = config.spacings.size() > 0 ? config.spacings[0] : 0;
@@ -122,6 +166,7 @@ JavaRef BandConfigFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region re
 }
 
 V1_0::BandConfig BandConfigToHal(JNIEnv *env, jobject jConfig, Region &region) {
+    ALOGV("BandConfigToHal()");
     auto jDescriptor = env->GetObjectField(jConfig, gjni.BandConfig.descriptor);
     if (jDescriptor == nullptr) {
         ALOGE("Descriptor is missing");
index b7e5b9c..dba948e 100644 (file)
@@ -37,6 +37,9 @@ namespace V1_1 = hardware::broadcastradio::V1_1;
 JavaRef BandConfigFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region region);
 V1_0::BandConfig BandConfigToHal(JNIEnv *env, jobject jConfig, Region &region);
 
+bool ThrowIfFailed(JNIEnv *env, const hardware::Return<V1_0::Result> &hidlResult);
+bool ThrowIfFailed(JNIEnv *env, const hardware::Return<void> &hidlResult);
+
 } // namespace convert
 } // namespace radio
 } // namespace server
index f2f253c..8430e05 100644 (file)
@@ -27,6 +27,18 @@ namespace radio {
  * frameworks/base/core/java/android/hardware/radio/RadioManager.java.
  */
 
+// Keep in sync with STATUS_* constants from RadioManager.java.
+enum class Status : jint {
+    OK = 0,
+    ERROR = -0x80000000ll,  // Integer.MIN_VALUE
+    PERMISSION_DENIED = -1,  // -EPERM
+    NO_INIT = -19,  // -ENODEV
+    BAD_VALUE = -22,  // -EINVAL
+    DEAD_OBJECT = -32,  // -EPIPE
+    INVALID_OPERATION = -38,  // -ENOSYS
+    TIMED_OUT = -110,  // -ETIMEDOUT
+};
+
 // Keep in sync with REGION_* constants from RadioManager.java.
 enum class Region : jint {
     ITU_1 = 0,
index e9c9b8c..7ab5618 100644 (file)
@@ -22,6 +22,7 @@ import android.hardware.radio.RadioTuner;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 
+import java.lang.reflect.Constructor;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -30,6 +31,7 @@ import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import static org.junit.Assert.*;
@@ -37,6 +39,7 @@ import static org.junit.Assume.*;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -48,6 +51,8 @@ public class RadioTest {
 
     public final Context mContext = InstrumentationRegistry.getContext();
 
+    private final int kConfigCallbacktimeoutNs = 10000;
+
     private RadioManager mRadioManager;
     private RadioTuner mRadioTuner;
     private final List<RadioManager.ModuleProperties> mModules = new ArrayList<>();
@@ -56,6 +61,9 @@ public class RadioTest {
     RadioManager.AmBandDescriptor mAmBandDescriptor;
     RadioManager.FmBandDescriptor mFmBandDescriptor;
 
+    RadioManager.BandConfig mAmBandConfig;
+    RadioManager.BandConfig mFmBandConfig;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -98,11 +106,14 @@ public class RadioTest {
         }
         assertNotNull(mAmBandDescriptor);
         assertNotNull(mFmBandDescriptor);
-        RadioManager.BandConfig fmBandConfig =
-            new RadioManager.FmBandConfig.Builder(mFmBandDescriptor).build();
+        mAmBandConfig = new RadioManager.AmBandConfig.Builder(mAmBandDescriptor).build();
+        mFmBandConfig = new RadioManager.FmBandConfig.Builder(mFmBandDescriptor).build();
 
-        mRadioTuner = mRadioManager.openTuner(module.getId(), fmBandConfig, true, mCallback, null);
+        mRadioTuner = mRadioManager.openTuner(module.getId(), mFmBandConfig, true, mCallback, null);
         assertNotNull(mRadioTuner);
+        verify(mCallback, timeout(kConfigCallbacktimeoutNs).times(1)).onConfigurationChanged(any());
+        verify(mCallback, never()).onError(anyInt());
+        Mockito.reset(mCallback);
     }
 
     @Test
@@ -112,26 +123,59 @@ public class RadioTest {
     }
 
     @Test
-    public void testReopenTuner() {
+    public void testReopenTuner() throws Throwable {
         openTuner();
         mRadioTuner.close();
         mRadioTuner = null;
+        Thread.sleep(100);  // TODO(b/36122635): force reopen
         openTuner();
         verify(mCallback, never()).onError(anyInt());
     }
 
     @Test
-    @org.junit.Ignore("setConfiguration is not implemented yet")
     public void testSetAndGetConfiguration() {
         openTuner();
 
-        RadioManager.BandConfig amBandConfig =
-            new RadioManager.AmBandConfig.Builder(mAmBandDescriptor).build();
-        mRadioTuner.setConfiguration(amBandConfig);
+        // set
+        int ret = mRadioTuner.setConfiguration(mAmBandConfig);
+        assertEquals(RadioManager.STATUS_OK, ret);
+        verify(mCallback, timeout(kConfigCallbacktimeoutNs).times(1)).onConfigurationChanged(any());
+
+        // get
+        RadioManager.BandConfig[] config = new RadioManager.BandConfig[1];
+        ret = mRadioTuner.getConfiguration(config);
+        assertEquals(RadioManager.STATUS_OK, ret);
 
-        verify(mCallback, times(1)).onConfigurationChanged(any());
         verify(mCallback, never()).onError(anyInt());
+        assertEquals(mAmBandConfig, config[0]);
+    }
 
-        // TODO(b/36863239): implement "get" too
+    @Test
+    public void testSetBadConfiguration() throws Throwable {
+        openTuner();
+
+        // set bad config
+        Constructor<RadioManager.AmBandConfig> configConstr =
+                RadioManager.AmBandConfig.class.getDeclaredConstructor(
+                        int.class, int.class, int.class, int.class, int.class, boolean.class);
+        configConstr.setAccessible(true);
+        RadioManager.AmBandConfig badConfig = configConstr.newInstance(
+                0 /*region*/, RadioManager.BAND_AM /*type*/,
+                10000 /*lowerLimit*/, 1 /*upperLimit*/, 100 /*spacing*/, false /*stereo*/);
+        int ret = mRadioTuner.setConfiguration(badConfig);
+        assertEquals(RadioManager.STATUS_BAD_VALUE, ret);
+        verify(mCallback, never()).onConfigurationChanged(any());
+
+        // set null config
+        ret = mRadioTuner.setConfiguration(null);
+        assertEquals(RadioManager.STATUS_BAD_VALUE, ret);
+        verify(mCallback, never()).onConfigurationChanged(any());
+
+        // setting good config should recover
+        ret = mRadioTuner.setConfiguration(mAmBandConfig);
+        assertEquals(RadioManager.STATUS_OK, ret);
+        verify(mCallback, timeout(kConfigCallbacktimeoutNs).times(1)).onConfigurationChanged(any());
+
+        verify(mCallback, never()).onError(anyInt());
     }
 }