blob: 557e9c8c85577d764f033fd5bc42fd85e5dd14d9 [file] [log] [blame]
/*
* Copyright (C) 2016 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.tv;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.tv.ITvRemoteProvider;
import android.media.tv.ITvRemoteServiceInput;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
/**
* Maintains a connection to a tv remote provider service.
*/
final class TvRemoteProviderProxy implements ServiceConnection {
private static final String TAG = "TvRemoteProvProxy"; // max. 23 chars
private static final boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean DEBUG_KEY = false;
// This should match TvRemoteProvider.ACTION_TV_REMOTE_PROVIDER
protected static final String SERVICE_INTERFACE =
"com.android.media.tv.remoteprovider.TvRemoteProvider";
private final Context mContext;
private final ComponentName mComponentName;
private final int mUserId;
private final int mUid;
private final Handler mHandler;
/**
* State guarded by mLock.
* This is the first lock in sequence for an incoming call.
* The second lock is always {@link TvRemoteService#mLock}
*
* There are currently no methods that break this sequence.
*/
private final Object mLock = new Object();
private ProviderMethods mProviderMethods;
// Connection state
private boolean mRunning;
private boolean mBound;
private Connection mActiveConnection;
private boolean mConnectionReady;
public TvRemoteProviderProxy(Context context, ComponentName componentName, int userId,
int uid) {
mContext = context;
mComponentName = componentName;
mUserId = userId;
mUid = uid;
mHandler = new Handler();
}
public void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "Proxy");
pw.println(prefix + " mUserId=" + mUserId);
pw.println(prefix + " mRunning=" + mRunning);
pw.println(prefix + " mBound=" + mBound);
pw.println(prefix + " mActiveConnection=" + mActiveConnection);
pw.println(prefix + " mConnectionReady=" + mConnectionReady);
}
public void setProviderSink(ProviderMethods provider) {
mProviderMethods = provider;
}
public boolean hasComponentName(String packageName, String className) {
return mComponentName.getPackageName().equals(packageName)
&& mComponentName.getClassName().equals(className);
}
public void start() {
if (!mRunning) {
if (DEBUG) {
Slog.d(TAG, this + ": Starting");
}
mRunning = true;
updateBinding();
}
}
public void stop() {
if (mRunning) {
if (DEBUG) {
Slog.d(TAG, this + ": Stopping");
}
mRunning = false;
updateBinding();
}
}
public void rebindIfDisconnected() {
synchronized (mLock) {
if (mActiveConnection == null && shouldBind()) {
unbind();
bind();
}
}
}
private void updateBinding() {
if (shouldBind()) {
bind();
} else {
unbind();
}
}
private boolean shouldBind() {
return mRunning;
}
private void bind() {
if (!mBound) {
if (DEBUG) {
Slog.d(TAG, this + ": Binding");
}
Intent service = new Intent(SERVICE_INTERFACE);
service.setComponent(mComponentName);
try {
mBound = mContext.bindServiceAsUser(service, this,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
new UserHandle(mUserId));
if (!mBound && DEBUG) {
Slog.d(TAG, this + ": Bind failed");
}
} catch (SecurityException ex) {
if (DEBUG) {
Slog.d(TAG, this + ": Bind failed", ex);
}
}
}
}
private void unbind() {
if (mBound) {
if (DEBUG) {
Slog.d(TAG, this + ": Unbinding");
}
mBound = false;
disconnect();
mContext.unbindService(this);
}
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG) {
Slog.d(TAG, this + ": onServiceConnected()");
}
if (mBound) {
disconnect();
ITvRemoteProvider provider = ITvRemoteProvider.Stub.asInterface(service);
if (provider != null) {
Connection connection = new Connection(provider);
if (connection.register()) {
synchronized (mLock) {
mActiveConnection = connection;
}
if (DEBUG) {
Slog.d(TAG, this + ": Connected successfully.");
}
} else {
if (DEBUG) {
Slog.d(TAG, this + ": Registration failed");
}
}
} else {
Slog.e(TAG, this + ": Service returned invalid remote-control provider binder");
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
if (DEBUG) Slog.d(TAG, this + ": Service disconnected");
disconnect();
}
private void onConnectionReady(Connection connection) {
synchronized (mLock) {
if (DEBUG) Slog.d(TAG, "onConnectionReady");
if (mActiveConnection == connection) {
if (DEBUG) Slog.d(TAG, "mConnectionReady = true");
mConnectionReady = true;
}
}
}
private void onConnectionDied(Connection connection) {
if (mActiveConnection == connection) {
if (DEBUG) Slog.d(TAG, this + ": Service connection died");
disconnect();
}
}
private void disconnect() {
synchronized (mLock) {
if (mActiveConnection != null) {
mConnectionReady = false;
mActiveConnection.dispose();
mActiveConnection = null;
}
}
}
// Provider helpers
public void inputBridgeConnected(IBinder token) {
synchronized (mLock) {
if (DEBUG) Slog.d(TAG, this + ": inputBridgeConnected token: " + token);
if (mConnectionReady) {
mActiveConnection.onInputBridgeConnected(token);
}
}
}
public interface ProviderMethods {
// InputBridge
void openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name,
int width, int height, int maxPointers);
void closeInputBridge(TvRemoteProviderProxy provider, IBinder token);
void clearInputBridge(TvRemoteProviderProxy provider, IBinder token);
void sendTimeStamp(TvRemoteProviderProxy provider, IBinder token, long timestamp);
void sendKeyDown(TvRemoteProviderProxy provider, IBinder token, int keyCode);
void sendKeyUp(TvRemoteProviderProxy provider, IBinder token, int keyCode);
void sendPointerDown(TvRemoteProviderProxy provider, IBinder token, int pointerId, int x,
int y);
void sendPointerUp(TvRemoteProviderProxy provider, IBinder token, int pointerId);
void sendPointerSync(TvRemoteProviderProxy provider, IBinder token);
}
private final class Connection implements IBinder.DeathRecipient {
private final ITvRemoteProvider mTvRemoteProvider;
private final RemoteServiceInputProvider mServiceInputProvider;
public Connection(ITvRemoteProvider provider) {
mTvRemoteProvider = provider;
mServiceInputProvider = new RemoteServiceInputProvider(this);
}
public boolean register() {
if (DEBUG) Slog.d(TAG, "Connection::register()");
try {
mTvRemoteProvider.asBinder().linkToDeath(this, 0);
mTvRemoteProvider.setRemoteServiceInputSink(mServiceInputProvider);
mHandler.post(new Runnable() {
@Override
public void run() {
onConnectionReady(Connection.this);
}
});
return true;
} catch (RemoteException ex) {
binderDied();
}
return false;
}
public void dispose() {
if (DEBUG) Slog.d(TAG, "Connection::dispose()");
mTvRemoteProvider.asBinder().unlinkToDeath(this, 0);
mServiceInputProvider.dispose();
}
public void onInputBridgeConnected(IBinder token) {
if (DEBUG) Slog.d(TAG, this + ": onInputBridgeConnected");
try {
mTvRemoteProvider.onInputBridgeConnected(token);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver onInputBridgeConnected. ", ex);
}
}
@Override
public void binderDied() {
mHandler.post(new Runnable() {
@Override
public void run() {
onConnectionDied(Connection.this);
}
});
}
void openInputBridge(final IBinder token, final String name, final int width,
final int height, final int maxPointers) {
synchronized (mLock) {
if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
if (DEBUG) {
Slog.d(TAG, this + ": openInputBridge," +
" token=" + token + ", name=" + name);
}
final long idToken = Binder.clearCallingIdentity();
try {
if (mProviderMethods != null) {
mProviderMethods.openInputBridge(TvRemoteProviderProxy.this, token,
name, width, height, maxPointers);
}
} finally {
Binder.restoreCallingIdentity(idToken);
}
} else {
if (DEBUG) {
Slog.w(TAG,
"openInputBridge, Invalid connection or incorrect uid: " + Binder
.getCallingUid());
}
}
}
}
void closeInputBridge(final IBinder token) {
synchronized (mLock) {
if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
if (DEBUG) {
Slog.d(TAG, this + ": closeInputBridge," +
" token=" + token);
}
final long idToken = Binder.clearCallingIdentity();
try {
if (mProviderMethods != null) {
mProviderMethods.closeInputBridge(TvRemoteProviderProxy.this, token);
}
} finally {
Binder.restoreCallingIdentity(idToken);
}
} else {
if (DEBUG) {
Slog.w(TAG,
"closeInputBridge, Invalid connection or incorrect uid: " +
Binder.getCallingUid());
}
}
}
}
void clearInputBridge(final IBinder token) {
synchronized (mLock) {
if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
if (DEBUG) {
Slog.d(TAG, this + ": clearInputBridge," +
" token=" + token);
}
final long idToken = Binder.clearCallingIdentity();
try {
if (mProviderMethods != null) {
mProviderMethods.clearInputBridge(TvRemoteProviderProxy.this, token);
}
} finally {
Binder.restoreCallingIdentity(idToken);
}
} else {
if (DEBUG) {
Slog.w(TAG,
"clearInputBridge, Invalid connection or incorrect uid: " +
Binder.getCallingUid());
}
}
}
}
void sendTimestamp(final IBinder token, final long timestamp) {
synchronized (mLock) {
if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
final long idToken = Binder.clearCallingIdentity();
try {
if (mProviderMethods != null) {
mProviderMethods.sendTimeStamp(TvRemoteProviderProxy.this, token,
timestamp);
}
} finally {
Binder.restoreCallingIdentity(idToken);
}
} else {
if (DEBUG) {
Slog.w(TAG,
"sendTimeStamp, Invalid connection or incorrect uid: " + Binder
.getCallingUid());
}
}
}
}
void sendKeyDown(final IBinder token, final int keyCode) {
synchronized (mLock) {
if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
if (DEBUG_KEY) {
Slog.d(TAG, this + ": sendKeyDown," +
" token=" + token + ", keyCode=" + keyCode);
}
final long idToken = Binder.clearCallingIdentity();
try {
if (mProviderMethods != null) {
mProviderMethods.sendKeyDown(TvRemoteProviderProxy.this, token,
keyCode);
}
} finally {
Binder.restoreCallingIdentity(idToken);
}
} else {
if (DEBUG) {
Slog.w(TAG,
"sendKeyDown, Invalid connection or incorrect uid: " + Binder
.getCallingUid());
}
}
}
}
void sendKeyUp(final IBinder token, final int keyCode) {
synchronized (mLock) {
if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
if (DEBUG_KEY) {
Slog.d(TAG, this + ": sendKeyUp," +
" token=" + token + ", keyCode=" + keyCode);
}
final long idToken = Binder.clearCallingIdentity();
try {
if (mProviderMethods != null) {
mProviderMethods.sendKeyUp(TvRemoteProviderProxy.this, token, keyCode);
}
} finally {
Binder.restoreCallingIdentity(idToken);
}
} else {
if (DEBUG) {
Slog.w(TAG,
"sendKeyUp, Invalid connection or incorrect uid: " + Binder
.getCallingUid());
}
}
}
}
void sendPointerDown(final IBinder token, final int pointerId, final int x, final int y) {
synchronized (mLock) {
if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
if (DEBUG_KEY) {
Slog.d(TAG, this + ": sendPointerDown," +
" token=" + token + ", pointerId=" + pointerId);
}
final long idToken = Binder.clearCallingIdentity();
try {
if (mProviderMethods != null) {
mProviderMethods.sendPointerDown(TvRemoteProviderProxy.this, token,
pointerId, x, y);
}
} finally {
Binder.restoreCallingIdentity(idToken);
}
} else {
if (DEBUG) {
Slog.w(TAG,
"sendPointerDown, Invalid connection or incorrect uid: " + Binder
.getCallingUid());
}
}
}
}
void sendPointerUp(final IBinder token, final int pointerId) {
synchronized (mLock) {
if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
if (DEBUG_KEY) {
Slog.d(TAG, this + ": sendPointerUp," +
" token=" + token + ", pointerId=" + pointerId);
}
final long idToken = Binder.clearCallingIdentity();
try {
if (mProviderMethods != null) {
mProviderMethods.sendPointerUp(TvRemoteProviderProxy.this, token,
pointerId);
}
} finally {
Binder.restoreCallingIdentity(idToken);
}
} else {
if (DEBUG) {
Slog.w(TAG,
"sendPointerUp, Invalid connection or incorrect uid: " + Binder
.getCallingUid());
}
}
}
}
void sendPointerSync(final IBinder token) {
synchronized (mLock) {
if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
if (DEBUG_KEY) {
Slog.d(TAG, this + ": sendPointerSync," +
" token=" + token);
}
final long idToken = Binder.clearCallingIdentity();
try {
if (mProviderMethods != null) {
mProviderMethods.sendPointerSync(TvRemoteProviderProxy.this, token);
}
} finally {
Binder.restoreCallingIdentity(idToken);
}
} else {
if (DEBUG) {
Slog.w(TAG,
"sendPointerSync, Invalid connection or incorrect uid: " + Binder
.getCallingUid());
}
}
}
}
}
/**
* Receives events from the connected provider.
* <p>
* This inner class is static and only retains a weak reference to the connection
* to prevent the client from being leaked in case the service is holding an
* active reference to the client's callback.
* </p>
*/
private static final class RemoteServiceInputProvider extends ITvRemoteServiceInput.Stub {
private final WeakReference<Connection> mConnectionRef;
public RemoteServiceInputProvider(Connection connection) {
mConnectionRef = new WeakReference<Connection>(connection);
}
public void dispose() {
// Terminate the connection.
mConnectionRef.clear();
}
@Override
public void openInputBridge(IBinder token, String name, int width,
int height, int maxPointers) throws RemoteException {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.openInputBridge(token, name, width, height, maxPointers);
}
}
@Override
public void closeInputBridge(IBinder token) throws RemoteException {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.closeInputBridge(token);
}
}
@Override
public void clearInputBridge(IBinder token) throws RemoteException {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.clearInputBridge(token);
}
}
@Override
public void sendTimestamp(IBinder token, long timestamp) throws RemoteException {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.sendTimestamp(token, timestamp);
}
}
@Override
public void sendKeyDown(IBinder token, int keyCode) throws RemoteException {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.sendKeyDown(token, keyCode);
}
}
@Override
public void sendKeyUp(IBinder token, int keyCode) throws RemoteException {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.sendKeyUp(token, keyCode);
}
}
@Override
public void sendPointerDown(IBinder token, int pointerId, int x, int y)
throws RemoteException {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.sendPointerDown(token, pointerId, x, y);
}
}
@Override
public void sendPointerUp(IBinder token, int pointerId) throws RemoteException {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.sendPointerUp(token, pointerId);
}
}
@Override
public void sendPointerSync(IBinder token) throws RemoteException {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.sendPointerSync(token);
}
}
}
}