summaryrefslogtreecommitdiff
path: root/com/android/support/mediarouter/media/MediaRouteProvider.java
blob: 91a2e1ac8c6d675ead86756aa62251544c8225e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.support.mediarouter.media;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.support.v4.util.ObjectsCompat;

import com.android.support.mediarouter.media.MediaRouter.ControlRequestCallback;

/**
 * Media route providers are used to publish additional media routes for
 * use within an application.  Media route providers may also be declared
 * as a service to publish additional media routes to all applications
 * in the system.
 * <p>
 * The purpose of a media route provider is to discover media routes that satisfy
 * the criteria specified by the current {@link MediaRouteDiscoveryRequest} and publish a
 * {@link MediaRouteProviderDescriptor} with information about each route by calling
 * {@link #setDescriptor} to notify the currently registered {@link Callback}.
 * </p><p>
 * The provider should watch for changes to the discovery request by implementing
 * {@link #onDiscoveryRequestChanged} and updating the set of routes that it is
 * attempting to discover.  It should also handle route control requests such
 * as volume changes or {@link MediaControlIntent media control intents}
 * by implementing {@link #onCreateRouteController} to return a {@link RouteController}
 * for a particular route.
 * </p><p>
 * A media route provider may be used privately within the scope of a single
 * application process by calling {@link MediaRouter#addProvider MediaRouter.addProvider}
 * to add it to the local {@link MediaRouter}.  A media route provider may also be made
 * available globally to all applications by registering a {@link MediaRouteProviderService}
 * in the provider's manifest.  When the media route provider is registered
 * as a service, all applications that use the media router API will be able to
 * discover and used the provider's routes without having to install anything else.
 * </p><p>
 * This object must only be accessed on the main thread.
 * </p>
 */
public abstract class MediaRouteProvider {
    static final int MSG_DELIVER_DESCRIPTOR_CHANGED = 1;
    static final int MSG_DELIVER_DISCOVERY_REQUEST_CHANGED = 2;

    private final Context mContext;
    private final ProviderMetadata mMetadata;
    private final ProviderHandler mHandler = new ProviderHandler();

    private Callback mCallback;

    private MediaRouteDiscoveryRequest mDiscoveryRequest;
    private boolean mPendingDiscoveryRequestChange;

    private MediaRouteProviderDescriptor mDescriptor;
    private boolean mPendingDescriptorChange;

    /**
     * Creates a media route provider.
     *
     * @param context The context.
     */
    public MediaRouteProvider(@NonNull Context context) {
        this(context, null);
    }

    MediaRouteProvider(Context context, ProviderMetadata metadata) {
        if (context == null) {
            throw new IllegalArgumentException("context must not be null");
        }

        mContext = context;
        if (metadata == null) {
            mMetadata = new ProviderMetadata(new ComponentName(context, getClass()));
        } else {
            mMetadata = metadata;
        }
    }

    /**
     * Gets the context of the media route provider.
     */
    public final Context getContext() {
        return mContext;
    }

    /**
     * Gets the provider's handler which is associated with the main thread.
     */
    public final Handler getHandler() {
        return mHandler;
    }

    /**
     * Gets some metadata about the provider's implementation.
     */
    public final ProviderMetadata getMetadata() {
        return mMetadata;
    }

    /**
     * Sets a callback to invoke when the provider's descriptor changes.
     *
     * @param callback The callback to use, or null if none.
     */
    public final void setCallback(@Nullable Callback callback) {
        MediaRouter.checkCallingThread();
        mCallback = callback;
    }

    /**
     * Gets the current discovery request which informs the provider about the
     * kinds of routes to discover and whether to perform active scanning.
     *
     * @return The current discovery request, or null if no discovery is needed at this time.
     *
     * @see #onDiscoveryRequestChanged
     */
    @Nullable
    public final MediaRouteDiscoveryRequest getDiscoveryRequest() {
        return mDiscoveryRequest;
    }

    /**
     * Sets a discovery request to inform the provider about the kinds of
     * routes that its clients would like to discover and whether to perform active scanning.
     *
     * @param request The discovery request, or null if no discovery is needed at this time.
     *
     * @see #onDiscoveryRequestChanged
     */
    public final void setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
        MediaRouter.checkCallingThread();

        if (ObjectsCompat.equals(mDiscoveryRequest, request)) {
            return;
        }

