diff options
4 files changed, 157 insertions, 32 deletions
diff --git a/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java b/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java index 35322ad8ee6b..0bf0f97d2c5e 100644 --- a/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java +++ b/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java @@ -19,18 +19,18 @@ package com.android.media.tv.remoteprovider; import android.content.Context; import android.media.tv.ITvRemoteProvider; import android.media.tv.ITvRemoteServiceInput; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; -import android.os.Message; import android.os.RemoteException; import android.util.Log; +import java.util.LinkedList; + /** * Base class for emote providers implemented in unbundled service. * <p/> * This object is not thread safe. It is only intended to be accessed on the * {@link Context#getMainLooper main looper thread} of an application. + * The callback {@link #onInputBridgeConnected()} may be called from a different thread. * </p><p> * IMPORTANT: This class is effectively a system API for unbundled emote service, and * must remain API stable. See README.txt in the root of this package for more information. @@ -50,11 +50,9 @@ public abstract class TvRemoteProvider { private static final String TAG = "TvRemoteProvider"; private static final boolean DEBUG_KEYS = false; - private static final int MSG_SET_SERVICE_INPUT = 1; - private static final int MSG_SEND_INPUTBRIDGE_CONNECTED = 2; private final Context mContext; private final ProviderStub mStub; - private final ProviderHandler mHandler; + private final LinkedList<Runnable> mOpenBridgeRunnables; private ITvRemoteServiceInput mRemoteServiceInput; /** @@ -67,7 +65,7 @@ public abstract class TvRemoteProvider { public TvRemoteProvider(Context context) { mContext = context.getApplicationContext(); mStub = new ProviderStub(); - mHandler = new ProviderHandler(mContext.getMainLooper()); + mOpenBridgeRunnables = new LinkedList<Runnable>(); } /** @@ -77,7 +75,6 @@ public abstract class TvRemoteProvider { return mContext; } - /** * Gets the Binder associated with the provider. * <p> @@ -105,7 +102,11 @@ public abstract class TvRemoteProvider { * @param tvServiceInput sink defined in framework service */ private void setRemoteServiceInputSink(ITvRemoteServiceInput tvServiceInput) { - mRemoteServiceInput = tvServiceInput; + synchronized (mOpenBridgeRunnables) { + mRemoteServiceInput = tvServiceInput; + } + mOpenBridgeRunnables.forEach(Runnable::run); + mOpenBridgeRunnables.clear(); } /** @@ -125,8 +126,25 @@ public abstract class TvRemoteProvider { */ public void openRemoteInputBridge(IBinder token, String name, int width, int height, int maxPointers) throws RuntimeException { + synchronized (mOpenBridgeRunnables) { + if (mRemoteServiceInput == null) { + Log.d(TAG, "Delaying openRemoteInputBridge() for " + name); + + mOpenBridgeRunnables.add(() -> { + try { + mRemoteServiceInput.openInputBridge( + token, name, width, height, maxPointers); + Log.d(TAG, "Delayed openRemoteInputBridge() for " + name + ": success"); + } catch (RemoteException re) { + Log.e(TAG, "Delayed openRemoteInputBridge() for " + name + ": failure", re); + } + }); + return; + } + } try { mRemoteServiceInput.openInputBridge(token, name, width, height, maxPointers); + Log.d(TAG, "openRemoteInputBridge() for " + name + ": success"); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -271,33 +289,12 @@ public abstract class TvRemoteProvider { private final class ProviderStub extends ITvRemoteProvider.Stub { @Override public void setRemoteServiceInputSink(ITvRemoteServiceInput tvServiceInput) { - mHandler.obtainMessage(MSG_SET_SERVICE_INPUT, tvServiceInput).sendToTarget(); + TvRemoteProvider.this.setRemoteServiceInputSink(tvServiceInput); } @Override public void onInputBridgeConnected(IBinder token) { - mHandler.obtainMessage(MSG_SEND_INPUTBRIDGE_CONNECTED, 0, 0, - (IBinder) token).sendToTarget(); - } - } - - private final class ProviderHandler extends Handler { - public ProviderHandler(Looper looper) { - super(looper, null, true); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_SET_SERVICE_INPUT: { - setRemoteServiceInputSink((ITvRemoteServiceInput) msg.obj); - break; - } - case MSG_SEND_INPUTBRIDGE_CONNECTED: { - onInputBridgeConnected((IBinder) msg.obj); - break; - } - } + TvRemoteProvider.this.onInputBridgeConnected(token); } } } diff --git a/media/lib/tvremote/tests/Android.bp b/media/lib/tvremote/tests/Android.bp new file mode 100644 index 000000000000..f00eed070798 --- /dev/null +++ b/media/lib/tvremote/tests/Android.bp @@ -0,0 +1,15 @@ +android_test { + name: "TvRemoteTests", + srcs: ["src/**/*.java"], + libs: [ + "android.test.runner", + "android.test.base", + "com.android.media.tv.remoteprovider", + ], + static_libs: [ + "mockito-target-minus-junit4", + ], + platform_apis: true, + certificate: "platform", + privileged: true, +} diff --git a/media/lib/tvremote/tests/AndroidManifest.xml b/media/lib/tvremote/tests/AndroidManifest.xml new file mode 100644 index 000000000000..4f843f701d20 --- /dev/null +++ b/media/lib/tvremote/tests/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 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="com.android.media.tv.remoteprovider"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.media.tv.remoteprovider" + android:label="Tests for TV Remote"/> +</manifest> diff --git a/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java b/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java new file mode 100644 index 000000000000..c9ce56138217 --- /dev/null +++ b/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 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.media.tv.remoteprovider; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.content.Context; +import android.media.tv.ITvRemoteProvider; +import android.media.tv.ITvRemoteServiceInput; +import android.os.Binder; +import android.os.IBinder; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.util.ArrayList; + +public class TvRemoteProviderTest extends AndroidTestCase { + private static final String TAG = TvRemoteProviderTest.class.getSimpleName(); + + @SmallTest + public void testOpenRemoteInputBridge() throws Exception { + Binder tokenA = new Binder(); + Binder tokenB = new Binder(); + Binder tokenC = new Binder(); + + class LocalTvRemoteProvider extends TvRemoteProvider { + private final ArrayList<IBinder> mTokens = new ArrayList<IBinder>(); + + LocalTvRemoteProvider(Context context) { + super(context); + } + + @Override + public void onInputBridgeConnected(IBinder token) { + mTokens.add(token); + } + + public boolean verifyTokens() { + return mTokens.size() == 3 + && mTokens.contains(tokenA) + && mTokens.contains(tokenB) + && mTokens.contains(tokenC); + } + } + + LocalTvRemoteProvider tvProvider = new LocalTvRemoteProvider(getContext()); + ITvRemoteProvider binder = (ITvRemoteProvider) tvProvider.getBinder(); + + ITvRemoteServiceInput tvServiceInput = mock(ITvRemoteServiceInput.class); + doAnswer((i) -> { + binder.onInputBridgeConnected(i.getArgument(0)); + return null; + }).when(tvServiceInput).openInputBridge(any(), any(), anyInt(), anyInt(), anyInt()); + + tvProvider.openRemoteInputBridge(tokenA, "A", 1, 1, 1); + tvProvider.openRemoteInputBridge(tokenB, "B", 1, 1, 1); + binder.setRemoteServiceInputSink(tvServiceInput); + tvProvider.openRemoteInputBridge(tokenC, "C", 1, 1, 1); + + verify(tvServiceInput).openInputBridge(tokenA, "A", 1, 1, 1); + verify(tvServiceInput).openInputBridge(tokenB, "B", 1, 1, 1); + verify(tvServiceInput).openInputBridge(tokenC, "C", 1, 1, 1); + verifyNoMoreInteractions(tvServiceInput); + + assertTrue(tvProvider.verifyTokens()); + } +} |