Unbundle RemoteService on TV - part 3
- New service TVRemoteService triggered by SystemServer
- Provider service proxy and watcher for maintaining connections to unbundled
services which have the BIND_TV_REMOTE_SERVICE permission.
- Shared library to facilitate connections between unbundled service and
TVRemoteService.
- Unbundled service needs TV_VIRTUAL_REMOTE_CONTROLLER
permission to be fully functional.
b/23792608
Change-Id: Ief5c6995883d1f7268a73bdd0c920c4c3f42cddb
diff --git a/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java b/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java
new file mode 100644
index 0000000..557e9c8
--- /dev/null
+++ b/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java
@@ -0,0 +1,647 @@
+/*
+ * 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);
+ }
+ }
+ }
+}