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
17package com.android.server.tv;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.media.tv.ITvRemoteProvider;
24import android.media.tv.ITvRemoteServiceInput;
25import android.os.Binder;
26import android.os.Handler;
27import android.os.IBinder;
28import android.os.RemoteException;
29import android.os.UserHandle;
30import android.util.Log;
31import android.util.Slog;
32
33import java.io.PrintWriter;
34import java.lang.ref.WeakReference;
35
36/**
37 * Maintains a connection to a tv remote provider service.
38 */
39final 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