MediaRouteProviderService.java revision d8e02077fc00aabc7432246e2301626fb342129d
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.app.Service;
20import android.content.Intent;
21import android.os.Handler;
22import android.os.IBinder;
23import android.os.IBinder.DeathRecipient;
24import android.os.Bundle;
25import android.os.DeadObjectException;
26import android.os.Message;
27import android.os.Messenger;
28import android.os.RemoteException;
29import android.util.Log;
30import android.util.SparseArray;
31
32import java.lang.ref.WeakReference;
33import java.util.ArrayList;
34
35import static android.support.v7.media.MediaRouteProviderProtocol.*;
36
37/**
38 * Base class for media route provider services.
39 * <p>
40 * A media router will bind to media route provider services when a callback is added via
41 * {@link MediaRouter#addCallback(MediaRouteSelector, MediaRouter.Callback, int)} with a discovery
42 * flag: {@link MediaRouter#CALLBACK_FLAG_REQUEST_DISCOVERY},
43 * {@link MediaRouter#CALLBACK_FLAG_FORCE_DISCOVERY}, or
44 * {@link MediaRouter#CALLBACK_FLAG_PERFORM_ACTIVE_SCAN}, and will unbind when the callback
45 * is removed via {@link MediaRouter#removeCallback(MediaRouter.Callback)}.
46 * </p><p>
47 * To implement your own media route provider service, extend this class and
48 * override the {@link #onCreateMediaRouteProvider} method to return an
49 * instance of your {@link MediaRouteProvider}.
50 * </p><p>
51 * Declare your media route provider service in your application manifest
52 * like this:
53 * </p>
54 * <pre>
55 *   &lt;service android:name=".MyMediaRouteProviderService"
56 *           android:label="@string/my_media_route_provider_service">
57 *       &lt;intent-filter>
58 *           &lt;action android:name="android.media.MediaRouteProviderService" />
59 *       &lt;/intent-filter>
60 *   &lt;/service>
61 * </pre>
62 */
63public abstract class MediaRouteProviderService extends Service {
64    private static final String TAG = "MediaRouteProviderSrv"; // max. 23 chars
65    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
66
67    private final ArrayList<ClientRecord> mClients = new ArrayList<ClientRecord>();
68    private final ReceiveHandler mReceiveHandler;
69    private final Messenger mReceiveMessenger;
70    private final PrivateHandler mPrivateHandler;
71    private final ProviderCallback mProviderCallback;
72
73    private MediaRouteProvider mProvider;
74    private MediaRouteDiscoveryRequest mCompositeDiscoveryRequest;
75
76    /**
77     * The {@link Intent} that must be declared as handled by the service.
78     * Put this in your manifest.
79     */
80    public static final String SERVICE_INTERFACE = MediaRouteProviderProtocol.SERVICE_INTERFACE;
81
82    /*
83     * Private messages used internally.  (Yes, you can renumber these.)
84     */
85
86    private static final int PRIVATE_MSG_CLIENT_DIED = 1;
87
88    /**
89     * Creates a media route provider service.
90     */
91    public MediaRouteProviderService() {
92        mReceiveHandler = new ReceiveHandler(this);
93        mReceiveMessenger = new Messenger(mReceiveHandler);
94        mPrivateHandler = new PrivateHandler();
95        mProviderCallback = new ProviderCallback();
96    }
97
98    /**
99     * Called by the system when it is time to create the media route provider.
100     *
101     * @return The media route provider offered by this service, or null if
102     * this service has decided not to offer a media route provider.
103     */
104    public abstract MediaRouteProvider onCreateMediaRouteProvider();
105
106    /**
107     * Gets the media route provider offered by this service.
108     *
109     * @return The media route provider offered by this service, or null if
110     * it has not yet been created.
111     *
112     * @see #onCreateMediaRouteProvider()
113     */
114    public MediaRouteProvider getMediaRouteProvider() {
115        return mProvider;
116    }
117
118    @Override
119    public IBinder onBind(Intent intent) {
120        if (intent.getAction().equals(SERVICE_INTERFACE)) {
121            if (mProvider == null) {
122                MediaRouteProvider provider = onCreateMediaRouteProvider();
123                if (provider != null) {
124                    String providerPackage = provider.getMetadata().getPackageName();
125                    if (!providerPackage.equals(getPackageName())) {
126                        throw new IllegalStateException("onCreateMediaRouteProvider() returned "
127                                + "a provider whose package name does not match the package "
128                                + "name of the service.  A media route provider service can "
129                                + "only export its own media route providers.  "
130                                + "Provider package name: " + providerPackage
131                                + ".  Service package name: " + getPackageName() + ".");
132                    }
133                    mProvider = provider;
134                    mProvider.setCallback(mProviderCallback);
135                }
136            }
137            if (mProvider != null) {
138                return mReceiveMessenger.getBinder();
139            }
140        }
141        return null;
142    }
143
144    private boolean onRegisterClient(Messenger messenger, int requestId, int version) {
145        if (version >= CLIENT_VERSION_1) {
146            int index = findClient(messenger);
147            if (index < 0) {
148                ClientRecord client = new ClientRecord(messenger, version);
149                if (client.register()) {
150                    mClients.add(client);
151                    if (DEBUG) {
152                        Log.d(TAG, client + ": Registered, version=" + version);
153                    }
154                    if (requestId != 0) {
155                        MediaRouteProviderDescriptor descriptor = mProvider.getDescriptor();
156                        sendReply(messenger, SERVICE_MSG_REGISTERED,
157                                requestId, SERVICE_VERSION_CURRENT,
158                                descriptor != null ? descriptor.asBundle() : null, null);
159                    }
160                    return true;
161                }
162            }
163        }
164        return false;
165    }
166
167    private boolean onUnregisterClient(Messenger messenger, int requestId) {
168        int index = findClient(messenger);
169        if (index >= 0) {
170            ClientRecord client = mClients.remove(index);
171            if (DEBUG) {
172                Log.d(TAG, client + ": Unregistered");
173            }
174            client.dispose();
175            sendGenericSuccess(messenger, requestId);
176            return true;
177        }
178        return false;
179    }
180
181    private void onBinderDied(Messenger messenger) {
182        int index = findClient(messenger);
183        if (index >= 0) {
184            ClientRecord client = mClients.remove(index);
185            if (DEBUG) {
186                Log.d(TAG, client + ": Binder died");
187            }
188            client.dispose();
189        }
190    }
191
192    private boolean onCreateRouteController(Messenger messenger, int requestId,
193            int controllerId, String routeId) {
194        ClientRecord client = getClient(messenger);
195        if (client != null) {
196            if (client.createRouteController(routeId, controllerId)) {
197                if (DEBUG) {
198                    Log.d(TAG, client + ": Route controller created"
199                            + ", controllerId=" + controllerId + ", routeId=" + routeId);
200                }
201                sendGenericSuccess(messenger, requestId);
202                return true;
203            }
204        }
205        return false;
206    }
207
208    private boolean onReleaseRouteController(Messenger messenger, int requestId,
209            int controllerId) {
210        ClientRecord client = getClient(messenger);
211        if (client != null) {
212            if (client.releaseRouteController(controllerId)) {
213                if (DEBUG) {
214                    Log.d(TAG, client + ": Route controller released"
215                            + ", controllerId=" + controllerId);
216                }
217                sendGenericSuccess(messenger, requestId);
218                return true;
219            }
220        }
221        return false;
222    }
223
224    private boolean onSelectRoute(Messenger messenger, int requestId,
225            int controllerId) {
226        ClientRecord client = getClient(messenger);
227        if (client != null) {
228            MediaRouteProvider.RouteController controller =
229                    client.getRouteController(controllerId);
230            if (controller != null) {
231                controller.onSelect();
232                if (DEBUG) {
233                    Log.d(TAG, client + ": Route selected"
234                            + ", controllerId=" + controllerId);
235                }
236                sendGenericSuccess(messenger, requestId);
237                return true;
238            }
239        }
240        return false;
241    }
242
243    private boolean onUnselectRoute(Messenger messenger, int requestId,
244            int controllerId, int reason) {
245        ClientRecord client = getClient(messenger);
246        if (client != null) {
247            MediaRouteProvider.RouteController controller =
248                    client.getRouteController(controllerId);
249            if (controller != null) {
250                controller.onUnselect(reason);
251                if (DEBUG) {
252                    Log.d(TAG, client + ": Route unselected"
253                            + ", controllerId=" + controllerId);
254                }
255                sendGenericSuccess(messenger, requestId);
256                return true;
257            }
258        }
259        return false;
260    }
261
262    private boolean onSetRouteVolume(Messenger messenger, int requestId,
263            int controllerId, int volume) {
264        ClientRecord client = getClient(messenger);
265        if (client != null) {
266            MediaRouteProvider.RouteController controller =
267                    client.getRouteController(controllerId);
268            if (controller != null) {
269                controller.onSetVolume(volume);
270                if (DEBUG) {
271                    Log.d(TAG, client + ": Route volume changed"
272                            + ", controllerId=" + controllerId + ", volume=" + volume);
273                }
274                sendGenericSuccess(messenger, requestId);
275                return true;
276            }
277        }
278        return false;
279    }
280
281    private boolean onUpdateRouteVolume(Messenger messenger, int requestId,
282            int controllerId, int delta) {
283        ClientRecord client = getClient(messenger);
284        if (client != null) {
285            MediaRouteProvider.RouteController controller =
286                    client.getRouteController(controllerId);
287            if (controller != null) {
288                controller.onUpdateVolume(delta);
289                if (DEBUG) {
290                    Log.d(TAG, client + ": Route volume updated"
291                            + ", controllerId=" + controllerId + ", delta=" + delta);
292                }
293                sendGenericSuccess(messenger, requestId);
294                return true;
295            }
296        }
297        return false;
298    }
299
300    private boolean onRouteControlRequest(final Messenger messenger, final int requestId,
301            final int controllerId, final Intent intent) {
302        final ClientRecord client = getClient(messenger);
303        if (client != null) {
304            MediaRouteProvider.RouteController controller =
305                    client.getRouteController(controllerId);
306            if (controller != null) {
307                MediaRouter.ControlRequestCallback callback = null;
308                if (requestId != 0) {
309                    callback = new MediaRouter.ControlRequestCallback() {
310                        @Override
311                        public void onResult(Bundle data) {
312                            if (DEBUG) {
313                                Log.d(TAG, client + ": Route control request succeeded"
314                                        + ", controllerId=" + controllerId
315                                        + ", intent=" + intent
316                                        + ", data=" + data);
317                            }
318                            if (findClient(messenger) >= 0) {
319                                sendReply(messenger, SERVICE_MSG_CONTROL_REQUEST_SUCCEEDED,
320                                        requestId, 0, data, null);
321                            }
322                        }
323
324                        @Override
325                        public void onError(String error, Bundle data) {
326                            if (DEBUG) {
327                                Log.d(TAG, client + ": Route control request failed"
328                                        + ", controllerId=" + controllerId
329                                        + ", intent=" + intent
330                                        + ", error=" + error + ", data=" + data);
331                            }
332                            if (findClient(messenger) >= 0) {
333                                if (error != null) {
334                                    Bundle bundle = new Bundle();
335                                    bundle.putString(SERVICE_DATA_ERROR, error);
336                                    sendReply(messenger, SERVICE_MSG_CONTROL_REQUEST_FAILED,
337                                            requestId, 0, data, bundle);
338                                } else {
339                                    sendReply(messenger, SERVICE_MSG_CONTROL_REQUEST_FAILED,
340                                            requestId, 0, data, null);
341                                }
342                            }
343                        }
344                    };
345                }
346                if (controller.onControlRequest(intent, callback)) {
347                    if (DEBUG) {
348                        Log.d(TAG, client + ": Route control request delivered"
349                                + ", controllerId=" + controllerId + ", intent=" + intent);
350                    }
351                    return true;
352                }
353            }
354        }
355        return false;
356    }
357
358    private boolean onSetDiscoveryRequest(Messenger messenger, int requestId,
359            MediaRouteDiscoveryRequest request) {
360        ClientRecord client = getClient(messenger);
361        if (client != null) {
362            boolean actuallyChanged = client.setDiscoveryRequest(request);
363            if (DEBUG) {
364                Log.d(TAG, client + ": Set discovery request, request=" + request
365                        + ", actuallyChanged=" + actuallyChanged
366                        + ", compositeDiscoveryRequest=" + mCompositeDiscoveryRequest);
367            }
368            sendGenericSuccess(messenger, requestId);
369            return true;
370        }
371        return false;
372    }
373
374    private void sendDescriptorChanged(MediaRouteProviderDescriptor descriptor) {
375        Bundle descriptorBundle = descriptor != null ? descriptor.asBundle() : null;
376        final int count = mClients.size();
377        for (int i = 0; i < count; i++) {
378            ClientRecord client = mClients.get(i);
379            sendReply(client.mMessenger, SERVICE_MSG_DESCRIPTOR_CHANGED, 0, 0,
380                    descriptorBundle, null);
381            if (DEBUG) {
382                Log.d(TAG, client + ": Sent descriptor change event, descriptor=" + descriptor);
383            }
384        }
385    }
386
387    private boolean updateCompositeDiscoveryRequest() {
388        MediaRouteDiscoveryRequest composite = null;
389        MediaRouteSelector.Builder selectorBuilder = null;
390        boolean activeScan = false;
391        final int count = mClients.size();
392        for (int i = 0; i < count; i++) {
393            MediaRouteDiscoveryRequest request = mClients.get(i).mDiscoveryRequest;
394            if (request != null
395                    && (!request.getSelector().isEmpty() || request.isActiveScan())) {
396                activeScan |= request.isActiveScan();
397                if (composite == null) {
398                    composite = request;
399                } else {
400                    if (selectorBuilder == null) {
401                        selectorBuilder = new MediaRouteSelector.Builder(composite.getSelector());
402                    }
403                    selectorBuilder.addSelector(request.getSelector());
404                }
405            }
406        }
407        if (selectorBuilder != null) {
408            composite = new MediaRouteDiscoveryRequest(selectorBuilder.build(), activeScan);
409        }
410        if (mCompositeDiscoveryRequest != composite
411                && (mCompositeDiscoveryRequest == null
412                        || !mCompositeDiscoveryRequest.equals(composite))) {
413            mCompositeDiscoveryRequest = composite;
414            mProvider.setDiscoveryRequest(composite);
415            return true;
416        }
417        return false;
418    }
419
420    private ClientRecord getClient(Messenger messenger) {
421        int index = findClient(messenger);
422        return index >= 0 ? mClients.get(index) : null;
423    }
424
425    private int findClient(Messenger messenger) {
426        final int count = mClients.size();
427        for (int i = 0; i < count; i++) {
428            ClientRecord client = mClients.get(i);
429            if (client.hasMessenger(messenger)) {
430                return i;
431            }
432        }
433        return -1;
434    }
435
436    private static void sendGenericFailure(Messenger messenger, int requestId) {
437        if (requestId != 0) {
438            sendReply(messenger, SERVICE_MSG_GENERIC_FAILURE, requestId, 0, null, null);
439        }
440    }
441
442    private static void sendGenericSuccess(Messenger messenger, int requestId) {
443        if (requestId != 0) {
444            sendReply(messenger, SERVICE_MSG_GENERIC_SUCCESS, requestId, 0, null, null);
445        }
446    }
447
448    private static void sendReply(Messenger messenger, int what,
449            int requestId, int arg, Object obj, Bundle data) {
450        Message msg = Message.obtain();
451        msg.what = what;
452        msg.arg1 = requestId;
453        msg.arg2 = arg;
454        msg.obj = obj;
455        msg.setData(data);
456        try {
457            messenger.send(msg);
458        } catch (DeadObjectException ex) {
459            // The client died.
460        } catch (RemoteException ex) {
461            Log.e(TAG, "Could not send message to " + getClientId(messenger), ex);
462        }
463    }
464
465    private static String getClientId(Messenger messenger) {
466        return "Client connection " + messenger.getBinder().toString();
467    }
468
469    private final class PrivateHandler extends Handler {
470        @Override
471        public void handleMessage(Message msg) {
472            switch (msg.what) {
473                case PRIVATE_MSG_CLIENT_DIED:
474                    onBinderDied((Messenger)msg.obj);
475                    break;
476            }
477        }
478    }
479
480    private final class ProviderCallback extends MediaRouteProvider.Callback {
481        @Override
482        public void onDescriptorChanged(MediaRouteProvider provider,
483                MediaRouteProviderDescriptor descriptor) {
484            sendDescriptorChanged(descriptor);
485        }
486    }
487
488    private final class ClientRecord implements DeathRecipient {
489        public final Messenger mMessenger;
490        public final int mVersion;
491        public MediaRouteDiscoveryRequest mDiscoveryRequest;
492
493        private final SparseArray<MediaRouteProvider.RouteController> mControllers =
494                new SparseArray<MediaRouteProvider.RouteController>();
495
496        public ClientRecord(Messenger messenger, int version) {
497            mMessenger = messenger;
498            mVersion = version;
499        }
500
501        public boolean register() {
502            try {
503                mMessenger.getBinder().linkToDeath(this, 0);
504                return true;
505            } catch (RemoteException ex) {
506                binderDied();
507            }
508            return false;
509        }
510
511        public void dispose() {
512            int count = mControllers.size();
513            for (int i = 0; i < count; i++) {
514                mControllers.valueAt(i).onRelease();
515            }
516            mControllers.clear();
517
518            mMessenger.getBinder().unlinkToDeath(this, 0);
519
520            setDiscoveryRequest(null);
521        }
522
523        public boolean hasMessenger(Messenger other) {
524            return mMessenger.getBinder() == other.getBinder();
525        }
526
527        public boolean createRouteController(String routeId, int controllerId) {
528            if (mControllers.indexOfKey(controllerId) < 0) {
529                MediaRouteProvider.RouteController controller =
530                        mProvider.onCreateRouteController(routeId);
531                if (controller != null) {
532                    mControllers.put(controllerId, controller);
533                    return true;
534                }
535            }
536            return false;
537        }
538
539        public boolean releaseRouteController(int controllerId) {
540            MediaRouteProvider.RouteController controller = mControllers.get(controllerId);
541            if (controller != null) {
542                mControllers.remove(controllerId);
543                controller.onRelease();
544                return true;
545            }
546            return false;
547        }
548
549        public MediaRouteProvider.RouteController getRouteController(int controllerId) {
550            return mControllers.get(controllerId);
551        }
552
553        public boolean setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
554            if (mDiscoveryRequest != request
555                    && (mDiscoveryRequest == null || !mDiscoveryRequest.equals(request))) {
556                mDiscoveryRequest = request;
557                return updateCompositeDiscoveryRequest();
558            }
559            return false;
560        }
561
562        // Runs on a binder thread.
563        @Override
564        public void binderDied() {
565            mPrivateHandler.obtainMessage(PRIVATE_MSG_CLIENT_DIED, mMessenger).sendToTarget();
566        }
567
568        @Override
569        public String toString() {
570            return getClientId(mMessenger);
571        }
572    }
573
574    /**
575     * Handler that receives messages from clients.
576     * <p>
577     * This inner class is static and only retains a weak reference to the service
578     * to prevent the service from being leaked in case one of the clients is holding an
579     * active reference to the server's messenger.
580     * </p><p>
581     * This handler should not be used to handle any messages other than those
582     * that come from the client.
583     * </p>
584     */
585    private static final class ReceiveHandler extends Handler {
586        private final WeakReference<MediaRouteProviderService> mServiceRef;
587
588        public ReceiveHandler(MediaRouteProviderService service) {
589            mServiceRef = new WeakReference<MediaRouteProviderService>(service);
590        }
591
592        @Override
593        public void handleMessage(Message msg) {
594            final Messenger messenger = msg.replyTo;
595            if (isValidRemoteMessenger(messenger)) {
596                final int what = msg.what;
597                final int requestId = msg.arg1;
598                final int arg = msg.arg2;
599                final Object obj = msg.obj;
600                final Bundle data = msg.peekData();
601                if (!processMessage(what, messenger, requestId, arg, obj, data)) {
602                    if (DEBUG) {
603                        Log.d(TAG, getClientId(messenger) + ": Message failed, what=" + what
604                                + ", requestId=" + requestId + ", arg=" + arg
605                                + ", obj=" + obj + ", data=" + data);
606                    }
607                    sendGenericFailure(messenger, requestId);
608                }
609            } else {
610                if (DEBUG) {
611                    Log.d(TAG, "Ignoring message without valid reply messenger.");
612                }
613            }
614        }
615
616        private boolean processMessage(int what,
617                Messenger messenger, int requestId, int arg, Object obj, Bundle data) {
618            MediaRouteProviderService service = mServiceRef.get();
619            if (service != null) {
620                switch (what) {
621                    case CLIENT_MSG_REGISTER:
622                        return service.onRegisterClient(messenger, requestId, arg);
623
624                    case CLIENT_MSG_UNREGISTER:
625                        return service.onUnregisterClient(messenger, requestId);
626
627                    case CLIENT_MSG_CREATE_ROUTE_CONTROLLER: {
628                        String routeId = data.getString(CLIENT_DATA_ROUTE_ID);
629                        if (routeId != null) {
630                            return service.onCreateRouteController(
631                                    messenger, requestId, arg, routeId);
632                        }
633                        break;
634                    }
635
636                    case CLIENT_MSG_RELEASE_ROUTE_CONTROLLER:
637                        return service.onReleaseRouteController(messenger, requestId, arg);
638
639                    case CLIENT_MSG_SELECT_ROUTE:
640                        return service.onSelectRoute(messenger, requestId, arg);
641
642                    case CLIENT_MSG_UNSELECT_ROUTE:
643                        int reason = data == null ?
644                                MediaRouter.UNSELECT_REASON_UNKNOWN
645                                : data.getInt(CLIENT_DATA_UNSELECT_REASON,
646                                        MediaRouter.UNSELECT_REASON_UNKNOWN);
647                        return service.onUnselectRoute(messenger, requestId, arg, reason);
648
649                    case CLIENT_MSG_SET_ROUTE_VOLUME: {
650                        int volume = data.getInt(CLIENT_DATA_VOLUME, -1);
651                        if (volume >= 0) {
652                            return service.onSetRouteVolume(
653                                    messenger, requestId, arg, volume);
654                        }
655                        break;
656                    }
657
658                    case CLIENT_MSG_UPDATE_ROUTE_VOLUME: {
659                        int delta = data.getInt(CLIENT_DATA_VOLUME, 0);
660                        if (delta != 0) {
661                            return service.onUpdateRouteVolume(
662                                    messenger, requestId, arg, delta);
663                        }
664                        break;
665                    }
666
667                    case CLIENT_MSG_ROUTE_CONTROL_REQUEST:
668                        if (obj instanceof Intent) {
669                            return service.onRouteControlRequest(
670                                    messenger, requestId, arg, (Intent)obj);
671                        }
672                        break;
673
674                    case CLIENT_MSG_SET_DISCOVERY_REQUEST: {
675                        if (obj == null || obj instanceof Bundle) {
676                            MediaRouteDiscoveryRequest request =
677                                    MediaRouteDiscoveryRequest.fromBundle((Bundle)obj);
678                            return service.onSetDiscoveryRequest(
679                                    messenger, requestId,
680                                    request != null && request.isValid() ? request : null);
681                        }
682                    }
683                }
684            }
685            return false;
686        }
687    }
688}
689