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