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