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