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