• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 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 
17 package android.media;
18 
19 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
20 
21 import android.annotation.CallSuper;
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.SdkConstant;
26 import android.app.Service;
27 import android.content.Intent;
28 import android.os.Binder;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.IBinder;
32 import android.os.Looper;
33 import android.os.Process;
34 import android.os.RemoteException;
35 import android.text.TextUtils;
36 import android.util.ArrayMap;
37 import android.util.Log;
38 
39 import com.android.internal.annotations.GuardedBy;
40 
41 import java.lang.annotation.Retention;
42 import java.lang.annotation.RetentionPolicy;
43 import java.util.ArrayDeque;
44 import java.util.ArrayList;
45 import java.util.Collection;
46 import java.util.Deque;
47 import java.util.List;
48 import java.util.Objects;
49 import java.util.concurrent.atomic.AtomicBoolean;
50 
51 /**
52  * Base class for media route provider services.
53  * <p>
54  * Media route provider services are used to publish {@link MediaRoute2Info media routes} such as
55  * speakers, TVs, etc. The routes are published by calling {@link #notifyRoutes(Collection)}.
56  * Media apps which use {@link MediaRouter2} can request to play their media on the routes.
57  * </p><p>
58  * When {@link MediaRouter2 media router} wants to play media on a route,
59  * {@link #onCreateSession(long, String, String, Bundle)} will be called to handle the request.
60  * A session can be considered as a group of currently selected routes for each connection.
61  * Create and manage the sessions by yourself, and notify the {@link RoutingSessionInfo
62  * session infos} when there are any changes.
63  * </p><p>
64  * The system media router service will bind to media route provider services when a
65  * {@link RouteDiscoveryPreference discovery preference} is registered via
66  * a {@link MediaRouter2 media router} by an application. See
67  * {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} for the details.
68  * </p>
69  * Use {@link #notifyRequestFailed(long, int)} to notify the failure with previously received
70  * request ID.
71  */
72 public abstract class MediaRoute2ProviderService extends Service {
73     private static final String TAG = "MR2ProviderService";
74     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
75 
76     /**
77      * The {@link Intent} action that must be declared as handled by the service.
78      * Put this in your manifest to provide media routes.
79      */
80     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
81     public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
82 
83     /**
84      * A category indicating that the associated provider is only intended for use within the app
85      * that hosts the provider.
86      *
87      * <p>Declaring this category helps the system save resources by avoiding the launch of services
88      * whose routes are known to be private to the app that provides them.
89      *
90      * @hide
91      */
92     public static final String CATEGORY_SELF_SCAN_ONLY =
93             "android.media.MediaRoute2ProviderService.SELF_SCAN_ONLY";
94 
95     /**
96      * The request ID to pass {@link #notifySessionCreated(long, RoutingSessionInfo)}
97      * when {@link MediaRoute2ProviderService} created a session although there was no creation
98      * request.
99      *
100      * @see #notifySessionCreated(long, RoutingSessionInfo)
101      */
102     public static final long REQUEST_ID_NONE = 0;
103 
104     /**
105      * The request has failed due to unknown reason.
106      *
107      * @see #notifyRequestFailed(long, int)
108      */
109     public static final int REASON_UNKNOWN_ERROR = 0;
110 
111     /**
112      * The request has failed since this service rejected the request.
113      *
114      * @see #notifyRequestFailed(long, int)
115      */
116     public static final int REASON_REJECTED = 1;
117 
118     /**
119      * The request has failed due to a network error.
120      *
121      * @see #notifyRequestFailed(long, int)
122      */
123     public static final int REASON_NETWORK_ERROR = 2;
124 
125     /**
126      * The request has failed since the requested route is no longer available.
127      *
128      * @see #notifyRequestFailed(long, int)
129      */
130     public static final int REASON_ROUTE_NOT_AVAILABLE = 3;
131 
132     /**
133      * The request has failed since the request is not valid. For example, selecting a route
134      * which is not selectable.
135      *
136      * @see #notifyRequestFailed(long, int)
137      */
138     public static final int REASON_INVALID_COMMAND = 4;
139 
140     /**
141      * @hide
142      */
143     @IntDef(prefix = "REASON_", value = {
144             REASON_UNKNOWN_ERROR, REASON_REJECTED, REASON_NETWORK_ERROR, REASON_ROUTE_NOT_AVAILABLE,
145             REASON_INVALID_COMMAND
146     })
147     @Retention(RetentionPolicy.SOURCE)
148     public @interface Reason {}
149 
150     private static final int MAX_REQUEST_IDS_SIZE = 500;
151 
152     private final Handler mHandler;
153     private final Object mSessionLock = new Object();
154     private final Object mRequestIdsLock = new Object();
155     private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false);
156     private final AtomicBoolean mSessionUpdateScheduled = new AtomicBoolean(false);
157     private MediaRoute2ProviderServiceStub mStub;
158     private IMediaRoute2ProviderServiceCallback mRemoteCallback;
159     private volatile MediaRoute2ProviderInfo mProviderInfo;
160 
161     @GuardedBy("mRequestIdsLock")
162     private final Deque<Long> mRequestIds = new ArrayDeque<>(MAX_REQUEST_IDS_SIZE);
163 
164     @GuardedBy("mSessionLock")
165     private final ArrayMap<String, RoutingSessionInfo> mSessionInfos = new ArrayMap<>();
166 
MediaRoute2ProviderService()167     public MediaRoute2ProviderService() {
168         mHandler = new Handler(Looper.getMainLooper());
169     }
170 
171     /**
172      * If overriding this method, call through to the super method for any unknown actions.
173      * <p>
174      * {@inheritDoc}
175      */
176     @CallSuper
177     @Override
178     @Nullable
onBind(@onNull Intent intent)179     public IBinder onBind(@NonNull Intent intent) {
180         if (SERVICE_INTERFACE.equals(intent.getAction())) {
181             if (mStub == null) {
182                 mStub = new MediaRoute2ProviderServiceStub();
183             }
184             return mStub;
185         }
186         return null;
187     }
188 
189     /**
190      * Called when a volume setting is requested on a route of the provider
191      *
192      * @param requestId the ID of this request
193      * @param routeId the ID of the route
194      * @param volume the target volume
195      * @see MediaRoute2Info.Builder#setVolume(int)
196      */
onSetRouteVolume(long requestId, @NonNull String routeId, int volume)197     public abstract void onSetRouteVolume(long requestId, @NonNull String routeId, int volume);
198 
199     /**
200      * Called when {@link MediaRouter2.RoutingController#setVolume(int)} is called on
201      * a routing session of the provider
202      *
203      * @param requestId the ID of this request
204      * @param sessionId the ID of the routing session
205      * @param volume the target volume
206      * @see RoutingSessionInfo.Builder#setVolume(int)
207      */
onSetSessionVolume(long requestId, @NonNull String sessionId, int volume)208     public abstract void onSetSessionVolume(long requestId, @NonNull String sessionId, int volume);
209 
210     /**
211      * Gets information of the session with the given id.
212      *
213      * @param sessionId the ID of the session
214      * @return information of the session with the given id.
215      *         null if the session is released or ID is not valid.
216      */
217     @Nullable
getSessionInfo(@onNull String sessionId)218     public final RoutingSessionInfo getSessionInfo(@NonNull String sessionId) {
219         if (TextUtils.isEmpty(sessionId)) {
220             throw new IllegalArgumentException("sessionId must not be empty");
221         }
222         synchronized (mSessionLock) {
223             return mSessionInfos.get(sessionId);
224         }
225     }
226 
227     /**
228      * Gets the list of {@link RoutingSessionInfo session info} that the provider service maintains.
229      */
230     @NonNull
getAllSessionInfo()231     public final List<RoutingSessionInfo> getAllSessionInfo() {
232         synchronized (mSessionLock) {
233             return new ArrayList<>(mSessionInfos.values());
234         }
235     }
236 
237     /**
238      * Notifies clients of that the session is created and ready for use.
239      * <p>
240      * If this session is created without any creation request, use {@link #REQUEST_ID_NONE}
241      * as the request ID.
242      *
243      * @param requestId the ID of the previous request to create this session provided in
244      *                  {@link #onCreateSession(long, String, String, Bundle)}. Can be
245      *                  {@link #REQUEST_ID_NONE} if this session is created without any request.
246      * @param sessionInfo information of the new session.
247      *                    The {@link RoutingSessionInfo#getId() id} of the session must be unique.
248      * @see #onCreateSession(long, String, String, Bundle)
249      * @see #getSessionInfo(String)
250      */
notifySessionCreated(long requestId, @NonNull RoutingSessionInfo sessionInfo)251     public final void notifySessionCreated(long requestId,
252             @NonNull RoutingSessionInfo sessionInfo) {
253         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
254 
255         if (DEBUG) {
256             Log.d(TAG, "notifySessionCreated: Creating a session. requestId=" + requestId
257                     + ", sessionInfo=" + sessionInfo);
258         }
259 
260         if (requestId != REQUEST_ID_NONE && !removeRequestId(requestId)) {
261             Log.w(TAG, "notifySessionCreated: The requestId doesn't exist. requestId=" + requestId);
262             return;
263         }
264 
265         String sessionId = sessionInfo.getId();
266         synchronized (mSessionLock) {
267             if (mSessionInfos.containsKey(sessionId)) {
268                 Log.w(TAG, "notifySessionCreated: Ignoring duplicate session id.");
269                 return;
270             }
271             mSessionInfos.put(sessionInfo.getId(), sessionInfo);
272 
273             if (mRemoteCallback == null) {
274                 return;
275             }
276             try {
277                 mRemoteCallback.notifySessionCreated(requestId, sessionInfo);
278             } catch (RemoteException ex) {
279                 Log.w(TAG, "Failed to notify session created.");
280             }
281         }
282     }
283 
284     /**
285      * Notifies the existing session is updated. For example, when
286      * {@link RoutingSessionInfo#getSelectedRoutes() selected routes} are changed.
287      */
notifySessionUpdated(@onNull RoutingSessionInfo sessionInfo)288     public final void notifySessionUpdated(@NonNull RoutingSessionInfo sessionInfo) {
289         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
290 
291         if (DEBUG) {
292             Log.d(TAG, "notifySessionUpdated: Updating session id=" + sessionInfo);
293         }
294 
295         String sessionId = sessionInfo.getId();
296         synchronized (mSessionLock) {
297             if (mSessionInfos.containsKey(sessionId)) {
298                 mSessionInfos.put(sessionId, sessionInfo);
299             } else {
300                 Log.w(TAG, "notifySessionUpdated: Ignoring unknown session info.");
301                 return;
302             }
303         }
304         scheduleUpdateSessions();
305     }
306 
307     /**
308      * Notifies that the session is released.
309      *
310      * @param sessionId the ID of the released session.
311      * @see #onReleaseSession(long, String)
312      */
notifySessionReleased(@onNull String sessionId)313     public final void notifySessionReleased(@NonNull String sessionId) {
314         if (TextUtils.isEmpty(sessionId)) {
315             throw new IllegalArgumentException("sessionId must not be empty");
316         }
317         if (DEBUG) {
318             Log.d(TAG, "notifySessionReleased: Releasing session id=" + sessionId);
319         }
320 
321         RoutingSessionInfo sessionInfo;
322         synchronized (mSessionLock) {
323             sessionInfo = mSessionInfos.remove(sessionId);
324 
325             if (sessionInfo == null) {
326                 Log.w(TAG, "notifySessionReleased: Ignoring unknown session info.");
327                 return;
328             }
329 
330             if (mRemoteCallback == null) {
331                 return;
332             }
333             try {
334                 mRemoteCallback.notifySessionReleased(sessionInfo);
335             } catch (RemoteException ex) {
336                 Log.w(TAG, "Failed to notify session released.", ex);
337             }
338         }
339     }
340 
341     /**
342      * Notifies to the client that the request has failed.
343      *
344      * @param requestId the ID of the previous request
345      * @param reason the reason why the request has failed
346      *
347      * @see #REASON_UNKNOWN_ERROR
348      * @see #REASON_REJECTED
349      * @see #REASON_NETWORK_ERROR
350      * @see #REASON_ROUTE_NOT_AVAILABLE
351      * @see #REASON_INVALID_COMMAND
352      */
notifyRequestFailed(long requestId, @Reason int reason)353     public final void notifyRequestFailed(long requestId, @Reason int reason) {
354         if (mRemoteCallback == null) {
355             return;
356         }
357 
358         if (!removeRequestId(requestId)) {
359             Log.w(TAG, "notifyRequestFailed: The requestId doesn't exist. requestId="
360                     + requestId);
361             return;
362         }
363 
364         try {
365             mRemoteCallback.notifyRequestFailed(requestId, reason);
366         } catch (RemoteException ex) {
367             Log.w(TAG, "Failed to notify that the request has failed.");
368         }
369     }
370 
371     /**
372      * Called when the service receives a request to create a session.
373      * <p>
374      * You should create and maintain your own session and notifies the client of
375      * session info. Call {@link #notifySessionCreated(long, RoutingSessionInfo)}
376      * with the given {@code requestId} to notify the information of a new session.
377      * The created session must have the same route feature and must include the given route
378      * specified by {@code routeId}.
379      * <p>
380      * If the session can be controlled, you can optionally pass the control hints to
381      * {@link RoutingSessionInfo.Builder#setControlHints(Bundle)}. Control hints is a
382      * {@link Bundle} which contains how to control the session.
383      * <p>
384      * If you can't create the session or want to reject the request, call
385      * {@link #notifyRequestFailed(long, int)} with the given {@code requestId}.
386      *
387      * @param requestId the ID of this request
388      * @param packageName the package name of the application that selected the route
389      * @param routeId the ID of the route initially being connected
390      * @param sessionHints an optional bundle of app-specific arguments sent by
391      *                     {@link MediaRouter2}, or null if none. The contents of this bundle
392      *                     may affect the result of session creation.
393      *
394      * @see RoutingSessionInfo.Builder#Builder(String, String)
395      * @see RoutingSessionInfo.Builder#addSelectedRoute(String)
396      * @see RoutingSessionInfo.Builder#setControlHints(Bundle)
397      */
onCreateSession(long requestId, @NonNull String packageName, @NonNull String routeId, @Nullable Bundle sessionHints)398     public abstract void onCreateSession(long requestId, @NonNull String packageName,
399             @NonNull String routeId, @Nullable Bundle sessionHints);
400 
401     /**
402      * Called when the session should be released. A client of the session or system can request
403      * a session to be released.
404      * <p>
405      * After releasing the session, call {@link #notifySessionReleased(String)}
406      * with the ID of the released session.
407      *
408      * Note: Calling {@link #notifySessionReleased(String)} will <em>NOT</em> trigger
409      * this method to be called.
410      *
411      * @param requestId the ID of this request
412      * @param sessionId the ID of the session being released.
413      * @see #notifySessionReleased(String)
414      * @see #getSessionInfo(String)
415      */
onReleaseSession(long requestId, @NonNull String sessionId)416     public abstract void onReleaseSession(long requestId, @NonNull String sessionId);
417 
418     /**
419      * Called when a client requests selecting a route for the session.
420      * After the route is selected, call {@link #notifySessionUpdated(RoutingSessionInfo)}
421      * to update session info.
422      *
423      * @param requestId the ID of this request
424      * @param sessionId the ID of the session
425      * @param routeId the ID of the route
426      */
onSelectRoute(long requestId, @NonNull String sessionId, @NonNull String routeId)427     public abstract void onSelectRoute(long requestId, @NonNull String sessionId,
428             @NonNull String routeId);
429 
430     /**
431      * Called when a client requests deselecting a route from the session.
432      * After the route is deselected, call {@link #notifySessionUpdated(RoutingSessionInfo)}
433      * to update session info.
434      *
435      * @param requestId the ID of this request
436      * @param sessionId the ID of the session
437      * @param routeId the ID of the route
438      */
onDeselectRoute(long requestId, @NonNull String sessionId, @NonNull String routeId)439     public abstract void onDeselectRoute(long requestId, @NonNull String sessionId,
440             @NonNull String routeId);
441 
442     /**
443      * Called when a client requests transferring a session to a route.
444      * After the transfer is finished, call {@link #notifySessionUpdated(RoutingSessionInfo)}
445      * to update session info.
446      *
447      * @param requestId the ID of this request
448      * @param sessionId the ID of the session
449      * @param routeId the ID of the route
450      */
onTransferToRoute(long requestId, @NonNull String sessionId, @NonNull String routeId)451     public abstract void onTransferToRoute(long requestId, @NonNull String sessionId,
452             @NonNull String routeId);
453 
454     /**
455      * Called when the {@link RouteDiscoveryPreference discovery preference} has changed.
456      * <p>
457      * Whenever an application registers a {@link MediaRouter2.RouteCallback callback},
458      * it also provides a discovery preference to specify features of routes that it is interested
459      * in. The media router combines all of these discovery request into a single discovery
460      * preference and notifies each provider.
461      * </p><p>
462      * The provider should examine {@link RouteDiscoveryPreference#getPreferredFeatures()
463      * preferred features} in the discovery preference to determine what kind of routes it should
464      * try to discover and whether it should perform active or passive scans. In many cases,
465      * the provider may be able to save power by not performing any scans when the request doesn't
466      * have any matching route features.
467      * </p>
468      *
469      * @param preference the new discovery preference
470      */
onDiscoveryPreferenceChanged(@onNull RouteDiscoveryPreference preference)471     public void onDiscoveryPreferenceChanged(@NonNull RouteDiscoveryPreference preference) {}
472 
473     /**
474      * Updates routes of the provider and notifies the system media router service.
475      */
notifyRoutes(@onNull Collection<MediaRoute2Info> routes)476     public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) {
477         Objects.requireNonNull(routes, "routes must not be null");
478         List<MediaRoute2Info> sanitizedRoutes = new ArrayList<>(routes.size());
479 
480         for (MediaRoute2Info route : routes) {
481             if (route.isSystemRouteType()) {
482                 Log.w(
483                         TAG,
484                         "Attempting to add a system route type from a non-system route "
485                                 + "provider. Overriding type to TYPE_UNKNOWN. Route: "
486                                 + route);
487                 sanitizedRoutes.add(
488                         new MediaRoute2Info.Builder(route)
489                                 .setType(MediaRoute2Info.TYPE_UNKNOWN)
490                                 .build());
491             } else {
492                 sanitizedRoutes.add(route);
493             }
494         }
495 
496         mProviderInfo = new MediaRoute2ProviderInfo.Builder().addRoutes(sanitizedRoutes).build();
497         schedulePublishState();
498     }
499 
setCallback(IMediaRoute2ProviderServiceCallback callback)500     void setCallback(IMediaRoute2ProviderServiceCallback callback) {
501         mRemoteCallback = callback;
502         schedulePublishState();
503         scheduleUpdateSessions();
504     }
505 
schedulePublishState()506     void schedulePublishState() {
507         if (mStatePublishScheduled.compareAndSet(false, true)) {
508             mHandler.post(this::publishState);
509         }
510     }
511 
publishState()512     private void publishState() {
513         if (!mStatePublishScheduled.compareAndSet(true, false)) {
514             return;
515         }
516 
517         if (mRemoteCallback == null) {
518             return;
519         }
520 
521         if (mProviderInfo == null) {
522             return;
523         }
524 
525         try {
526             mRemoteCallback.notifyProviderUpdated(mProviderInfo);
527         } catch (RemoteException ex) {
528             Log.w(TAG, "Failed to publish provider state.", ex);
529         }
530     }
531 
scheduleUpdateSessions()532     void scheduleUpdateSessions() {
533         if (mSessionUpdateScheduled.compareAndSet(false, true)) {
534             mHandler.post(this::updateSessions);
535         }
536     }
537 
updateSessions()538     private void updateSessions() {
539         if (!mSessionUpdateScheduled.compareAndSet(true, false)) {
540             return;
541         }
542 
543         if (mRemoteCallback == null) {
544             return;
545         }
546 
547         List<RoutingSessionInfo> sessions;
548         synchronized (mSessionLock) {
549             sessions = new ArrayList<>(mSessionInfos.values());
550         }
551 
552         try {
553             mRemoteCallback.notifySessionsUpdated(sessions);
554         } catch (RemoteException ex) {
555             Log.w(TAG, "Failed to notify session info changed.");
556         }
557 
558     }
559 
560     /**
561      * Adds a requestId in the request ID list whose max size is {@link #MAX_REQUEST_IDS_SIZE}.
562      * When the max size is reached, the first element is removed (FIFO).
563      */
addRequestId(long requestId)564     private void addRequestId(long requestId) {
565         synchronized (mRequestIdsLock) {
566             if (mRequestIds.size() >= MAX_REQUEST_IDS_SIZE) {
567                 mRequestIds.removeFirst();
568             }
569             mRequestIds.addLast(requestId);
570         }
571     }
572 
573     /**
574      * Removes the given {@code requestId} from received request ID list.
575      * <p>
576      * Returns whether the list contains the {@code requestId}. These are the cases when the list
577      * doesn't contain the given {@code requestId}:
578      * <ul>
579      *     <li>This service has never received a request with the requestId. </li>
580      *     <li>{@link #notifyRequestFailed} or {@link #notifySessionCreated} already has been called
581      *         for the requestId. </li>
582      * </ul>
583      */
removeRequestId(long requestId)584     private boolean removeRequestId(long requestId) {
585         synchronized (mRequestIdsLock) {
586             return mRequestIds.removeFirstOccurrence(requestId);
587         }
588     }
589 
590     final class MediaRoute2ProviderServiceStub extends IMediaRoute2ProviderService.Stub {
MediaRoute2ProviderServiceStub()591         MediaRoute2ProviderServiceStub() { }
592 
checkCallerIsSystem()593         private boolean checkCallerIsSystem() {
594             return Binder.getCallingUid() == Process.SYSTEM_UID;
595         }
596 
checkSessionIdIsValid(String sessionId, String description)597         private boolean checkSessionIdIsValid(String sessionId, String description) {
598             if (TextUtils.isEmpty(sessionId)) {
599                 Log.w(TAG, description + ": Ignoring empty sessionId from system service.");
600                 return false;
601             }
602             if (getSessionInfo(sessionId) == null) {
603                 Log.w(TAG, description + ": Ignoring unknown session from system service. "
604                         + "sessionId=" + sessionId);
605                 return false;
606             }
607             return true;
608         }
609 
checkRouteIdIsValid(String routeId, String description)610         private boolean checkRouteIdIsValid(String routeId, String description) {
611             if (TextUtils.isEmpty(routeId)) {
612                 Log.w(TAG, description + ": Ignoring empty routeId from system service.");
613                 return false;
614             }
615             if (mProviderInfo == null || mProviderInfo.getRoute(routeId) == null) {
616                 Log.w(TAG, description + ": Ignoring unknown route from system service. "
617                         + "routeId=" + routeId);
618                 return false;
619             }
620             return true;
621         }
622 
623         @Override
setCallback(IMediaRoute2ProviderServiceCallback callback)624         public void setCallback(IMediaRoute2ProviderServiceCallback callback) {
625             if (!checkCallerIsSystem()) {
626                 return;
627             }
628             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::setCallback,
629                     MediaRoute2ProviderService.this, callback));
630         }
631 
632         @Override
updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference)633         public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
634             if (!checkCallerIsSystem()) {
635                 return;
636             }
637             mHandler.sendMessage(obtainMessage(
638                     MediaRoute2ProviderService::onDiscoveryPreferenceChanged,
639                     MediaRoute2ProviderService.this, discoveryPreference));
640         }
641 
642         @Override
setRouteVolume(long requestId, String routeId, int volume)643         public void setRouteVolume(long requestId, String routeId, int volume) {
644             if (!checkCallerIsSystem()) {
645                 return;
646             }
647             if (!checkRouteIdIsValid(routeId, "setRouteVolume")) {
648                 return;
649             }
650             addRequestId(requestId);
651             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetRouteVolume,
652                     MediaRoute2ProviderService.this, requestId, routeId, volume));
653         }
654 
655         @Override
requestCreateSession(long requestId, String packageName, String routeId, @Nullable Bundle requestCreateSession)656         public void requestCreateSession(long requestId, String packageName, String routeId,
657                 @Nullable Bundle requestCreateSession) {
658             if (!checkCallerIsSystem()) {
659                 return;
660             }
661             if (!checkRouteIdIsValid(routeId, "requestCreateSession")) {
662                 return;
663             }
664             addRequestId(requestId);
665             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession,
666                     MediaRoute2ProviderService.this, requestId, packageName, routeId,
667                     requestCreateSession));
668         }
669 
670         @Override
selectRoute(long requestId, String sessionId, String routeId)671         public void selectRoute(long requestId, String sessionId, String routeId) {
672             if (!checkCallerIsSystem()) {
673                 return;
674             }
675             if (!checkSessionIdIsValid(sessionId, "selectRoute")
676                     || !checkRouteIdIsValid(routeId, "selectRoute")) {
677                 return;
678             }
679             addRequestId(requestId);
680             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
681                     MediaRoute2ProviderService.this, requestId, sessionId, routeId));
682         }
683 
684         @Override
deselectRoute(long requestId, String sessionId, String routeId)685         public void deselectRoute(long requestId, String sessionId, String routeId) {
686             if (!checkCallerIsSystem()) {
687                 return;
688             }
689             if (!checkSessionIdIsValid(sessionId, "deselectRoute")
690                     || !checkRouteIdIsValid(routeId, "deselectRoute")) {
691                 return;
692             }
693             addRequestId(requestId);
694             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute,
695                     MediaRoute2ProviderService.this, requestId, sessionId, routeId));
696         }
697 
698         @Override
transferToRoute(long requestId, String sessionId, String routeId)699         public void transferToRoute(long requestId, String sessionId, String routeId) {
700             if (!checkCallerIsSystem()) {
701                 return;
702             }
703             if (!checkSessionIdIsValid(sessionId, "transferToRoute")
704                     || !checkRouteIdIsValid(routeId, "transferToRoute")) {
705                 return;
706             }
707             addRequestId(requestId);
708             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute,
709                     MediaRoute2ProviderService.this, requestId, sessionId, routeId));
710         }
711 
712         @Override
setSessionVolume(long requestId, String sessionId, int volume)713         public void setSessionVolume(long requestId, String sessionId, int volume) {
714             if (!checkCallerIsSystem()) {
715                 return;
716             }
717             if (!checkSessionIdIsValid(sessionId, "setSessionVolume")) {
718                 return;
719             }
720             addRequestId(requestId);
721             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetSessionVolume,
722                     MediaRoute2ProviderService.this, requestId, sessionId, volume));
723         }
724 
725         @Override
releaseSession(long requestId, String sessionId)726         public void releaseSession(long requestId, String sessionId) {
727             if (!checkCallerIsSystem()) {
728                 return;
729             }
730             if (!checkSessionIdIsValid(sessionId, "releaseSession")) {
731                 return;
732             }
733             addRequestId(requestId);
734             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession,
735                     MediaRoute2ProviderService.this, requestId, sessionId));
736         }
737     }
738 }
739