1/*
2 * Copyright 2018 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.support.mediarouter.media;
18
19import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_ROUTE_ID;
20import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
21        .CLIENT_DATA_ROUTE_LIBRARY_GROUP;
22import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
23        .CLIENT_DATA_UNSELECT_REASON;
24import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_DATA_VOLUME;
25import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
26        .CLIENT_MSG_CREATE_ROUTE_CONTROLLER;
27import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_REGISTER;
28import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
29        .CLIENT_MSG_RELEASE_ROUTE_CONTROLLER;
30import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
31        .CLIENT_MSG_ROUTE_CONTROL_REQUEST;
32import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_SELECT_ROUTE;
33import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
34        .CLIENT_MSG_SET_DISCOVERY_REQUEST;
35import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
36        .CLIENT_MSG_SET_ROUTE_VOLUME;
37import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_MSG_UNREGISTER;
38import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
39        .CLIENT_MSG_UNSELECT_ROUTE;
40import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
41        .CLIENT_MSG_UPDATE_ROUTE_VOLUME;
42import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.CLIENT_VERSION_CURRENT;
43import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_DATA_ERROR;
44import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
45        .SERVICE_MSG_CONTROL_REQUEST_FAILED;
46import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
47        .SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED;
48import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
49        .SERVICE_MSG_DESCRIPTOR_CHANGED;
50import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
51        .SERVICE_MSG_GENERIC_FAILURE;
52import static com.android.support.mediarouter.media.MediaRouteProviderProtocol
53        .SERVICE_MSG_GENERIC_SUCCESS;
54import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_MSG_REGISTERED;
55import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.SERVICE_VERSION_1;
56import static com.android.support.mediarouter.media.MediaRouteProviderProtocol.isValidRemoteMessenger;
57
58import android.annotation.NonNull;
59import android.content.ComponentName;
60import android.content.Context;
61import android.content.Intent;
62import android.content.ServiceConnection;
63import android.os.Bundle;
64import android.os.DeadObjectException;
65import android.os.Handler;
66import android.os.IBinder;
67import android.os.IBinder.DeathRecipient;
68import android.os.Message;
69import android.os.Messenger;
70import android.os.RemoteException;
71import android.util.Log;
72import android.util.SparseArray;
73
74import com.android.support.mediarouter.media.MediaRouter.ControlRequestCallback;
75
76import java.lang.ref.WeakReference;
77import java.util.ArrayList;
78import java.util.List;
79
80/**
81 * Maintains a connection to a particular media route provider service.
82 */
83final class RegisteredMediaRouteProvider extends MediaRouteProvider
84        implements ServiceConnection {
85    static final String TAG = "MediaRouteProviderProxy";  // max. 23 chars
86    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
87
88    private final ComponentName mComponentName;
89    final PrivateHandler mPrivateHandler;
90    private final ArrayList<Controller> mControllers = new ArrayList<Controller>();
91
92    private boolean mStarted;
93    private boolean mBound;
94    private Connection mActiveConnection;
95    private boolean mConnectionReady;
96
97    public RegisteredMediaRouteProvider(Context context, ComponentName componentName) {
98        super(context, new ProviderMetadata(componentName));
99
100        mComponentName = componentName;
101        mPrivateHandler = new PrivateHandler();
102    }
103
104    @Override
105    public RouteController onCreateRouteController(@NonNull String routeId) {
106        if (routeId == null) {
107            throw new IllegalArgumentException("routeId cannot be null");
108        }
109        return createRouteController(routeId, null);
110    }
111
112    @Override
113    public RouteController onCreateRouteController(
114            @NonNull String routeId, @NonNull String routeGroupId) {
115        if (routeId == null) {
116            throw new IllegalArgumentException("routeId cannot be null");
117        }
118        if (routeGroupId == null) {
119            throw new IllegalArgumentException("routeGroupId cannot be null");
120        }
121        return createRouteController(routeId, routeGroupId);
122    }
123
124    @Override
125    public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
126        if (mConnectionReady) {
127            mActiveConnection.setDiscoveryRequest(request);
128        }
129        updateBinding();
130    }
131
132    @Override
133    public void onServiceConnected(ComponentName name, IBinder service) {
134        if (DEBUG) {
135            Log.d(TAG, this + ": Connected");
136        }
137
138        if (mBound) {
139            disconnect();
140
141            Messenger messenger = (service != null ? new Messenger(service) : null);
142            if (isValidRemoteMessenger(messenger)) {
143                Connection connection = new Connection(messenger);
144                if (connection.register()) {
145                    mActiveConnection = connection;
146                } else {
147                    if (DEBUG) {
148                        Log.d(TAG, this + ": Registration failed");
149                    }
150                }
151            } else {
152                Log.e(TAG, this + ": Service returned invalid messenger binder");
153            }
154        }
155    }
156
157    @Override
158    public void onServiceDisconnected(ComponentName name) {
159        if (DEBUG) {
160            Log.d(TAG, this + ": Service disconnected");
161        }
162        disconnect();
163    }
164
165    @Override
166    public String toString() {
167        return "Service connection " + mComponentName.flattenToShortString();
168    }
169
170    public boolean hasComponentName(String packageName, String className) {
171        return mComponentName.getPackageName().equals(packageName)
172                && mComponentName.getClassName().equals(className);
173    }
174
175    public void start() {
176        if (!mStarted) {
177            if (DEBUG) {
178                Log.d(TAG, this + ": Starting");
179            }
180
181            mStarted = true;
182            updateBinding();
183        }
184    }
185
186    public void stop() {
187        if (mStarted) {
188            if (DEBUG) {
189                Log.d(TAG, this + ": Stopping");
190            }
191
192            mStarted = false;
193            updateBinding();
194        }
195    }
196
197    public void rebindIfDisconnected() {
198        if (mActiveConnection == null && shouldBind()) {
199            unbind();
200            bind();
201        }
202    }
203
204    private void updateBinding() {
205        if (shouldBind()) {
206            bind();
207        } else {
208            unbind();
209        }
210    }
211
212    private boolean shouldBind() {
213        if (mStarted) {
214            // Bind whenever there is a discovery request.
215            if (getDiscoveryRequest() != null) {
216                return true;
217            }
218
219            // Bind whenever the application has an active route controller.
220            // This means that one of this provider's routes is selected.
221            if (!mControllers.isEmpty()) {
222                return true;
223            }
224        }
225        return false;
226    }
227
228    private void bind() {
229        if (!mBound) {
230            if (DEBUG) {
231                Log.d(TAG, this + ": Binding");
232            }
233
234            Intent service = new Intent(MediaRouteProviderProtocol.SERVICE_INTERFACE);
235            service.setComponent(mComponentName);
236            try {
237                mBound = getContext().bindService(service, this, Context.BIND_AUTO_CREATE);
238                if (!mBound && DEBUG) {
239                    Log.d(TAG, this + ": Bind failed");
240                }
241            } catch (SecurityException ex) {
242                if (DEBUG) {
243                    Log.d(TAG, this + ": Bind failed", ex);
244                }
245            }
246        }
247    }
248
249    private void unbind() {
250        if (mBound) {
251            if (DEBUG) {
252                Log.d(TAG, this + ": Unbinding");
253            }
254
255            mBound = false;
256            disconnect();
257            getContext().unbindService(this);
258        }
259    }
260
261    private RouteController createRouteController(String routeId, String routeGroupId) {
262        MediaRouteProviderDescriptor descriptor = getDescriptor();
263        if (descriptor != null) {
264            List<MediaRouteDescriptor> routes = descriptor.getRoutes();
265            final int count = routes.size();
266            for (int i = 0; i < count; i++) {
267                final MediaRouteDescriptor route = routes.get(i);
268                if (route.getId().equals(routeId)) {
269                    Controller controller = new Controller(routeId, routeGroupId);
270                    mControllers.add(controller);
271                    if (mConnectionReady) {
272                        controller.attachConnection(mActiveConnection);
273                    }
274                    updateBinding();
275                    return controller;
276                }
277            }
278        }
279        return null;
280    }
281
282    void onConnectionReady(Connection connection) {
283        if (mActiveConnection == connection) {
284            mConnectionReady = true;
285            attachControllersToConnection();
286
287            MediaRouteDiscoveryRequest request = getDiscoveryRequest();
288            if (request != null) {
289                mActiveConnection.setDiscoveryRequest(request);
290            }
291        }
292    }
293
294    void onConnectionDied(Connection connection) {
295        if (mActiveConnection == connection) {
296            if (DEBUG) {
297                Log.d(TAG, this + ": Service connection died");
298            }
299            disconnect();
300        }
301    }
302
303    void onConnectionError(Connection connection, String error) {
304        if (mActiveConnection == connection) {
305            if (DEBUG) {
306                Log.d(TAG, this + ": Service connection error - " + error);
307            }
308            unbind();
309        }
310    }
311
312    void onConnectionDescriptorChanged(Connection connection,
313            MediaRouteProviderDescriptor descriptor) {
314        if (mActiveConnection == connection) {
315            if (DEBUG) {
316                Log.d(TAG, this + ": Descriptor changed, descriptor=" + descriptor);
317            }
318            setDescriptor(descriptor);
319        }
320    }
321
322    private void disconnect() {
323        if (mActiveConnection != null) {
324            setDescriptor(null);
325            mConnectionReady = false;
326            detachControllersFromConnection();
327            mActiveConnection.dispose();
328            mActiveConnection = null;
329        }
330    }
331
332    void onControllerReleased(Controller controller) {
333        mControllers.remove(controller);
334        controller.detachConnection();
335        updateBinding();
336    }
337
338    private void attachControllersToConnection() {
339        int count = mControllers.size();
340        for (int i = 0; i < count; i++) {
341            mControllers.get(i).attachConnection(mActiveConnection);
342        }
343    }
344
345    private void detachControllersFromConnection() {
346        int count = mControllers.size();
347        for (int i = 0; i < count; i++) {
348            mControllers.get(i).detachConnection();
349        }
350    }
351
352    private final class Controller extends RouteController {
353        private final String mRouteId;
354        private final String mRouteGroupId;
355
356        private boolean mSelected;
357        private int mPendingSetVolume = -1;
358        private int mPendingUpdateVolumeDelta;
359
360        private Connection mConnection;
361        private int mControllerId;
362
363        public Controller(String routeId, String routeGroupId) {
364            mRouteId = routeId;
365            mRouteGroupId = routeGroupId;
366        }
367
368        public void attachConnection(Connection connection) {
369            mConnection = connection;
370            mControllerId = connection.createRouteController(mRouteId, mRouteGroupId);
371            if (mSelected) {
372                connection.selectRoute(mControllerId);
373                if (mPendingSetVolume >= 0) {
374                    connection.setVolume(mControllerId, mPendingSetVolume);
375                    mPendingSetVolume = -1;
376                }
377                if (mPendingUpdateVolumeDelta != 0) {
378                    connection.updateVolume(mControllerId, mPendingUpdateVolumeDelta);
379                    mPendingUpdateVolumeDelta = 0;
380                }
381            }
382        }
383
384        public void detachConnection() {
385            if (mConnection != null) {
386                mConnection.releaseRouteController(mControllerId);
387                mConnection = null;
388                mControllerId = 0;
389            }
390        }
391
392        @Override
393        public void onRelease() {
394            onControllerReleased(this);
395        }
396
397        @Override
398        public void onSelect() {
399            mSelected = true;
400            if (mConnection != null) {
401                mConnection.selectRoute(mControllerId);
402            }
403        }
404
405        @Override
406        public void onUnselect() {
407            onUnselect(MediaRouter.UNSELECT_REASON_UNKNOWN);
408        }
409
410        @Override
411        public void onUnselect(int reason) {
412            mSelected = false;
413            if (mConnection != null) {
414                mConnection.unselectRoute(mControllerId, reason);
415            }
416        }
417
418        @Override
419        public void onSetVolume(int volume) {
420            if (mConnection != null) {
421                mConnection.setVolume(mControllerId, volume);
422            } else {
423                mPendingSetVolume = volume;
424                mPendingUpdateVolumeDelta = 0;
425            }
426        }
427
428        @Override
429        public void onUpdateVolume(int delta) {
430            if (mConnection != null) {
431                mConnection.updateVolume(mControllerId, delta);
432            } else {
433                mPendingUpdateVolumeDelta += delta;
434            }
435        }
436
437        @Override
438        public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
439            if (mConnection != null) {
440                return mConnection.sendControlRequest(mControllerId, intent, callback);
441            }
442            return false;
443        }
444    }
445
446    private final class Connection implements DeathRecipient {
447        private final Messenger mServiceMessenger;
448        private final ReceiveHandler mReceiveHandler;
449        private final Messenger mReceiveMessenger;
450
451        private int mNextRequestId = 1;
452        private int mNextControllerId = 1;
453        private int mServiceVersion; // non-zero when registration complete
454
455        private int mPendingRegisterRequestId;
456        private final SparseArray<ControlRequestCallback> mPendingCallbacks =
457                new SparseArray<ControlRequestCallback>();
458
459        public Connection(Messenger serviceMessenger) {
460            mServiceMessenger = serviceMessenger;
461            mReceiveHandler = new ReceiveHandler(this);
462            mReceiveMessenger = new Messenger(mReceiveHandler);
463        }
464
465        public boolean register() {
466            mPendingRegisterRequestId = mNextRequestId++;
467            if (!sendRequest(CLIENT_MSG_REGISTER,
468                    mPendingRegisterRequestId,
469                    CLIENT_VERSION_CURRENT, null, null)) {
470                return false;
471            }
472
473            try {
474                mServiceMessenger.getBinder().linkToDeath(this, 0);
475                return true;
476            } catch (RemoteException ex) {
477                binderDied();
478            }
479            return false;
480        }
481
482        public void dispose() {
483            sendRequest(CLIENT_MSG_UNREGISTER, 0, 0, null, null);
484            mReceiveHandler.dispose();
485            mServiceMessenger.getBinder().unlinkToDeath(this, 0);
486
487            mPrivateHandler.post(new Runnable() {
488                @Override
489                public void run() {
490                    failPendingCallbacks();
491                }
492            });
493        }
494
495        void failPendingCallbacks() {
496            int count = 0;
497            for (int i = 0; i < mPendingCallbacks.size(); i++) {
498                mPendingCallbacks.valueAt(i).onError(null, null);
499            }
500            mPendingCallbacks.clear();
501        }
502
503        public boolean onGenericFailure(int requestId) {
504            if (requestId == mPendingRegisterRequestId) {
505                mPendingRegisterRequestId = 0;
506                onConnectionError(this, "Registration failed");
507            }
508            ControlRequestCallback callback = mPendingCallbacks.get(requestId);
509            if (callback != null) {
510                mPendingCallbacks.remove(requestId);
511                callback.onError(null, null);
512            }
513            return true;
514        }
515
516        public boolean onGenericSuccess(int requestId) {
517            return true;
518        }
519
520        public boolean onRegistered(int requestId, int serviceVersion,
521                Bundle descriptorBundle) {
522            if (mServiceVersion == 0
523                    && requestId == mPendingRegisterRequestId
524                    && serviceVersion >= SERVICE_VERSION_1) {
525                mPendingRegisterRequestId = 0;
526                mServiceVersion = serviceVersion;
527                onConnectionDescriptorChanged(this,
528                        MediaRouteProviderDescriptor.fromBundle(descriptorBundle));
529                onConnectionReady(this);
530                return true;
531            }
532            return false;
533        }
534
535        public boolean onDescriptorChanged(Bundle descriptorBundle) {
536            if (mServiceVersion != 0) {
537                onConnectionDescriptorChanged(this,
538                        MediaRouteProviderDescriptor.fromBundle(descriptorBundle));
539                return true;
540            }
541            return false;
542        }
543
544        public boolean onControlRequestSucceeded(int requestId, Bundle data) {
545            ControlRequestCallback callback = mPendingCallbacks.get(requestId);
546            if (callback != null) {
547                mPendingCallbacks.remove(requestId);
548                callback.onResult(data);
549                return true;
550            }
551            return false;
552        }
553
554        public boolean onControlRequestFailed(int requestId, String error, Bundle data) {
555            ControlRequestCallback callback = mPendingCallbacks.get(requestId);
556            if (callback != null) {
557                mPendingCallbacks.remove(requestId);
558                callback.onError(error, data);
559                return true;
560            }
561            return false;
562        }
563
564        @Override
565        public void binderDied() {
566            mPrivateHandler.post(new Runnable() {
567                @Override
568                public void run() {
569                    onConnectionDied(Connection.this);
570                }
571            });
572        }
573
574        public int createRouteController(String routeId, String routeGroupId) {
575            int controllerId = mNextControllerId++;
576            Bundle data = new Bundle();
577            data.putString(CLIENT_DATA_ROUTE_ID, routeId);
578            data.putString(CLIENT_DATA_ROUTE_LIBRARY_GROUP, routeGroupId);
579            sendRequest(CLIENT_MSG_CREATE_ROUTE_CONTROLLER,
580                    mNextRequestId++, controllerId, null, data);
581            return controllerId;
582        }
583
584        public void releaseRouteController(int controllerId) {
585            sendRequest(CLIENT_MSG_RELEASE_ROUTE_CONTROLLER,
586                    mNextRequestId++, controllerId, null, null);
587        }
588
589        public void selectRoute(int controllerId) {
590            sendRequest(CLIENT_MSG_SELECT_ROUTE,
591                    mNextRequestId++, controllerId, null, null);
592        }
593
594        public void unselectRoute(int controllerId, int reason) {
595            Bundle extras = new Bundle();
596            extras.putInt(CLIENT_DATA_UNSELECT_REASON, reason);
597            sendRequest(CLIENT_MSG_UNSELECT_ROUTE,
598                    mNextRequestId++, controllerId, null, extras);
599        }
600
601        public void setVolume(int controllerId, int volume) {
602            Bundle data = new Bundle();
603            data.putInt(CLIENT_DATA_VOLUME, volume);
604            sendRequest(CLIENT_MSG_SET_ROUTE_VOLUME,
605                    mNextRequestId++, controllerId, null, data);
606        }
607
608        public void updateVolume(int controllerId, int delta) {
609            Bundle data = new Bundle();
610            data.putInt(CLIENT_DATA_VOLUME, delta);
611            sendRequest(CLIENT_MSG_UPDATE_ROUTE_VOLUME,
612                    mNextRequestId++, controllerId, null, data);
613        }
614
615        public boolean sendControlRequest(int controllerId, Intent intent,
616                ControlRequestCallback callback) {
617            int requestId = mNextRequestId++;
618            if (sendRequest(CLIENT_MSG_ROUTE_CONTROL_REQUEST,
619                    requestId, controllerId, intent, null)) {
620                if (callback != null) {
621                    mPendingCallbacks.put(requestId, callback);
622                }
623                return true;
624            }
625            return false;
626        }
627
628        public void setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
629            sendRequest(CLIENT_MSG_SET_DISCOVERY_REQUEST,
630                    mNextRequestId++, 0, request != null ? request.asBundle() : null, null);
631        }
632
633        private boolean sendRequest(int what, int requestId, int arg, Object obj, Bundle data) {
634            Message msg = Message.obtain();
635            msg.what = what;
636            msg.arg1 = requestId;
637            msg.arg2 = arg;
638            msg.obj = obj;
639            msg.setData(data);
640            msg.replyTo = mReceiveMessenger;
641            try {
642                mServiceMessenger.send(msg);
643                return true;
644            } catch (DeadObjectException ex) {
645                // The service died.
646            } catch (RemoteException ex) {
647                if (what != CLIENT_MSG_UNREGISTER) {
648                    Log.e(TAG, "Could not send message to service.", ex);
649                }
650            }
651            return false;
652        }
653    }
654
655    private static final class PrivateHandler extends Handler {
656        PrivateHandler() {
657        }
658    }
659
660    /**
661     * Handler that receives messages from the server.
662     * <p>
663     * This inner class is static and only retains a weak reference to the connection
664     * to prevent the client from being leaked in case the service is holding an
665     * active reference to the client's messenger.
666     * </p><p>
667     * This handler should not be used to handle any messages other than those
668     * that come from the service.
669     * </p>
670     */
671    private static final class ReceiveHandler extends Handler {
672        private final WeakReference<Connection> mConnectionRef;
673
674        public ReceiveHandler(Connection connection) {
675            mConnectionRef = new WeakReference<Connection>(connection);
676        }
677
678        public void dispose() {
679            mConnectionRef.clear();
680        }
681
682        @Override
683        public void handleMessage(Message msg) {
684            Connection connection = mConnectionRef.get();
685            if (connection != null) {
686                final int what = msg.what;
687                final int requestId = msg.arg1;
688                final int arg = msg.arg2;
689                final Object obj = msg.obj;
690                final Bundle data = msg.peekData();
691                if (!processMessage(connection, what, requestId, arg, obj, data)) {
692                    if (DEBUG) {
693                        Log.d(TAG, "Unhandled message from server: " + msg);
694                    }
695                }
696            }
697        }
698
699        private boolean processMessage(Connection connection,
700                int what, int requestId, int arg, Object obj, Bundle data) {
701            switch (what) {
702                case SERVICE_MSG_GENERIC_FAILURE:
703                    connection.onGenericFailure(requestId);
704                    return true;
705
706                case SERVICE_MSG_GENERIC_SUCCESS:
707                    connection.onGenericSuccess(requestId);
708                    return true;
709
710                case SERVICE_MSG_REGISTERED:
711                    if (obj == null || obj instanceof Bundle) {
712                        return connection.onRegistered(requestId, arg, (Bundle)obj);
713                    }
714                    break;
715
716                case SERVICE_MSG_DESCRIPTOR_CHANGED:
717                    if (obj == null || obj instanceof Bundle) {
718                        return connection.onDescriptorChanged((Bundle)obj);
719                    }
720                    break;
721
722                case SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED:
723                    if (obj == null || obj instanceof Bundle) {
724                        return connection.onControlRequestSucceeded(
725                                requestId, (Bundle)obj);
726                    }
727                    break;
728
729                case SERVICE_MSG_CONTROL_REQUEST_FAILED:
730                    if (obj == null || obj instanceof Bundle) {
731                        String error = (data == null ? null :
732                                data.getString(SERVICE_DATA_ERROR));
733                        return connection.onControlRequestFailed(
734                                requestId, error, (Bundle)obj);
735                    }
736                    break;
737            }
738            return false;
739        }
740    }
741}
742