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