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