Merge "Fix TvRemoteProvider NullPointer bug"
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 35322ad..0bf0f97 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 @@
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 @@
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 TvRemoteProvider(Context context) {
mContext = context.getApplicationContext();
mStub = new ProviderStub();
- mHandler = new ProviderHandler(mContext.getMainLooper());
+ mOpenBridgeRunnables = new LinkedList<Runnable>();
}
/**
@@ -77,7 +75,6 @@
return mContext;
}
-
/**
* Gets the Binder associated with the provider.
* <p>
@@ -105,7 +102,11 @@
* @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 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 @@
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 0000000..f00eed0
--- /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 0000000..4f843f7
--- /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 0000000..c9ce5613
--- /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());
+ }
+}