From 347192e0da1f39347e7ab7b58b002f166b4afaa7 Mon Sep 17 00:00:00 2001 From: Tomasz Wasilczyk Date: Tue, 4 Apr 2017 11:13:44 -0700 Subject: [PATCH] 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 --- Android.mk | 3 + .../android/app/SystemServiceRegistry.java | 2 +- .../android/content/pm/PackageManager.java | 8 + .../android/hardware/radio/IRadioService.aidl | 28 ++++ core/java/android/hardware/radio/ITuner.aidl | 24 +++ .../android/hardware/radio/RadioManager.aidl | 20 +++ .../android/hardware/radio/RadioManager.java | 39 ++++- .../android/hardware/radio/TunerAdapter.java | 143 ++++++++++++++++++ .../android/server/radio/RadioService.java | 56 +++++++ .../java/com/android/server/SystemServer.java | 10 ++ tests/radio/Android.mk | 33 ++++ tests/radio/AndroidManifest.xml | 29 ++++ .../hardware/radio/tests/RadioTest.java | 97 ++++++++++++ 13 files changed, 484 insertions(+), 8 deletions(-) create mode 100644 core/java/android/hardware/radio/IRadioService.aidl create mode 100644 core/java/android/hardware/radio/ITuner.aidl create mode 100644 core/java/android/hardware/radio/RadioManager.aidl create mode 100644 core/java/android/hardware/radio/TunerAdapter.java create mode 100644 services/core/java/com/android/server/radio/RadioService.java create mode 100644 tests/radio/Android.mk create mode 100644 tests/radio/AndroidManifest.xml create mode 100644 tests/radio/src/android/hardware/radio/tests/RadioTest.java diff --git a/Android.mk b/Android.mk index cf4a7aa8232c4..d3415b339ccba 100644 --- a/Android.mk +++ b/Android.mk @@ -216,6 +216,8 @@ LOCAL_SRC_FILES += \ 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 @@ aidl_files := \ 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 c2c40df2c2b85..abc50ae30f595 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -784,7 +784,7 @@ final class SystemServiceRegistry { registerService(Context.RADIO_SERVICE, RadioManager.class, new CachedServiceFetcher() { @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 4f0ff61d2adc0..7ba2e96ca2d63 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1828,6 +1828,14 @@ public abstract class PackageManager { @SdkConstant(SdkConstantType.FEATURE) 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 0000000000000..3c831147bf4c5 --- /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 0000000000000..73f6dc2e366fa --- /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 0000000000000..d79ae4fdda638 --- /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 ac2bd9591c476..99412de6b862d 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 @@ import java.util.Arrays; */ @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 class RadioManager { 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 0000000000000..1822e07b7e177 --- /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 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 0000000000000..327e98fc1cf4b --- /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 f74512a001229..ad25ce009cdfa 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.pm.UserManagerService; 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 @@ public final class SystemServer { 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 @@ public final class SystemServer { 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 0000000000000..46bf9cc98ae2e --- /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 0000000000000..150edbffe8f8b --- /dev/null +++ b/tests/radio/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + 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 0000000000000..47fbe74db3b57 --- /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 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() {}); + } +}