        mDiscoveryRequest = request;
        if (!mPendingDiscoveryRequestChange) {
            mPendingDiscoveryRequestChange = true;
            mHandler.sendEmptyMessage(MSG_DELIVER_DISCOVERY_REQUEST_CHANGED);
        }
    }

    void deliverDiscoveryRequestChanged() {
        mPendingDiscoveryRequestChange = false;
        onDiscoveryRequestChanged(mDiscoveryRequest);
    }

    /**
     * Called by the media router when the {@link MediaRouteDiscoveryRequest discovery request}
     * has changed.
     * <p>
     * Whenever an applications calls {@link MediaRouter#addCallback} to register
     * a callback, it also provides a selector to specify the kinds of routes that
     * it is interested in.  The media router combines all of these selectors together
     * to generate a {@link MediaRouteDiscoveryRequest} and notifies each provider when a change
     * occurs by calling {@link #setDiscoveryRequest} which posts a message to invoke
     * this method asynchronously.
     * </p><p>
     * The provider should examine the {@link MediaControlIntent media control categories}
     * in the discovery request's {@link MediaRouteSelector selector} to determine what
     * kinds of routes it should try to discover and whether it should perform active
     * or passive scans.  In many cases, the provider may be able to save power by
     * determining that the selector does not contain any categories that it supports
     * and it can therefore avoid performing any scans at all.
     * </p>
     *
     * @param request The new discovery request, or null if no discovery is needed at this time.
     *
     * @see MediaRouter#addCallback
     */
    public void onDiscoveryRequestChanged(@Nullable MediaRouteDiscoveryRequest request) {
    }

    /**
     * Gets the provider's descriptor.
     * <p>
     * The descriptor describes the state of the media route provider and
     * the routes that it publishes.  Watch for changes to the descriptor
     * by registering a {@link Callback callback} with {@link #setCallback}.
     * </p>
     *
     * @return The media route provider descriptor, or null if none.
     *
     * @see Callback#onDescriptorChanged
     */
    @Nullable
    public final MediaRouteProviderDescriptor getDescriptor() {
        return mDescriptor;
    }

    /**
     * Sets the provider's descriptor.
     * <p>
     * The provider must call this method to notify the currently registered
     * {@link Callback callback} about the change to the provider's descriptor.
     * </p>
     *
     * @param descriptor The updated route provider descriptor, or null if none.
     *
     * @see Callback#onDescriptorChanged
     */
    public final void setDescriptor(@Nullable MediaRouteProviderDescriptor descriptor) {
        MediaRouter.checkCallingThread();

        if (mDescriptor != descriptor) {
            mDescriptor = descriptor;
            if (!mPendingDescriptorChange) {
                mPendingDescriptorChange = true;
                mHandler.sendEmptyMessage(MSG_DELIVER_DESCRIPTOR_CHANGED);
            }
        }
    }

    void deliverDescriptorChanged() {
        mPendingDescriptorChange = false;

        if (mCallback != null) {
            mCallback.onDescriptorChanged(this, mDescriptor);
        }
    }

    /**
     * Called by the media router to obtain a route controller for a particular route.
     * <p>
     * The media router will invoke the {@link RouteController#onRelease} method of the route
     * controller when it is no longer needed to allow it to free its resources.
     * </p>
     *
     * @param routeId The unique id of the route.
     * @return The route controller.  Returns null if there is no such route or if the route
     * cannot be controlled using the route controller interface.
     */
    @Nullable
    public RouteController onCreateRouteController(@NonNull String routeId) {
        if (routeId == null) {
            throw new IllegalArgumentException("routeId cannot be null");
        }
        return null;
    }

    /**
     * Called by the media router to obtain a route controller for a particular route which is a
     * member of {@link MediaRouter.RouteGroup}.
     * <p>
     * The media router will invoke the {@link RouteController#onRelease} method of the route
     * controller when it is no longer needed to allow it to free its resources.
     * </p>
     *
     * @param routeId The unique id of the member route.
     * @param routeGroupId The unique id of the route group.
     * @return The route controller.  Returns null if there is no such route or if the route
     * cannot be controlled using the route controller interface.
     * @hide
     */
    // @RestrictTo(LIBRARY_GROUP)
    @Nullable
    public RouteController onCreateRouteController(@NonNull String routeId,
            @NonNull String routeGroupId) {
        if (routeId == null) {
            throw new IllegalArgumentException("routeId cannot be null");
        }
        if (routeGroupId == null) {
            throw new IllegalArgumentException("routeGroupId cannot be null");
        }
        return onCreateRouteController(routeId);
    }

    /**
     * Describes properties of the route provider's implementation.
     * <p>
     * This object is immutable once created.
     * </p>
     */
    public static final class ProviderMetadata {
        private final ComponentName mComponentName;

        ProviderMetadata(ComponentName componentName) {
            if (componentName == null) {
                throw new IllegalArgumentException("componentName must not be null");
            }
            mComponentName = componentName;
        }

        /**
         * Gets the provider's package name.
         */
        public String getPackageName() {
            return mComponentName.getPackageName();
        }

        /**
         * Gets the provider's component name.
         */
        public ComponentName getComponentName() {
            return mComponentName;
        }

        @Override
        public String toString() {
            return "ProviderMetadata{ componentName="
                    + mComponentName.flattenToShortString() + " }";
        }
    }

    /**
     * Provides control over a particular route.
     * <p>
     * The media router obtains a route controller for a route whenever it needs
     * to control a route.  When a route is selected, the media router invokes
     * the {@link #onSelect} method of its route controller.  While selected,
     * the media router may call other methods of the route controller to
     * request that it perform certain actions to the route.  When a route is
     * unselected, the media router invokes the {@link #onUnselect} method of its
     * route controller.  When the media route no longer needs the route controller
     * it will invoke the {@link #onRelease} method to allow the route controller
     * to free its resources.
     * </p><p>
     * There may be multiple route controllers simultaneously active for the
     * same route.  Each route controller will be released separately.
     * </p><p>
     * All operations on the route controller are asynchronous and
     * results are communicated via callbacks.
     * </p>
     */
    public static abstract class RouteController {
        /**
         * Releases the route controller, allowing it to free its resources.
         */
        public void onRelease() {
        }

        /**
         * Selects the route.
         */
        public void onSelect() {
        }

        /**
         * Unselects the route.
         */
        public void onUnselect() {
        }

        /**
         * Unselects the route and provides a reason. The default implementation
         * calls {@link #onUnselect()}.
         * <p>
         * The reason provided will be one of the following:
         * <ul>
         * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
         * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
         * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
         * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
         * </ul>
         *
         * @param reason The reason for unselecting the route.
         */
        public void onUnselect(int reason) {
            onUnselect();
        }

        /**
         * Requests to set the volume of the route.
         *
         * @param volume The new volume value between 0 and {@link MediaRouteDescriptor#getVolumeMax}.
         */
        public void onSetVolume(int volume) {
        }

        /**
         * Requests an incremental volume update for the route.
         *
         * @param delta The delta to add to the current volume.
         */
        public void onUpdateVolume(int delta) {
        }

        /**
         * Performs a {@link MediaControlIntent media control} request
         * asynchronously on behalf of the route.
         *
         * @param intent A {@link MediaControlIntent media control intent}.
         * @param callback A {@link ControlRequestCallback} to invoke with the result
         * of the request, or null if no result is required.
         * @return True if the controller intends to handle the request and will
         * invoke the callback when finished.  False if the controller will not
         * handle the request and will not invoke the callback.
         *
         * @see MediaControlIntent
         */
        public boolean onControlRequest(Intent intent, @Nullable ControlRequestCallback callback) {
            return false;
        }
    }

    /**
     * Callback which is invoked when route information becomes available or changes.
     */
    public static abstract class Callback {
        /**
         * Called when information about a route provider and its routes changes.
         *
         * @param provider The media route provider that changed, never null.
         * @param descriptor The new media route provider descriptor, or null if none.
         */
        public void onDescriptorChanged(@NonNull MediaRouteProvider provider,
                @Nullable MediaRouteProviderDescriptor descriptor) {
        }
    }

    private final class ProviderHandler extends Handler {
        ProviderHandler() {
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DELIVER_DESCRIPTOR_CHANGED:
                    deliverDescriptorChanged();
                    break;
                case MSG_DELIVER_DISCOVERY_REQUEST_CHANGED:
                    deliverDiscoveryRequestChanged();
                    break;
            }
        }
    }
}