Skip to content
Snippets Groups Projects
Commit 57050da2 authored by Tom Macieszczak's avatar Tom Macieszczak
Browse files

Fix TvRemoteProvider NullPointer bug

Test: atest TvRemoteProviderTest

Change-Id: I6dc934a83e6b21ba3ea6ccf64bd61b386ebf589f
parent fdc9134e
No related branches found
No related tags found
No related merge requests found
......@@ -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);
}
}
}
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,
}
<?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>
/*
* 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());
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment