Initial implementation of Java-based broadcast radio service.
It provides only limited amount of service, without actual interaction
with HAL.
Added config.enable_java_radio switch to use Java-based service instead
of native. Added FEATURE_RADIO to PackageManager.
Bug: b/36863239
Test: Instrumentation, manual (Kitchen Sink)
Change-Id: I01139d326893c0a437c60cc35d6e5b005da35231
diff --git a/Android.mk b/Android.mk
index cf4a7aa..d3415b3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -216,6 +216,8 @@
core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \
core/java/android/hardware/location/IContextHubCallback.aidl \
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/soundtrigger/IRecognitionStatusCallback.aidl \
core/java/android/hardware/usb/IUsbManager.aidl \
core/java/android/net/ICaptivePortal.aidl \
@@ -674,6 +676,7 @@
frameworks/base/core/java/android/print/PrinterInfo.aidl \
frameworks/base/core/java/android/print/PrintJobId.aidl \
frameworks/base/core/java/android/printservice/recommendation/RecommendationInfo.aidl \
+ frameworks/base/core/java/android/hardware/radio/RadioManager.aidl \
frameworks/base/core/java/android/hardware/usb/UsbDevice.aidl \
frameworks/base/core/java/android/hardware/usb/UsbInterface.aidl \
frameworks/base/core/java/android/hardware/usb/UsbEndpoint.aidl \
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index c2c40df..abc50ae 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -784,7 +784,7 @@
registerService(Context.RADIO_SERVICE, RadioManager.class,
new CachedServiceFetcher<RadioManager>() {
@Override
- public RadioManager createService(ContextImpl ctx) {
+ public RadioManager createService(ContextImpl ctx) throws ServiceNotFoundException {
return new RadioManager(ctx);
}});
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4f0ff61..7ba2e96 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1829,6 +1829,14 @@
public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
/**
+ * The device includes broadcast radio tuner.
+ *
+ * @hide FutureFeature
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_RADIO = "android.hardware.radio";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device includes an accelerometer.
*/
diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl
new file mode 100644
index 0000000..3c83114
--- /dev/null
+++ b/core/java/android/hardware/radio/IRadioService.aidl
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.hardware.radio.ITuner;
+
+/**
+ * API to the broadcast radio service.
+ *
+ * {@hide}
+ */
+interface IRadioService {
+ ITuner openTuner();
+}
diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl
new file mode 100644
index 0000000..73f6dc2
--- /dev/null
+++ b/core/java/android/hardware/radio/ITuner.aidl
@@ -0,0 +1,24 @@
+/**
+ * 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} */
+interface ITuner {
+ int getProgramInformation(out RadioManager.ProgramInfo[] infoOut);
+}
diff --git a/core/java/android/hardware/radio/RadioManager.aidl b/core/java/android/hardware/radio/RadioManager.aidl
new file mode 100644
index 0000000..d79ae4f
--- /dev/null
+++ b/core/java/android/hardware/radio/RadioManager.aidl
@@ -0,0 +1,20 @@
+/**
+ * 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;
+
+/** @hide */
+parcelable RadioManager.ProgramInfo;
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index ac2bd95..99412de 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -17,12 +17,19 @@
package android.hardware.radio;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.SystemProperties;
import android.text.TextUtils;
+import android.util.Log;
+
import java.util.List;
import java.util.Arrays;
@@ -35,6 +42,7 @@
*/
@SystemApi
public class RadioManager {
+ private static final String TAG = "RadioManager";
/** Method return status: successful operation */
public static final int STATUS_OK = 0;
@@ -1434,23 +1442,40 @@
public RadioTuner openTuner(int moduleId, BandConfig config, boolean withAudio,
RadioTuner.Callback callback, Handler handler) {
if (callback == null) {
- return null;
+ throw new IllegalArgumentException("callback must not be empty");
}
- RadioModule module = new RadioModule(moduleId, config, withAudio, callback, handler);
- if (module != null) {
- if (!module.initCheck()) {
- module = null;
+
+ if (mService != null) {
+ ITuner tuner;
+ try {
+ tuner = mService.openTuner();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
+ return new TunerAdapter(tuner);
}
+
+ RadioModule module = new RadioModule(moduleId, config, withAudio, callback, handler);
+ if (!module.initCheck()) {
+ Log.e(TAG, "Failed to open tuner");
+ module = null;
+ }
+
return (RadioTuner)module;
}
- private final Context mContext;
+ @NonNull private final Context mContext;
+ // TODO(b/36863239): NonNull when transitioned from native service
+ @Nullable private final IRadioService mService;
/**
* @hide
*/
- public RadioManager(Context context) {
+ public RadioManager(@NonNull Context context) throws ServiceNotFoundException {
mContext = context;
+
+ boolean isServiceJava = SystemProperties.getBoolean("config.enable_java_radio", false);
+ mService = isServiceJava ? IRadioService.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.RADIO_SERVICE)) : null;
}
}
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
new file mode 100644
index 0000000..1822e07b
--- /dev/null
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -0,0 +1,143 @@
+/**
+ * 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.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implements the RadioTuner interface by forwarding calls to radio service.
+ */
+class TunerAdapter extends RadioTuner {
+ private static final String TAG = "radio.TunerAdapter";
+
+ @NonNull private final ITuner mTuner;
+
+ TunerAdapter(ITuner tuner) {
+ if (tuner == null) {
+ throw new NullPointerException();
+ }
+ mTuner = tuner;
+ }
+
+ @Override
+ public void close() {
+ // TODO(b/36863239): forward to mTuner
+ Log.w(TAG, "Close call not implemented");
+ }
+
+ @Override
+ public int setConfiguration(RadioManager.BandConfig config) {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int getConfiguration(RadioManager.BandConfig[] config) {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int setMute(boolean mute) {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean getMute() {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int step(int direction, boolean skipSubChannel) {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int scan(int direction, boolean skipSubChannel) {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int tune(int channel, int subChannel) {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int cancel() {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int getProgramInformation(RadioManager.ProgramInfo[] info) {
+ if (info == null || info.length != 1) {
+ throw new IllegalArgumentException("The argument must be an array of length 1");
+ }
+ try {
+ return mTuner.getProgramInformation(info);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public boolean startBackgroundScan() {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public @NonNull List<RadioManager.ProgramInfo> getProgramList(@Nullable String filter) {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean isAnalogForced() {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void setAnalogForced(boolean isForced) {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean isAntennaConnected() {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean hasControl() {
+ // TODO(b/36863239): forward to mTuner
+ throw new RuntimeException("Not implemented");
+ }
+}
diff --git a/services/core/java/com/android/server/radio/RadioService.java b/services/core/java/com/android/server/radio/RadioService.java
new file mode 100644
index 0000000..327e98f
--- /dev/null
+++ b/services/core/java/com/android/server/radio/RadioService.java
@@ -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 com.android.server.radio;
+
+import android.content.Context;
+import android.hardware.radio.IRadioService;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.RadioManager;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+public class RadioService extends SystemService {
+ // TODO(b/36863239): rename to RadioService when native service goes away
+ private static final String TAG = "RadioServiceJava";
+
+ public RadioService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.RADIO_SERVICE, new RadioServiceImpl());
+ Slog.v(TAG, "RadioService started");
+ }
+
+ private static class RadioServiceImpl extends IRadioService.Stub {
+ @Override
+ public ITuner openTuner() {
+ Slog.d(TAG, "openTuner()");
+ return new TunerImpl();
+ }
+ }
+
+ private static class TunerImpl extends ITuner.Stub {
+ @Override
+ public int getProgramInformation(RadioManager.ProgramInfo[] infoOut) {
+ Slog.d(TAG, "getProgramInformation()");
+ return RadioManager.STATUS_INVALID_OPERATION;
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f74512a..ad25ce0 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -96,6 +96,7 @@
import com.android.server.policy.PhoneWindowManager;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
+import com.android.server.radio.RadioService;
import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.retaildemo.RetailDemoModeService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
@@ -711,6 +712,8 @@
boolean disableVrManager = SystemProperties.getBoolean("config.disable_vrmanager", false);
boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
false);
+ // TODO(b/36863239): Remove when transitioned from native service.
+ boolean enableRadioService = SystemProperties.getBoolean("config.enable_java_radio", false);
boolean isEmulator = SystemProperties.get("ro.kernel.qemu").equals("1");
@@ -1211,6 +1214,13 @@
mSystemServiceManager.startService(AudioService.Lifecycle.class);
traceEnd();
+ if (enableRadioService &&
+ mPackageManager.hasSystemFeature(PackageManager.FEATURE_RADIO)) {
+ traceBeginAndSlog("StartRadioService");
+ mSystemServiceManager.startService(RadioService.class);
+ traceEnd();
+ }
+
if (!disableNonCoreServices) {
traceBeginAndSlog("StartDockObserver");
mSystemServiceManager.startService(DockObserver.class);
diff --git a/tests/radio/Android.mk b/tests/radio/Android.mk
new file mode 100644
index 0000000..46bf9cc
--- /dev/null
+++ b/tests/radio/Android.mk
@@ -0,0 +1,33 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := RadioTests
+
+LOCAL_MODULE_TAGS := tests
+# TODO(b/13282254): uncomment when b/13282254 is fixed
+# LOCAL_SDK_VERSION := current
+
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util android-support-test
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
diff --git a/tests/radio/AndroidManifest.xml b/tests/radio/AndroidManifest.xml
new file mode 100644
index 0000000..150edbf
--- /dev/null
+++ b/tests/radio/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.hardware.radio.tests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.hardware.radio.tests"
+ android:label="Tests for broadcast radio API" >
+ </instrumentation>
+</manifest>
diff --git a/tests/radio/src/android/hardware/radio/tests/RadioTest.java b/tests/radio/src/android/hardware/radio/tests/RadioTest.java
new file mode 100644
index 0000000..47fbe74
--- /dev/null
+++ b/tests/radio/src/android/hardware/radio/tests/RadioTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.tests;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioTuner;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+
+/**
+ * A test for broadcast radio API.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RadioTest {
+
+ public final Context mContext = InstrumentationRegistry.getContext();
+
+ private RadioManager mRadioManager;
+ private RadioTuner mRadioTuner;
+ private final List<RadioManager.ModuleProperties> mModules = new ArrayList<>();
+
+ @Before
+ public void setup() {
+ // check if radio is supported and skip the test if it's not
+ PackageManager packageManager = mContext.getPackageManager();
+ boolean isRadioSupported = packageManager.hasSystemFeature(PackageManager.FEATURE_RADIO);
+ assumeTrue(isRadioSupported);
+
+ mRadioManager = (RadioManager)mContext.getSystemService(Context.RADIO_SERVICE);
+ assertNotNull(mRadioManager);
+
+ int status = mRadioManager.listModules(mModules);
+ assertEquals(RadioManager.STATUS_OK, status);
+ assertFalse(mModules.isEmpty());
+ }
+
+ @After
+ public void tearDown() {
+ mRadioManager = null;
+ mModules.clear();
+ if (mRadioTuner != null) {
+ mRadioTuner.close();
+ mRadioTuner = null;
+ }
+ }
+
+ private void openTuner(RadioTuner.Callback callback) {
+ 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_FM) {
+ fmBandDescriptor = (RadioManager.FmBandDescriptor)band;
+ break;
+ }
+ }
+ assertNotNull(fmBandDescriptor);
+ RadioManager.BandConfig fmBandConfig =
+ new RadioManager.FmBandConfig.Builder(fmBandDescriptor).build();
+
+ mRadioTuner = mRadioManager.openTuner(module.getId(), fmBandConfig, true, callback, null);
+ assertNotNull(mRadioTuner);
+ }
+
+ @Test
+ public void testOpenTuner() {
+ openTuner(new RadioTuner.Callback() {});
+ }
+}