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));
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            onUnselect(MediaRouter.UNSELECT_REASON_UNKNOWN);
348        }
349
350        @Override
351        public void onUnselect(int reason) {
352            mSelected = false;
353            if (mConnection != null) {
354                mConnection.unselectRoute(mControllerId, reason);
355            }
356        }
357
358        @Override
359        public void onSetVolume(int volume) {
360            if (mConnection != null) {
361                mConnection.setVolume(mControllerId, volume);
362            } else {
363                mPendingSetVolume = volume;
364                mPendingUpdateVolumeDelta = 0;
365            }
366        }
367
368        @Override
369        public void onUpdateVolume(int delta) {
370            if (mConnection != null) {
371                mConnection.updateVolume(mControllerId, delta);
372            } else {
373                mPendingUpdateVolumeDelta += delta;
374            }
375        }
376
377        @Override
378        public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
379            if (mConnection != null) {
380                return mConnection.sendControlRequest(mControllerId, intent, callback);
381            }
382            return false;
383        }
384    }
385
386    private final class Connection implements DeathRecipient {
387        private final Messenger mServiceMessenger;
388        private final ReceiveHandler mReceiveHandler;
389        private final Messenger mReceiveMessenger;
390
391        private int mNextRequestId = 1;
392        private int mNextControllerId = 1;
393        private int mServiceVersion; // non-zero when registration complete
394
395        private int mPendingRegisterRequestId;
396        private final SparseArray<ControlRequestCallback> mPendingCallbacks =
397                new SparseArray<ControlRequestCallback>();
398
399        public Connection(Messenger serviceMessenger) {
400            mServiceMessenger = serviceMessenger;
401            mReceiveHandler = new ReceiveHandler(this);
402            mReceiveMessenger = new Messenger(mReceiveHandler);
403        }
404
405        public boolean register() {
406            mPendingRegisterRequestId = mNextRequestId++;
407            if (!sendRequest(CLIENT_MSG_REGISTER,
408                    mPendingRegisterRequestId,
409                    CLIENT_VERSION_CURRENT, null, null)) {
410                return false;
411            }
412
413            try {
414                mServiceMessenger.getBinder().linkToDeath(this, 0);
415                return true;
416            } catch (RemoteException ex) {
417                binderDied();
418            }
419            return false;
420        }
421
422        public void dispose() {
423            sendRequest(CLIENT_MSG_UNREGISTER, 0, 0, null, null);
424            mReceiveHandler.dispose();
425            mServiceMessenger.getBinder().unlinkToDeath(this, 0);
426
427            mPrivateHandler.post(new Runnable() {
428                @Override
429                public void run() {
430                    failPendingCallbacks();
431                }
432            });
433        }
434
435        private void failPendingCallbacks() {
436            int count = 0;
437            for (int i = 0; i < mPendingCallbacks.size(); i++) {
438                mPendingCallbacks.valueAt(i).onError(null, null);
439            }
440            mPendingCallbacks.clear();
441        }
442
443        public boolean onGenericFailure(int requestId) {
444            if (requestId == mPendingRegisterRequestId) {
445                mPendingRegisterRequestId = 0;
446                onConnectionError(this, "Registation failed");
447            }
448            ControlRequestCallback callback = mPendingCallbacks.get(requestId);
449            if (callback != null) {
450                mPendingCallbacks.remove(requestId);
451                callback.onError(null, null);
452            }
453            return true;
454        }
455
456        public boolean onGenericSuccess(int requestId) {
457            return true;
458        }
459
460        public boolean onRegistered(int requestId, int serviceVersion,
461                Bundle descriptorBundle) {
462            if (mServiceVersion == 0
463                    && requestId == mPendingRegisterRequestId
464                    && serviceVersion >= SERVICE_VERSION_1) {
465                mPendingRegisterRequestId = 0;
466                mServiceVersion = serviceVersion;
467                onConnectionDescriptorChanged(this,
468                        MediaRouteProviderDescriptor.fromBundle(descriptorBundle));
469                onConnectionReady(this);
470                return true;
471            }
472            return false;
473        }
474
475        public boolean onDescriptorChanged(Bundle descriptorBundle) {
476            if (mServiceVersion != 0) {
477                onConnectionDescriptorChanged(this,
478                        MediaRouteProviderDescriptor.fromBundle(descriptorBundle));
479                return true;
480            }
481            return false;
482        }
483
484        public boolean onControlRequestSucceeded(int requestId, Bundle data) {
485            ControlRequestCallback callback = mPendingCallbacks.get(requestId);
486            if (callback != null) {
487                mPendingCallbacks.remove(requestId);
488                callback.onResult(data);
489                return true;
490            }
491            return false;
492        }
493
494        public boolean onControlRequestFailed(int requestId, String error, Bundle data) {
495            ControlRequestCallback callback = mPendingCallbacks.get(requestId);
496            if (callback != null) {
497                mPendingCallbacks.remove(requestId);
498                callback.onError(error, data);
499                return true;
500            }
501            return false;
502        }
503
504        @Override
505        public void binderDied() {
506            mPrivateHandler.post(new Runnable() {
507                @Override
508                public void run() {
509                    onConnectionDied(Connection.this);
510                }
511            });
512        }
513
514        public int createRouteController(String routeId) {
515            int controllerId = mNextControllerId++;
516            Bundle data = new Bundle();
517            data.putString(CLIENT_DATA_ROUTE_ID, routeId);
518            sendRequest(CLIENT_MSG_CREATE_ROUTE_CONTROLLER,
519                    mNextRequestId++, controllerId, null, data);
520            return controllerId;
521        }
522
523        public void releaseRouteController(int controllerId) {
524            sendRequest(CLIENT_MSG_RELEASE_ROUTE_CONTROLLER,
525                    mNextRequestId++, controllerId, null, null);
526        }
527
528        public void selectRoute(int controllerId) {
529            sendRequest(CLIENT_MSG_SELECT_ROUTE,
530                    mNextRequestId++, controllerId, null, null);
531        }
532
533        public void unselectRoute(int controllerId, int reason) {
534            Bundle extras = new Bundle();
535            extras.putInt(CLIENT_DATA_UNSELECT_REASON, reason);
536            sendRequest(CLIENT_MSG_UNSELECT_ROUTE,
537                    mNextRequestId++, controllerId, null, extras);
538        }
539
540        public void setVolume(int controllerId, int volume) {
541            Bundle data = new Bundle();
542            data.putInt(CLIENT_DATA_VOLUME, volume);
543            sendRequest(CLIENT_MSG_SET_ROUTE_VOLUME,
544                    mNextRequestId++, controllerId, null, data);
545        }
546
547        public void updateVolume(int controllerId, int delta) {
548            Bundle data = new Bundle();
549            data.putInt(CLIENT_DATA_VOLUME, delta);
550            sendRequest(CLIENT_MSG_UPDATE_ROUTE_VOLUME,
551                    mNextRequestId++, controllerId, null, data);
552        }
553
554        public boolean sendControlRequest(int controllerId, Intent intent,
555                ControlRequestCallback callback) {
556            int requestId = mNextRequestId++;
557            if (sendRequest(CLIENT_MSG_ROUTE_CONTROL_REQUEST,
558                    requestId, controllerId, intent, null)) {
559                if (callback != null) {
560                    mPendingCallbacks.put(requestId, callback);
561                }
562                return true;
563            }
564            return false;
565        }
566
567        public void setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
568            sendRequest(CLIENT_MSG_SET_DISCOVERY_REQUEST,
569                    mNextRequestId++, 0, request != null ? request.asBundle() : null, null);
570        }
571
572        private boolean sendRequest(int what, int requestId, int arg, Object obj, Bundle data) {
573            Message msg = Message.obtain();
574            msg.what = what;
575            msg.arg1 = requestId;
576            msg.arg2 = arg;
577            msg.obj = obj;
578            msg.setData(data);
579            msg.replyTo = mReceiveMessenger;
580            try {
581                mServiceMessenger.send(msg);
582                return true;
583            } catch (DeadObjectException ex) {
584                // The service died.
585            } catch (RemoteException ex) {
586                if (what != CLIENT_MSG_UNREGISTER) {
587                    Log.e(TAG, "Could not send message to service.", ex);
588                }
589            }
590            return false;
591        }
592    }
593
594    private final class PrivateHandler extends Handler {
595    }
596
597    /**
598     * Handler that receives messages from the server.
599     * <p>
600     * This inner class is static and only retains a weak reference to the connection
601     * to prevent the client from being leaked in case the service is holding an
602     * active reference to the client's messenger.
603     * </p><p>
604     * This handler should not be used to handle any messages other than those
605     * that come from the service.
606     * </p>
607     */
608    private static final class ReceiveHandler extends Handler {
609        private final WeakReference<Connection> mConnectionRef;
610
611        public ReceiveHandler(Connection connection) {
612            mConnectionRef = new WeakReference<Connection>(connection);
613        }
614
615        public void dispose() {
616            mConnectionRef.clear();
617        }
618
619        @Override
620        public void handleMessage(Message msg) {
621            Connection connection = mConnectionRef.get();
622            if (connection != null) {
623                final int what = msg.what;
624                final int requestId = msg.arg1;
625                final int arg = msg.arg2;
626                final Object obj = msg.obj;
627                final Bundle data = msg.peekData();
628                if (!processMessage(connection, what, requestId, arg, obj, data)) {
629                    if (DEBUG) {
630                        Log.d(TAG, "Unhandled message from server: " + msg);
631                    }
632                }
633            }
634        }
635
636        private boolean processMessage(Connection connection,
637                int what, int requestId, int arg, Object obj, Bundle data) {
638            switch (what) {
639                case SERVICE_MSG_GENERIC_FAILURE:
640                    connection.onGenericFailure(requestId);
641                    return true;
642
643                case SERVICE_MSG_GENERIC_SUCCESS:
644                    connection.onGenericSuccess(requestId);
645                    return true;
646
647                case SERVICE_MSG_REGISTERED:
648                    if (obj == null || obj instanceof Bundle) {
649                        return connection.onRegistered(requestId, arg, (Bundle)obj);
650                    }
651                    break;
652
653                case SERVICE_MSG_DESCRIPTOR_CHANGED:
654                    if (obj == null || obj instanceof Bundle) {
655                        return connection.onDescriptorChanged((Bundle)obj);
656                    }
657                    break;
658
659                case SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED:
660                    if (obj == null || obj instanceof Bundle) {
661                        return connection.onControlRequestSucceeded(
662                                requestId, (Bundle)obj);
663                    }
664                    break;
665
666                case SERVICE_MSG_CONTROL_REQUEST_FAILED:
667                    if (obj == null || obj instanceof Bundle) {
668                        String error = (data == null ? null :
669                                data.getString(SERVICE_DATA_ERROR));
670                        return connection.onControlRequestFailed(
671                                requestId, error, (Bundle)obj);
672                    }
673                    break;
674            }
675            return false;
676        }
677    }
678}
679