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