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