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());
+    }
+}