Home | History | Annotate | Download | only in tv
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.tv;
     18 
     19 import android.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.ServiceConnection;
     23 import android.media.tv.ITvRemoteProvider;
     24 import android.media.tv.ITvRemoteServiceInput;
     25 import android.os.Binder;
     26 import android.os.Handler;
     27 import android.os.IBinder;
     28 import android.os.RemoteException;
     29 import android.os.UserHandle;
     30 import android.util.Log;
     31 import android.util.Slog;
     32 
     33 import java.io.PrintWriter;
     34 import java.lang.ref.WeakReference;
     35 
     36 /**
     37  * Maintains a connection to a tv remote provider service.
     38  */
     39 final class TvRemoteProviderProxy implements ServiceConnection {
     40     private static final String TAG = "TvRemoteProvProxy";  // max. 23 chars
     41     private static final boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE);
     42     private static final boolean DEBUG_KEY = false;
     43 
     44 
     45     // This should match TvRemoteProvider.ACTION_TV_REMOTE_PROVIDER
     46     protected static final String SERVICE_INTERFACE =
     47             "com.android.media.tv.remoteprovider.TvRemoteProvider";
     48     private final Context mContext;
     49     private final ComponentName mComponentName;
     50     private final int mUserId;
     51     private final int mUid;
     52     private final Handler mHandler;
     53 
     54     /**
     55      * State guarded by mLock.
     56      *  This is the first lock in sequence for an incoming call.
     57      *  The second lock is always {@link TvRemoteService#mLock}
     58      *
     59      *  There are currently no methods that break this sequence.
     60      */
     61     private final Object mLock = new Object();
     62 
     63     private ProviderMethods mProviderMethods;
     64     // Connection state
     65     private boolean mRunning;
     66     private boolean mBound;
     67     private Connection mActiveConnection;
     68     private boolean mConnectionReady;
     69 
     70     public TvRemoteProviderProxy(Context context, ComponentName componentName, int userId,
     71                                  int uid) {
     72         mContext = context;
     73         mComponentName = componentName;
     74         mUserId = userId;
     75         mUid = uid;
     76         mHandler = new Handler();
     77     }
     78 
     79     public void dump(PrintWriter pw, String prefix) {
     80         pw.println(prefix + "Proxy");
     81         pw.println(prefix + "  mUserId=" + mUserId);
     82         pw.println(prefix + "  mRunning=" + mRunning);
     83         pw.println(prefix + "  mBound=" + mBound);
     84         pw.println(prefix + "  mActiveConnection=" + mActiveConnection);
     85         pw.println(prefix + "  mConnectionReady=" + mConnectionReady);
     86     }
     87 
     88     public void setProviderSink(ProviderMethods provider) {
     89         mProviderMethods = provider;
     90     }
     91 
     92     public boolean hasComponentName(String packageName, String className) {
     93         return mComponentName.getPackageName().equals(packageName)
     94                 && mComponentName.getClassName().equals(className);
     95     }
     96 
     97     public void start() {
     98         if (!mRunning) {
     99             if (DEBUG) {
    100                 Slog.d(TAG, this + ": Starting");
    101             }
    102 
    103             mRunning = true;
    104             updateBinding();
    105         }
    106     }
    107 
    108     public void stop() {
    109         if (mRunning) {
    110             if (DEBUG) {
    111                 Slog.d(TAG, this + ": Stopping");
    112             }
    113 
    114             mRunning = false;
    115             updateBinding();
    116         }
    117     }
    118 
    119     public void rebindIfDisconnected() {
    120         synchronized (mLock) {
    121             if (mActiveConnection == null && shouldBind()) {
    122                 unbind();
    123                 bind();
    124             }
    125         }
    126     }
    127 
    128     private void updateBinding() {
    129         if (shouldBind()) {
    130             bind();
    131         } else {
    132             unbind();
    133         }
    134     }
    135 
    136     private boolean shouldBind() {
    137         return mRunning;
    138     }
    139 
    140     private void bind() {
    141         if (!mBound) {
    142             if (DEBUG) {
    143                 Slog.d(TAG, this + ": Binding");
    144             }
    145 
    146             Intent service = new Intent(SERVICE_INTERFACE);
    147             service.setComponent(mComponentName);
    148             try {
    149                 mBound = mContext.bindServiceAsUser(service, this,
    150                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
    151                         new UserHandle(mUserId));
    152                 if (!mBound && DEBUG) {
    153                     Slog.d(TAG, this + ": Bind failed");
    154                 }
    155             } catch (SecurityException ex) {
    156                 if (DEBUG) {
    157                     Slog.d(TAG, this + ": Bind failed", ex);
    158                 }
    159             }
    160         }
    161     }
    162 
    163     private void unbind() {
    164         if (mBound) {
    165             if (DEBUG) {
    166                 Slog.d(TAG, this + ": Unbinding");
    167             }
    168 
    169             mBound = false;
    170             disconnect();
    171             mContext.unbindService(this);
    172         }
    173     }
    174 
    175     @Override
    176     public void onServiceConnected(ComponentName name, IBinder service) {
    177         if (DEBUG) {
    178             Slog.d(TAG, this + ": onServiceConnected()");
    179         }
    180 
    181         if (mBound) {
    182             disconnect();
    183 
    184             ITvRemoteProvider provider = ITvRemoteProvider.Stub.asInterface(service);
    185             if (provider != null) {
    186                 Connection connection = new Connection(provider);
    187                 if (connection.register()) {
    188                     synchronized (mLock) {
    189                         mActiveConnection = connection;
    190                     }
    191                     if (DEBUG) {
    192                         Slog.d(TAG, this + ": Connected successfully.");
    193                     }
    194                 } else {
    195                     if (DEBUG) {
    196                         Slog.d(TAG, this + ": Registration failed");
    197                     }
    198                 }
    199             } else {
    200                 Slog.e(TAG, this + ": Service returned invalid remote-control provider binder");
    201             }
    202         }
    203     }
    204 
    205     @Override
    206     public void onServiceDisconnected(ComponentName name) {
    207         if (DEBUG) Slog.d(TAG, this + ": Service disconnected");
    208         disconnect();
    209     }
    210 
    211 
    212     private void onConnectionReady(Connection connection) {
    213         synchronized (mLock) {
    214             if (DEBUG) Slog.d(TAG, "onConnectionReady");
    215             if (mActiveConnection == connection) {
    216                 if (DEBUG) Slog.d(TAG, "mConnectionReady = true");
    217                 mConnectionReady = true;
    218             }
    219         }
    220     }
    221 
    222     private void onConnectionDied(Connection connection) {
    223         if (mActiveConnection == connection) {
    224             if (DEBUG) Slog.d(TAG, this + ": Service connection died");
    225             disconnect();
    226         }
    227     }
    228 
    229     private void disconnect() {
    230         synchronized (mLock) {
    231             if (mActiveConnection != null) {
    232                 mConnectionReady = false;
    233                 mActiveConnection.dispose();
    234                 mActiveConnection = null;
    235             }
    236         }
    237     }
    238 
    239     // Provider helpers
    240     public void inputBridgeConnected(IBinder token) {
    241         synchronized (mLock) {
    242             if (DEBUG) Slog.d(TAG, this + ": inputBridgeConnected token: " + token);
    243             if (mConnectionReady) {
    244                 mActiveConnection.onInputBridgeConnected(token);
    245             }
    246         }
    247     }
    248 
    249     public interface ProviderMethods {
    250         // InputBridge
    251         void openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name,
    252                              int width, int height, int maxPointers);
    253 
    254         void closeInputBridge(TvRemoteProviderProxy provider, IBinder token);
    255 
    256         void clearInputBridge(TvRemoteProviderProxy provider, IBinder token);
    257 
    258         void sendTimeStamp(TvRemoteProviderProxy provider, IBinder token, long timestamp);
    259 
    260         void sendKeyDown(TvRemoteProviderProxy provider, IBinder token, int keyCode);
    261 
    262         void sendKeyUp(TvRemoteProviderProxy provider, IBinder token, int keyCode);
    263 
    264         void sendPointerDown(TvRemoteProviderProxy provider, IBinder token, int pointerId, int x,
    265                              int y);
    266 
    267         void sendPointerUp(TvRemoteProviderProxy provider, IBinder token, int pointerId);
    268 
    269         void sendPointerSync(TvRemoteProviderProxy provider, IBinder token);
    270     }
    271 
    272     private final class Connection implements IBinder.DeathRecipient {
    273         private final ITvRemoteProvider mTvRemoteProvider;
    274         private final RemoteServiceInputProvider mServiceInputProvider;
    275 
    276         public Connection(ITvRemoteProvider provider) {
    277             mTvRemoteProvider = provider;
    278             mServiceInputProvider = new RemoteServiceInputProvider(this);
    279         }
    280 
    281         public boolean register() {
    282             if (DEBUG) Slog.d(TAG, "Connection::register()");
    283             try {
    284                 mTvRemoteProvider.asBinder().linkToDeath(this, 0);
    285                 mTvRemoteProvider.setRemoteServiceInputSink(mServiceInputProvider);
    286                 mHandler.post(new Runnable() {
    287                     @Override
    288                     public void run() {
    289                         onConnectionReady(Connection.this);
    290                     }
    291                 });
    292                 return true;
    293             } catch (RemoteException ex) {
    294                 binderDied();
    295             }
    296             return false;
    297         }
    298 
    299         public void dispose() {
    300             if (DEBUG) Slog.d(TAG, "Connection::dispose()");
    301             mTvRemoteProvider.asBinder().unlinkToDeath(this, 0);
    302             mServiceInputProvider.dispose();
    303         }
    304 
    305 
    306         public void onInputBridgeConnected(IBinder token) {
    307             if (DEBUG) Slog.d(TAG, this + ": onInputBridgeConnected");
    308             try {
    309                 mTvRemoteProvider.onInputBridgeConnected(token);
    310             } catch (RemoteException ex) {
    311                 Slog.e(TAG, "Failed to deliver onInputBridgeConnected. ", ex);
    312             }
    313         }
    314 
    315         @Override
    316         public void binderDied() {
    317             mHandler.post(new Runnable() {
    318                 @Override
    319                 public void run() {
    320                     onConnectionDied(Connection.this);
    321                 }
    322             });
    323         }
    324 
    325         void openInputBridge(final IBinder token, final String name, final int width,
    326                              final int height, final int maxPointers) {
    327             synchronized (mLock) {
    328                 if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
    329                     if (DEBUG) {
    330                         Slog.d(TAG, this + ": openInputBridge," +
    331                                 " token=" + token + ", name=" + name);
    332                     }
    333                     final long idToken = Binder.clearCallingIdentity();
    334                     try {
    335                         if (mProviderMethods != null) {
    336                             mProviderMethods.openInputBridge(TvRemoteProviderProxy.this, token,
    337                                     name, width, height, maxPointers);
    338                         }
    339                     } finally {
    340                         Binder.restoreCallingIdentity(idToken);
    341                     }
    342                 } else {
    343                     if (DEBUG) {
    344                         Slog.w(TAG,
    345                                 "openInputBridge, Invalid connection or incorrect uid: " + Binder
    346                                         .getCallingUid());
    347                     }
    348                 }
    349             }
    350         }
    351 
    352         void closeInputBridge(final IBinder token) {
    353             synchronized (mLock) {
    354                 if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
    355                     if (DEBUG) {
    356                         Slog.d(TAG, this + ": closeInputBridge," +
    357                                 " token=" + token);
    358                     }
    359                     final long idToken = Binder.clearCallingIdentity();
    360                     try {
    361                         if (mProviderMethods != null) {
    362                             mProviderMethods.closeInputBridge(TvRemoteProviderProxy.this, token);
    363                         }
    364                     } finally {
    365                         Binder.restoreCallingIdentity(idToken);
    366                     }
    367                 } else {
    368                     if (DEBUG) {
    369                         Slog.w(TAG,
    370                                 "closeInputBridge, Invalid connection or incorrect uid: " +
    371                                         Binder.getCallingUid());
    372                     }
    373                 }
    374             }
    375         }
    376 
    377         void clearInputBridge(final IBinder token) {
    378             synchronized (mLock) {
    379                 if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
    380                     if (DEBUG) {
    381                         Slog.d(TAG, this + ": clearInputBridge," +
    382                                 " token=" + token);
    383                     }
    384                     final long idToken = Binder.clearCallingIdentity();
    385                     try {
    386                         if (mProviderMethods != null) {
    387                             mProviderMethods.clearInputBridge(TvRemoteProviderProxy.this, token);
    388                         }
    389                     } finally {
    390                         Binder.restoreCallingIdentity(idToken);
    391                     }
    392                 } else {
    393                     if (DEBUG) {
    394                         Slog.w(TAG,
    395                                 "clearInputBridge, Invalid connection or incorrect uid: " +
    396                                         Binder.getCallingUid());
    397                     }
    398                 }
    399             }
    400         }
    401 
    402         void sendTimestamp(final IBinder token, final long timestamp) {
    403             synchronized (mLock) {
    404                 if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
    405                     final long idToken = Binder.clearCallingIdentity();
    406                     try {
    407                         if (mProviderMethods != null) {
    408                             mProviderMethods.sendTimeStamp(TvRemoteProviderProxy.this, token,
    409                                     timestamp);
    410                         }
    411                     } finally {
    412                         Binder.restoreCallingIdentity(idToken);
    413                     }
    414                 } else {
    415                     if (DEBUG) {
    416                         Slog.w(TAG,
    417                                 "sendTimeStamp, Invalid connection or incorrect uid: " + Binder
    418                                         .getCallingUid());
    419                     }
    420                 }
    421             }
    422         }
    423 
    424         void sendKeyDown(final IBinder token, final int keyCode) {
    425             synchronized (mLock) {
    426                 if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
    427                     if (DEBUG_KEY) {
    428                         Slog.d(TAG, this + ": sendKeyDown," +
    429                                 " token=" + token + ", keyCode=" + keyCode);
    430                     }
    431                     final long idToken = Binder.clearCallingIdentity();
    432                     try {
    433                         if (mProviderMethods != null) {
    434                             mProviderMethods.sendKeyDown(TvRemoteProviderProxy.this, token,
    435                                     keyCode);
    436                         }
    437                     } finally {
    438                         Binder.restoreCallingIdentity(idToken);
    439                     }
    440                 } else {
    441                     if (DEBUG) {
    442                         Slog.w(TAG,
    443                                 "sendKeyDown, Invalid connection or incorrect uid: " + Binder
    444                                         .getCallingUid());
    445                     }
    446                 }
    447             }
    448         }
    449 
    450         void sendKeyUp(final IBinder token, final int keyCode) {
    451             synchronized (mLock) {
    452                 if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
    453                     if (DEBUG_KEY) {
    454                         Slog.d(TAG, this + ": sendKeyUp," +
    455                                 " token=" + token + ", keyCode=" + keyCode);
    456                     }
    457                     final long idToken = Binder.clearCallingIdentity();
    458                     try {
    459                         if (mProviderMethods != null) {
    460                             mProviderMethods.sendKeyUp(TvRemoteProviderProxy.this, token, keyCode);
    461                         }
    462                     } finally {
    463                         Binder.restoreCallingIdentity(idToken);
    464                     }
    465                 } else {
    466                     if (DEBUG) {
    467                         Slog.w(TAG,
    468                                 "sendKeyUp, Invalid connection or incorrect uid: " + Binder
    469                                         .getCallingUid());
    470                     }
    471                 }
    472             }
    473         }
    474 
    475         void sendPointerDown(final IBinder token, final int pointerId, final int x, final int y) {
    476             synchronized (mLock) {
    477                 if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
    478                     if (DEBUG_KEY) {
    479                         Slog.d(TAG, this + ": sendPointerDown," +
    480                                 " token=" + token + ", pointerId=" + pointerId);
    481                     }
    482                     final long idToken = Binder.clearCallingIdentity();
    483                     try {
    484                         if (mProviderMethods != null) {
    485                             mProviderMethods.sendPointerDown(TvRemoteProviderProxy.this, token,
    486                                     pointerId, x, y);
    487                         }
    488                     } finally {
    489                         Binder.restoreCallingIdentity(idToken);
    490                     }
    491                 } else {
    492                     if (DEBUG) {
    493                         Slog.w(TAG,
    494                                 "sendPointerDown, Invalid connection or incorrect uid: " + Binder
    495                                         .getCallingUid());
    496                     }
    497                 }
    498             }
    499         }
    500 
    501         void sendPointerUp(final IBinder token, final int pointerId) {
    502             synchronized (mLock) {
    503                 if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
    504                     if (DEBUG_KEY) {
    505                         Slog.d(TAG, this + ": sendPointerUp," +
    506                                 " token=" + token + ", pointerId=" + pointerId);
    507                     }
    508                     final long idToken = Binder.clearCallingIdentity();
    509                     try {
    510                         if (mProviderMethods != null) {
    511                             mProviderMethods.sendPointerUp(TvRemoteProviderProxy.this, token,
    512                                     pointerId);
    513                         }
    514                     } finally {
    515                         Binder.restoreCallingIdentity(idToken);
    516                     }
    517                 } else {
    518                     if (DEBUG) {
    519                         Slog.w(TAG,
    520                                 "sendPointerUp, Invalid connection or incorrect uid: " + Binder
    521                                         .getCallingUid());
    522                     }
    523                 }
    524             }
    525         }
    526 
    527         void sendPointerSync(final IBinder token) {
    528             synchronized (mLock) {
    529                 if (mActiveConnection == this && Binder.getCallingUid() == mUid) {
    530                     if (DEBUG_KEY) {
    531                         Slog.d(TAG, this + ": sendPointerSync," +
    532                                 " token=" + token);
    533                     }
    534                     final long idToken = Binder.clearCallingIdentity();
    535                     try {
    536                         if (mProviderMethods != null) {
    537                             mProviderMethods.sendPointerSync(TvRemoteProviderProxy.this, token);
    538                         }
    539                     } finally {
    540                         Binder.restoreCallingIdentity(idToken);
    541                     }
    542                 } else {
    543                     if (DEBUG) {
    544                         Slog.w(TAG,
    545                                 "sendPointerSync, Invalid connection or incorrect uid: " + Binder
    546                                         .getCallingUid());
    547                     }
    548                 }
    549             }
    550         }
    551     }
    552 
    553     /**
    554      * Receives events from the connected provider.
    555      * <p>
    556      * This inner class is static and only retains a weak reference to the connection
    557      * to prevent the client from being leaked in case the service is holding an
    558      * active reference to the client's callback.
    559      * </p>
    560      */
    561     private static final class RemoteServiceInputProvider extends ITvRemoteServiceInput.Stub {
    562         private final WeakReference<Connection> mConnectionRef;
    563 
    564         public RemoteServiceInputProvider(Connection connection) {
    565             mConnectionRef = new WeakReference<Connection>(connection);
    566         }
    567 
    568         public void dispose() {
    569             // Terminate the connection.
    570             mConnectionRef.clear();
    571         }
    572 
    573         @Override
    574         public void openInputBridge(IBinder token, String name, int width,
    575                                     int height, int maxPointers) throws RemoteException {
    576             Connection connection = mConnectionRef.get();
    577             if (connection != null) {
    578                 connection.openInputBridge(token, name, width, height, maxPointers);
    579             }
    580         }
    581 
    582         @Override
    583         public void closeInputBridge(IBinder token) throws RemoteException {
    584             Connection connection = mConnectionRef.get();
    585             if (connection != null) {
    586                 connection.closeInputBridge(token);
    587             }
    588         }
    589 
    590         @Override
    591         public void clearInputBridge(IBinder token) throws RemoteException {
    592             Connection connection = mConnectionRef.get();
    593             if (connection != null) {
    594                 connection.clearInputBridge(token);
    595             }
    596         }
    597 
    598         @Override
    599         public void sendTimestamp(IBinder token, long timestamp) throws RemoteException {
    600             Connection connection = mConnectionRef.get();
    601             if (connection != null) {
    602                 connection.sendTimestamp(token, timestamp);
    603             }
    604         }
    605 
    606         @Override
    607         public void sendKeyDown(IBinder token, int keyCode) throws RemoteException {
    608             Connection connection = mConnectionRef.get();
    609             if (connection != null) {
    610                 connection.sendKeyDown(token, keyCode);
    611             }
    612         }
    613 
    614         @Override
    615         public void sendKeyUp(IBinder token, int keyCode) throws RemoteException {
    616             Connection connection = mConnectionRef.get();
    617             if (connection != null) {
    618                 connection.sendKeyUp(token, keyCode);
    619             }
    620         }
    621 
    622         @Override
    623         public void sendPointerDown(IBinder token, int pointerId, int x, int y)
    624                 throws RemoteException {
    625             Connection connection = mConnectionRef.get();
    626             if (connection != null) {
    627                 connection.sendPointerDown(token, pointerId, x, y);
    628             }
    629         }
    630 
    631         @Override
    632         public void sendPointerUp(IBinder token, int pointerId) throws RemoteException {
    633             Connection connection = mConnectionRef.get();
    634             if (connection != null) {
    635                 connection.sendPointerUp(token, pointerId);
    636             }
    637         }
    638 
    639         @Override
    640         public void sendPointerSync(IBinder token) throws RemoteException {
    641             Connection connection = mConnectionRef.get();
    642             if (connection != null) {
    643                 connection.sendPointerSync(token);
    644             }
    645         }
    646     }
    647 }
    648