/*
|
* 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);
|
}
|
}
|
}
|
}
|