• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 com.android.server.media;
18 
19 import android.Manifest;
20 import android.app.Activity;
21 import android.app.ActivityManager;
22 import android.app.KeyguardManager;
23 import android.app.PendingIntent;
24 import android.app.PendingIntent.CanceledException;
25 import android.content.ActivityNotFoundException;
26 import android.content.BroadcastReceiver;
27 import android.content.ComponentName;
28 import android.content.ContentResolver;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.pm.PackageManager;
32 import android.database.ContentObserver;
33 import android.media.AudioManager;
34 import android.media.AudioManagerInternal;
35 import android.media.AudioSystem;
36 import android.media.IAudioService;
37 import android.media.IRemoteVolumeController;
38 import android.media.session.IActiveSessionsListener;
39 import android.media.session.ISession;
40 import android.media.session.ISessionCallback;
41 import android.media.session.ISessionManager;
42 import android.media.session.MediaSession;
43 import android.net.Uri;
44 import android.os.Binder;
45 import android.os.Bundle;
46 import android.os.Handler;
47 import android.os.IBinder;
48 import android.os.Message;
49 import android.os.PowerManager;
50 import android.os.Process;
51 import android.os.RemoteException;
52 import android.os.ResultReceiver;
53 import android.os.ServiceManager;
54 import android.os.UserHandle;
55 import android.os.UserManager;
56 import android.provider.Settings;
57 import android.speech.RecognizerIntent;
58 import android.text.TextUtils;
59 import android.util.Log;
60 import android.util.Slog;
61 import android.util.SparseArray;
62 import android.view.KeyEvent;
63 
64 import com.android.server.LocalServices;
65 import com.android.server.SystemService;
66 import com.android.server.Watchdog;
67 import com.android.server.Watchdog.Monitor;
68 
69 import java.io.FileDescriptor;
70 import java.io.PrintWriter;
71 import java.util.ArrayList;
72 import java.util.Arrays;
73 import java.util.List;
74 
75 /**
76  * System implementation of MediaSessionManager
77  */
78 public class MediaSessionService extends SystemService implements Monitor {
79     private static final String TAG = "MediaSessionService";
80     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
81     // Leave log for media key event always.
82     private static final boolean DEBUG_MEDIA_KEY_EVENT = DEBUG || true;
83 
84     private static final int WAKELOCK_TIMEOUT = 5000;
85 
86     /* package */final IBinder mICallback = new Binder();
87 
88     private final SessionManagerImpl mSessionManagerImpl;
89     private final MediaSessionStack mPriorityStack;
90 
91     private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
92     private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
93     private final ArrayList<SessionsListenerRecord> mSessionsListeners
94             = new ArrayList<SessionsListenerRecord>();
95     private final Object mLock = new Object();
96     private final MessageHandler mHandler = new MessageHandler();
97     private final PowerManager.WakeLock mMediaEventWakeLock;
98 
99     private KeyguardManager mKeyguardManager;
100     private IAudioService mAudioService;
101     private AudioManagerInternal mAudioManagerInternal;
102     private ContentResolver mContentResolver;
103     private SettingsObserver mSettingsObserver;
104 
105     // List of user IDs running in the foreground.
106     // Multiple users can be in the foreground if the work profile is on.
107     private final List<Integer> mCurrentUserIdList = new ArrayList<>();
108 
109     // Used to notify system UI when remote volume was changed. TODO find a
110     // better way to handle this.
111     private IRemoteVolumeController mRvc;
112 
MediaSessionService(Context context)113     public MediaSessionService(Context context) {
114         super(context);
115         mSessionManagerImpl = new SessionManagerImpl();
116         mPriorityStack = new MediaSessionStack();
117         PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
118         mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
119     }
120 
121     @Override
onStart()122     public void onStart() {
123         publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
124         Watchdog.getInstance().addMonitor(this);
125         mKeyguardManager =
126                 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
127         mAudioService = getAudioService();
128         mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
129         mContentResolver = getContext().getContentResolver();
130         mSettingsObserver = new SettingsObserver();
131         mSettingsObserver.observe();
132 
133         updateUser();
134     }
135 
getAudioService()136     private IAudioService getAudioService() {
137         IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
138         return IAudioService.Stub.asInterface(b);
139     }
140 
updateSession(MediaSessionRecord record)141     public void updateSession(MediaSessionRecord record) {
142         synchronized (mLock) {
143             if (!mAllSessions.contains(record)) {
144                 Log.d(TAG, "Unknown session updated. Ignoring.");
145                 return;
146             }
147             mPriorityStack.onSessionStateChange(record);
148         }
149         mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
150     }
151 
152     /**
153      * Tells the system UI that volume has changed on a remote session.
154      */
notifyRemoteVolumeChanged(int flags, MediaSessionRecord session)155     public void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session) {
156         if (mRvc == null) {
157             return;
158         }
159         try {
160             mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
161         } catch (Exception e) {
162             Log.wtf(TAG, "Error sending volume change to system UI.", e);
163         }
164     }
165 
onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState)166     public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
167         boolean updateSessions = false;
168         synchronized (mLock) {
169             if (!mAllSessions.contains(record)) {
170                 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
171                 return;
172             }
173             updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState);
174         }
175         if (updateSessions) {
176             mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
177         }
178     }
179 
onSessionPlaybackTypeChanged(MediaSessionRecord record)180     public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
181         synchronized (mLock) {
182             if (!mAllSessions.contains(record)) {
183                 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
184                 return;
185             }
186             pushRemoteVolumeUpdateLocked(record.getUserId());
187         }
188     }
189 
190     @Override
onStartUser(int userId)191     public void onStartUser(int userId) {
192         if (DEBUG) Log.d(TAG, "onStartUser: " + userId);
193         updateUser();
194     }
195 
196     @Override
onSwitchUser(int userId)197     public void onSwitchUser(int userId) {
198         if (DEBUG) Log.d(TAG, "onSwitchUser: " + userId);
199         updateUser();
200     }
201 
202     @Override
onStopUser(int userId)203     public void onStopUser(int userId) {
204         if (DEBUG) Log.d(TAG, "onStopUser: " + userId);
205         synchronized (mLock) {
206             UserRecord user = mUserRecords.get(userId);
207             if (user != null) {
208                 destroyUserLocked(user);
209             }
210             updateUser();
211         }
212     }
213 
214     @Override
monitor()215     public void monitor() {
216         synchronized (mLock) {
217             // Check for deadlock
218         }
219     }
220 
enforcePhoneStatePermission(int pid, int uid)221     protected void enforcePhoneStatePermission(int pid, int uid) {
222         if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
223                 != PackageManager.PERMISSION_GRANTED) {
224             throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
225         }
226     }
227 
sessionDied(MediaSessionRecord session)228     void sessionDied(MediaSessionRecord session) {
229         synchronized (mLock) {
230             destroySessionLocked(session);
231         }
232     }
233 
destroySession(MediaSessionRecord session)234     void destroySession(MediaSessionRecord session) {
235         synchronized (mLock) {
236             destroySessionLocked(session);
237         }
238     }
239 
updateUser()240     private void updateUser() {
241         synchronized (mLock) {
242             UserManager manager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
243             int currentUser = ActivityManager.getCurrentUser();
244             // Include all profiles even though they aren't yet enabled to handle work profile case.
245             int[] userIds = manager.getProfileIdsWithDisabled(currentUser);
246             mCurrentUserIdList.clear();
247             if (userIds != null && userIds.length > 0) {
248                 for (int userId : userIds) {
249                     mCurrentUserIdList.add(userId);
250                 }
251             } else {
252                 // This shouldn't happen.
253                 Log.w(TAG, "Failed to get enabled profiles.");
254                 mCurrentUserIdList.add(currentUser);
255             }
256             for (int userId : mCurrentUserIdList) {
257                 if (mUserRecords.get(userId) == null) {
258                     mUserRecords.put(userId, new UserRecord(getContext(), userId));
259                 }
260             }
261         }
262     }
263 
updateActiveSessionListeners()264     private void updateActiveSessionListeners() {
265         synchronized (mLock) {
266             for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
267                 SessionsListenerRecord listener = mSessionsListeners.get(i);
268                 try {
269                     enforceMediaPermissions(listener.mComponentName, listener.mPid, listener.mUid,
270                             listener.mUserId);
271                 } catch (SecurityException e) {
272                     Log.i(TAG, "ActiveSessionsListener " + listener.mComponentName
273                             + " is no longer authorized. Disconnecting.");
274                     mSessionsListeners.remove(i);
275                     try {
276                         listener.mListener
277                                 .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
278                     } catch (Exception e1) {
279                         // ignore
280                     }
281                 }
282             }
283         }
284     }
285 
286     /**
287      * Stop the user and unbind from everything.
288      *
289      * @param user The user to dispose of
290      */
destroyUserLocked(UserRecord user)291     private void destroyUserLocked(UserRecord user) {
292         user.destroyLocked();
293         mUserRecords.remove(user.mUserId);
294     }
295 
296     /*
297      * When a session is removed several things need to happen.
298      * 1. We need to remove it from the relevant user.
299      * 2. We need to remove it from the priority stack.
300      * 3. We need to remove it from all sessions.
301      * 4. If this is the system priority session we need to clear it.
302      * 5. We need to unlink to death from the cb binder
303      * 6. We need to tell the session to do any final cleanup (onDestroy)
304      */
destroySessionLocked(MediaSessionRecord session)305     private void destroySessionLocked(MediaSessionRecord session) {
306         if (DEBUG) {
307             Log.d(TAG, "Destroying " + session);
308         }
309         int userId = session.getUserId();
310         UserRecord user = mUserRecords.get(userId);
311         if (user != null) {
312             user.removeSessionLocked(session);
313         }
314 
315         mPriorityStack.removeSession(session);
316         mAllSessions.remove(session);
317 
318         try {
319             session.getCallback().asBinder().unlinkToDeath(session, 0);
320         } catch (Exception e) {
321             // ignore exceptions while destroying a session.
322         }
323         session.onDestroy();
324 
325         mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
326     }
327 
enforcePackageName(String packageName, int uid)328     private void enforcePackageName(String packageName, int uid) {
329         if (TextUtils.isEmpty(packageName)) {
330             throw new IllegalArgumentException("packageName may not be empty");
331         }
332         String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
333         final int packageCount = packages.length;
334         for (int i = 0; i < packageCount; i++) {
335             if (packageName.equals(packages[i])) {
336                 return;
337             }
338         }
339         throw new IllegalArgumentException("packageName is not owned by the calling process");
340     }
341 
342     /**
343      * Checks a caller's authorization to register an IRemoteControlDisplay.
344      * Authorization is granted if one of the following is true:
345      * <ul>
346      * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
347      * permission</li>
348      * <li>the caller's listener is one of the enabled notification listeners
349      * for the caller's user</li>
350      * </ul>
351      */
enforceMediaPermissions(ComponentName compName, int pid, int uid, int resolvedUserId)352     private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
353             int resolvedUserId) {
354         if (isCurrentVolumeController(uid)) return;
355         if (getContext()
356                 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
357                     != PackageManager.PERMISSION_GRANTED
358                 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
359                         resolvedUserId)) {
360             throw new SecurityException("Missing permission to control media.");
361         }
362     }
363 
isCurrentVolumeController(int uid)364     private boolean isCurrentVolumeController(int uid) {
365         if (mAudioManagerInternal != null) {
366             final int vcuid = mAudioManagerInternal.getVolumeControllerUid();
367             if (vcuid > 0 && uid == vcuid) {
368                 return true;
369             }
370         }
371         return false;
372     }
373 
enforceSystemUiPermission(String action, int pid, int uid)374     private void enforceSystemUiPermission(String action, int pid, int uid) {
375         if (isCurrentVolumeController(uid)) return;
376         if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
377                 pid, uid) != PackageManager.PERMISSION_GRANTED) {
378             throw new SecurityException("Only system ui may " + action);
379         }
380     }
381 
382     /**
383      * This checks if the component is an enabled notification listener for the
384      * specified user. Enabled components may only operate on behalf of the user
385      * they're running as.
386      *
387      * @param compName The component that is enabled.
388      * @param userId The user id of the caller.
389      * @param forUserId The user id they're making the request on behalf of.
390      * @return True if the component is enabled, false otherwise
391      */
isEnabledNotificationListener(ComponentName compName, int userId, int forUserId)392     private boolean isEnabledNotificationListener(ComponentName compName, int userId,
393             int forUserId) {
394         if (userId != forUserId) {
395             // You may not access another user's content as an enabled listener.
396             return false;
397         }
398         if (DEBUG) {
399             Log.d(TAG, "Checking if enabled notification listener " + compName);
400         }
401         if (compName != null) {
402             final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
403                     Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
404                     userId);
405             if (enabledNotifListeners != null) {
406                 final String[] components = enabledNotifListeners.split(":");
407                 for (int i = 0; i < components.length; i++) {
408                     final ComponentName component =
409                             ComponentName.unflattenFromString(components[i]);
410                     if (component != null) {
411                         if (compName.equals(component)) {
412                             if (DEBUG) {
413                                 Log.d(TAG, "ok to get sessions. " + component +
414                                         " is authorized notification listener");
415                             }
416                             return true;
417                         }
418                     }
419                 }
420             }
421             if (DEBUG) {
422                 Log.d(TAG, "not ok to get sessions. " + compName +
423                         " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
424             }
425         }
426         return false;
427     }
428 
createSessionInternal(int callerPid, int callerUid, int userId, String callerPackageName, ISessionCallback cb, String tag)429     private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
430             String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
431         synchronized (mLock) {
432             return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
433         }
434     }
435 
436     /*
437      * When a session is created the following things need to happen.
438      * 1. Its callback binder needs a link to death
439      * 2. It needs to be added to all sessions.
440      * 3. It needs to be added to the priority stack.
441      * 4. It needs to be added to the relevant user record.
442      */
createSessionLocked(int callerPid, int callerUid, int userId, String callerPackageName, ISessionCallback cb, String tag)443     private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
444             String callerPackageName, ISessionCallback cb, String tag) {
445 
446         UserRecord user = mUserRecords.get(userId);
447         if (user == null) {
448             Log.wtf(TAG, "Request from invalid user: " +  userId);
449             throw new RuntimeException("Session request from invalid user.");
450         }
451 
452         final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
453                 callerPackageName, cb, tag, this, mHandler);
454         try {
455             cb.asBinder().linkToDeath(session, 0);
456         } catch (RemoteException e) {
457             throw new RuntimeException("Media Session owner died prematurely.", e);
458         }
459 
460         mAllSessions.add(session);
461         mPriorityStack.addSession(session, mCurrentUserIdList.contains(userId));
462         user.addSessionLocked(session);
463 
464         mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
465 
466         if (DEBUG) {
467             Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
468         }
469         return session;
470     }
471 
findIndexOfSessionsListenerLocked(IActiveSessionsListener listener)472     private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
473         for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
474             if (mSessionsListeners.get(i).mListener.asBinder() == listener.asBinder()) {
475                 return i;
476             }
477         }
478         return -1;
479     }
480 
pushSessionsChanged(int userId)481     private void pushSessionsChanged(int userId) {
482         synchronized (mLock) {
483             List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
484             int size = records.size();
485             if (size > 0 && records.get(0).isPlaybackActive(false)) {
486                 rememberMediaButtonReceiverLocked(records.get(0));
487             }
488             ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
489             for (int i = 0; i < size; i++) {
490                 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
491             }
492             pushRemoteVolumeUpdateLocked(userId);
493             for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
494                 SessionsListenerRecord record = mSessionsListeners.get(i);
495                 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
496                     try {
497                         record.mListener.onActiveSessionsChanged(tokens);
498                     } catch (RemoteException e) {
499                         Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
500                                 e);
501                         mSessionsListeners.remove(i);
502                     }
503                 }
504             }
505         }
506     }
507 
pushRemoteVolumeUpdateLocked(int userId)508     private void pushRemoteVolumeUpdateLocked(int userId) {
509         if (mRvc != null) {
510             try {
511                 MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId);
512                 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
513             } catch (RemoteException e) {
514                 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
515             }
516         }
517     }
518 
rememberMediaButtonReceiverLocked(MediaSessionRecord record)519     private void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
520         PendingIntent receiver = record.getMediaButtonReceiver();
521         UserRecord user = mUserRecords.get(record.getUserId());
522         if (receiver != null && user != null) {
523             user.mLastMediaButtonReceiver = receiver;
524             ComponentName component = receiver.getIntent().getComponent();
525             if (component != null && record.getPackageName().equals(component.getPackageName())) {
526                 Settings.Secure.putStringForUser(mContentResolver,
527                         Settings.System.MEDIA_BUTTON_RECEIVER, component.flattenToString(),
528                         record.getUserId());
529             }
530         }
531     }
532 
533     /**
534      * Information about a particular user. The contents of this object is
535      * guarded by mLock.
536      */
537     final class UserRecord {
538         private final int mUserId;
539         private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
540         private final Context mContext;
541         private PendingIntent mLastMediaButtonReceiver;
542         private ComponentName mRestoredMediaButtonReceiver;
543 
UserRecord(Context context, int userId)544         public UserRecord(Context context, int userId) {
545             mContext = context;
546             mUserId = userId;
547             restoreMediaButtonReceiver();
548         }
549 
destroyLocked()550         public void destroyLocked() {
551             for (int i = mSessions.size() - 1; i >= 0; i--) {
552                 MediaSessionRecord session = mSessions.get(i);
553                 MediaSessionService.this.destroySessionLocked(session);
554             }
555         }
556 
getSessionsLocked()557         public ArrayList<MediaSessionRecord> getSessionsLocked() {
558             return mSessions;
559         }
560 
addSessionLocked(MediaSessionRecord session)561         public void addSessionLocked(MediaSessionRecord session) {
562             mSessions.add(session);
563         }
564 
removeSessionLocked(MediaSessionRecord session)565         public void removeSessionLocked(MediaSessionRecord session) {
566             mSessions.remove(session);
567         }
568 
dumpLocked(PrintWriter pw, String prefix)569         public void dumpLocked(PrintWriter pw, String prefix) {
570             pw.println(prefix + "Record for user " + mUserId);
571             String indent = prefix + "  ";
572             pw.println(indent + "MediaButtonReceiver:" + mLastMediaButtonReceiver);
573             pw.println(indent + "Restored ButtonReceiver:" + mRestoredMediaButtonReceiver);
574             int size = mSessions.size();
575             pw.println(indent + size + " Sessions:");
576             for (int i = 0; i < size; i++) {
577                 // Just print the short version, the full session dump will
578                 // already be in the list of all sessions.
579                 pw.println(indent + mSessions.get(i).toString());
580             }
581         }
582 
restoreMediaButtonReceiver()583         private void restoreMediaButtonReceiver() {
584             String receiverName = Settings.Secure.getStringForUser(mContentResolver,
585                     Settings.System.MEDIA_BUTTON_RECEIVER, mUserId);
586             if (!TextUtils.isEmpty(receiverName)) {
587                 ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName);
588                 if (eventReceiver == null) {
589                     // an invalid name was persisted
590                     return;
591                 }
592                 mRestoredMediaButtonReceiver = eventReceiver;
593             }
594         }
595     }
596 
597     final class SessionsListenerRecord implements IBinder.DeathRecipient {
598         private final IActiveSessionsListener mListener;
599         private final ComponentName mComponentName;
600         private final int mUserId;
601         private final int mPid;
602         private final int mUid;
603 
SessionsListenerRecord(IActiveSessionsListener listener, ComponentName componentName, int userId, int pid, int uid)604         public SessionsListenerRecord(IActiveSessionsListener listener,
605                 ComponentName componentName,
606                 int userId, int pid, int uid) {
607             mListener = listener;
608             mComponentName = componentName;
609             mUserId = userId;
610             mPid = pid;
611             mUid = uid;
612         }
613 
614         @Override
binderDied()615         public void binderDied() {
616             synchronized (mLock) {
617                 mSessionsListeners.remove(this);
618             }
619         }
620     }
621 
622     final class SettingsObserver extends ContentObserver {
623         private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
624                 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
625 
SettingsObserver()626         private SettingsObserver() {
627             super(null);
628         }
629 
observe()630         private void observe() {
631             mContentResolver.registerContentObserver(mSecureSettingsUri,
632                     false, this, UserHandle.USER_ALL);
633         }
634 
635         @Override
onChange(boolean selfChange, Uri uri)636         public void onChange(boolean selfChange, Uri uri) {
637             updateActiveSessionListeners();
638         }
639     }
640 
641     class SessionManagerImpl extends ISessionManager.Stub {
642         private static final String EXTRA_WAKELOCK_ACQUIRED =
643                 "android.media.AudioService.WAKELOCK_ACQUIRED";
644         private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
645 
646         private boolean mVoiceButtonDown = false;
647         private boolean mVoiceButtonHandled = false;
648 
649         @Override
createSession(String packageName, ISessionCallback cb, String tag, int userId)650         public ISession createSession(String packageName, ISessionCallback cb, String tag,
651                 int userId) throws RemoteException {
652             final int pid = Binder.getCallingPid();
653             final int uid = Binder.getCallingUid();
654             final long token = Binder.clearCallingIdentity();
655             try {
656                 enforcePackageName(packageName, uid);
657                 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
658                         false /* allowAll */, true /* requireFull */, "createSession", packageName);
659                 if (cb == null) {
660                     throw new IllegalArgumentException("Controller callback cannot be null");
661                 }
662                 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
663                         .getSessionBinder();
664             } finally {
665                 Binder.restoreCallingIdentity(token);
666             }
667         }
668 
669         @Override
getSessions(ComponentName componentName, int userId)670         public List<IBinder> getSessions(ComponentName componentName, int userId) {
671             final int pid = Binder.getCallingPid();
672             final int uid = Binder.getCallingUid();
673             final long token = Binder.clearCallingIdentity();
674 
675             try {
676                 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
677                 ArrayList<IBinder> binders = new ArrayList<IBinder>();
678                 synchronized (mLock) {
679                     ArrayList<MediaSessionRecord> records = mPriorityStack
680                             .getActiveSessions(resolvedUserId);
681                     int size = records.size();
682                     for (int i = 0; i < size; i++) {
683                         binders.add(records.get(i).getControllerBinder().asBinder());
684                     }
685                 }
686                 return binders;
687             } finally {
688                 Binder.restoreCallingIdentity(token);
689             }
690         }
691 
692         @Override
addSessionsListener(IActiveSessionsListener listener, ComponentName componentName, int userId)693         public void addSessionsListener(IActiveSessionsListener listener,
694                 ComponentName componentName, int userId) throws RemoteException {
695             final int pid = Binder.getCallingPid();
696             final int uid = Binder.getCallingUid();
697             final long token = Binder.clearCallingIdentity();
698 
699             try {
700                 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
701                 synchronized (mLock) {
702                     int index = findIndexOfSessionsListenerLocked(listener);
703                     if (index != -1) {
704                         Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
705                         return;
706                     }
707                     SessionsListenerRecord record = new SessionsListenerRecord(listener,
708                             componentName, resolvedUserId, pid, uid);
709                     try {
710                         listener.asBinder().linkToDeath(record, 0);
711                     } catch (RemoteException e) {
712                         Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
713                         return;
714                     }
715                     mSessionsListeners.add(record);
716                 }
717             } finally {
718                 Binder.restoreCallingIdentity(token);
719             }
720         }
721 
722         @Override
removeSessionsListener(IActiveSessionsListener listener)723         public void removeSessionsListener(IActiveSessionsListener listener)
724                 throws RemoteException {
725             synchronized (mLock) {
726                 int index = findIndexOfSessionsListenerLocked(listener);
727                 if (index != -1) {
728                     SessionsListenerRecord record = mSessionsListeners.remove(index);
729                     try {
730                         record.mListener.asBinder().unlinkToDeath(record, 0);
731                     } catch (Exception e) {
732                         // ignore exceptions, the record is being removed
733                     }
734                 }
735             }
736         }
737 
738         /**
739          * Handles the dispatching of the media button events to one of the
740          * registered listeners, or if there was none, broadcast an
741          * ACTION_MEDIA_BUTTON intent to the rest of the system.
742          *
743          * @param keyEvent a non-null KeyEvent whose key code is one of the
744          *            supported media buttons
745          * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
746          *            while this key event is dispatched.
747          */
748         @Override
dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock)749         public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
750             if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
751                 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
752                 return;
753             }
754 
755             final int pid = Binder.getCallingPid();
756             final int uid = Binder.getCallingUid();
757             final long token = Binder.clearCallingIdentity();
758             try {
759                 if (DEBUG) {
760                     Log.d(TAG, "dispatchMediaKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
761                             + keyEvent);
762                 }
763                 if (!isUserSetupComplete()) {
764                     // Global media key handling can have the side-effect of starting new
765                     // activities which is undesirable while setup is in progress.
766                     Slog.i(TAG, "Not dispatching media key event because user "
767                             + "setup is in progress.");
768                     return;
769                 }
770                 if (isGlobalPriorityActive() && uid != Process.SYSTEM_UID) {
771                     // Prevent dispatching key event through reflection while the global priority
772                     // session is active.
773                     Slog.i(TAG, "Only the system can dispatch media key event "
774                             + "to the global priority session.");
775                     return;
776                 }
777 
778                 synchronized (mLock) {
779                     // If we don't have a media button receiver to fall back on
780                     // include non-playing sessions for dispatching
781                     boolean useNotPlayingSessions = true;
782                     for (int userId : mCurrentUserIdList) {
783                         UserRecord ur = mUserRecords.get(userId);
784                         if (ur.mLastMediaButtonReceiver != null
785                                 || ur.mRestoredMediaButtonReceiver != null) {
786                             useNotPlayingSessions = false;
787                             break;
788                         }
789                     }
790 
791                     if (DEBUG) {
792                         Log.d(TAG, "dispatchMediaKeyEvent, useNotPlayingSessions="
793                                 + useNotPlayingSessions);
794                     }
795                     MediaSessionRecord session = mPriorityStack.getDefaultMediaButtonSession(
796                             mCurrentUserIdList, useNotPlayingSessions);
797                     if (isVoiceKey(keyEvent.getKeyCode())) {
798                         handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
799                     } else {
800                         dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
801                     }
802                 }
803             } finally {
804                 Binder.restoreCallingIdentity(token);
805             }
806         }
807 
808         @Override
dispatchAdjustVolume(int suggestedStream, int delta, int flags)809         public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) {
810             final long token = Binder.clearCallingIdentity();
811             try {
812                 synchronized (mLock) {
813                     MediaSessionRecord session = mPriorityStack
814                             .getDefaultVolumeSession(mCurrentUserIdList);
815                     dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
816                 }
817             } finally {
818                 Binder.restoreCallingIdentity(token);
819             }
820         }
821 
822         @Override
setRemoteVolumeController(IRemoteVolumeController rvc)823         public void setRemoteVolumeController(IRemoteVolumeController rvc) {
824             final int pid = Binder.getCallingPid();
825             final int uid = Binder.getCallingUid();
826             final long token = Binder.clearCallingIdentity();
827             try {
828                 enforceSystemUiPermission("listen for volume changes", pid, uid);
829                 mRvc = rvc;
830             } finally {
831                 Binder.restoreCallingIdentity(token);
832             }
833         }
834 
835         @Override
isGlobalPriorityActive()836         public boolean isGlobalPriorityActive() {
837             return mPriorityStack.isGlobalPriorityActive();
838         }
839 
840         @Override
dump(FileDescriptor fd, final PrintWriter pw, String[] args)841         public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
842             if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
843                     != PackageManager.PERMISSION_GRANTED) {
844                 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
845                         + Binder.getCallingPid()
846                         + ", uid=" + Binder.getCallingUid());
847                 return;
848             }
849 
850             pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
851             pw.println();
852 
853             synchronized (mLock) {
854                 pw.println(mSessionsListeners.size() + " sessions listeners.");
855                 int count = mAllSessions.size();
856                 pw.println(count + " Sessions:");
857                 for (int i = 0; i < count; i++) {
858                     mAllSessions.get(i).dump(pw, "");
859                     pw.println();
860                 }
861                 mPriorityStack.dump(pw, "");
862 
863                 pw.println("User Records:");
864                 count = mUserRecords.size();
865                 for (int i = 0; i < count; i++) {
866                     UserRecord user = mUserRecords.get(mUserRecords.keyAt(i));
867                     user.dumpLocked(pw, "");
868                 }
869             }
870         }
871 
verifySessionsRequest(ComponentName componentName, int userId, final int pid, final int uid)872         private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
873                 final int uid) {
874             String packageName = null;
875             if (componentName != null) {
876                 // If they gave us a component name verify they own the
877                 // package
878                 packageName = componentName.getPackageName();
879                 enforcePackageName(packageName, uid);
880             }
881             // Check that they can make calls on behalf of the user and
882             // get the final user id
883             int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
884                     true /* allowAll */, true /* requireFull */, "getSessions", packageName);
885             // Check if they have the permissions or their component is
886             // enabled for the user they're calling from.
887             enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
888             return resolvedUserId;
889         }
890 
dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags, MediaSessionRecord session)891         private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
892                 MediaSessionRecord session) {
893             boolean preferSuggestedStream = false;
894             if (isValidLocalStreamType(suggestedStream)
895                     && AudioSystem.isStreamActive(suggestedStream, 0)) {
896                 preferSuggestedStream = true;
897             }
898             if (DEBUG) {
899                 Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags="
900                         + flags + ", suggestedStream=" + suggestedStream
901                         + ", preferSuggestedStream=" + preferSuggestedStream);
902             }
903             if (session == null || preferSuggestedStream) {
904                 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
905                         && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
906                     if (DEBUG) {
907                         Log.d(TAG, "No active session to adjust, skipping media only volume event");
908                     }
909                     return;
910                 }
911                 try {
912                     String packageName = getContext().getOpPackageName();
913                     mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
914                             flags, packageName, TAG);
915                 } catch (RemoteException e) {
916                     Log.e(TAG, "Error adjusting default volume.", e);
917                 }
918             } else {
919                 session.adjustVolume(direction, flags, getContext().getPackageName(),
920                         Process.SYSTEM_UID, true);
921             }
922         }
923 
handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock, MediaSessionRecord session)924         private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
925                 MediaSessionRecord session) {
926             if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
927                 // If the phone app has priority just give it the event
928                 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
929                 return;
930             }
931             int action = keyEvent.getAction();
932             boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
933             if (action == KeyEvent.ACTION_DOWN) {
934                 if (keyEvent.getRepeatCount() == 0) {
935                     mVoiceButtonDown = true;
936                     mVoiceButtonHandled = false;
937                 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
938                     mVoiceButtonHandled = true;
939                     startVoiceInput(needWakeLock);
940                 }
941             } else if (action == KeyEvent.ACTION_UP) {
942                 if (mVoiceButtonDown) {
943                     mVoiceButtonDown = false;
944                     if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
945                         // Resend the down then send this event through
946                         KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
947                         dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
948                         dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
949                     }
950                 }
951             }
952         }
953 
dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock, MediaSessionRecord session)954         private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
955                 MediaSessionRecord session) {
956             if (session != null) {
957                 if (DEBUG_MEDIA_KEY_EVENT) {
958                     Log.d(TAG, "Sending " + keyEvent + " to " + session);
959                 }
960                 if (needWakeLock) {
961                     mKeyEventReceiver.aquireWakeLockLocked();
962                 }
963                 // If we don't need a wakelock use -1 as the id so we
964                 // won't release it later
965                 session.sendMediaButton(keyEvent,
966                         needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
967                         mKeyEventReceiver, Process.SYSTEM_UID,
968                         getContext().getPackageName());
969             } else {
970                 // Launch the last PendingIntent we had with priority
971                 for (int userId : mCurrentUserIdList) {
972                     UserRecord user = mUserRecords.get(userId);
973                     if (user.mLastMediaButtonReceiver == null
974                             && user.mRestoredMediaButtonReceiver == null) {
975                         continue;
976                     }
977                     if (needWakeLock) {
978                         mKeyEventReceiver.aquireWakeLockLocked();
979                     }
980                     Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
981                     mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
982                     mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
983                     try {
984                         if (user.mLastMediaButtonReceiver != null) {
985                             if (DEBUG_MEDIA_KEY_EVENT) {
986                                 Log.d(TAG, "Sending " + keyEvent
987                                         + " to the last known pendingIntent "
988                                         + user.mLastMediaButtonReceiver);
989                             }
990                             user.mLastMediaButtonReceiver.send(getContext(),
991                                     needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
992                                     mediaButtonIntent, mKeyEventReceiver, mHandler);
993                         } else {
994                             if (DEBUG_MEDIA_KEY_EVENT) {
995                                 Log.d(TAG, "Sending " + keyEvent + " to the restored intent "
996                                         + user.mRestoredMediaButtonReceiver);
997                             }
998                             mediaButtonIntent.setComponent(user.mRestoredMediaButtonReceiver);
999                             getContext().sendBroadcastAsUser(mediaButtonIntent,
1000                                     UserHandle.of(userId));
1001                         }
1002                     } catch (CanceledException e) {
1003                         Log.i(TAG, "Error sending key event to media button receiver "
1004                                 + user.mLastMediaButtonReceiver, e);
1005                     }
1006                     return;
1007                 }
1008                 if (DEBUG) {
1009                     Log.d(TAG, "Sending media key ordered broadcast");
1010                 }
1011                 if (needWakeLock) {
1012                     mMediaEventWakeLock.acquire();
1013                 }
1014                 // Fallback to legacy behavior
1015                 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
1016                 keyIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
1017                 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
1018                 if (needWakeLock) {
1019                     keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
1020                 }
1021                 // Send broadcast only to the full user.
1022                 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.CURRENT,
1023                         null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
1024             }
1025         }
1026 
startVoiceInput(boolean needWakeLock)1027         private void startVoiceInput(boolean needWakeLock) {
1028             Intent voiceIntent = null;
1029             // select which type of search to launch:
1030             // - screen on and device unlocked: action is ACTION_WEB_SEARCH
1031             // - device locked or screen off: action is
1032             // ACTION_VOICE_SEARCH_HANDS_FREE
1033             // with EXTRA_SECURE set to true if the device is securely locked
1034             PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1035             boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1036             if (!isLocked && pm.isScreenOn()) {
1037                 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
1038                 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
1039             } else {
1040                 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
1041                 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
1042                         isLocked && mKeyguardManager.isKeyguardSecure());
1043                 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
1044             }
1045             // start the search activity
1046             if (needWakeLock) {
1047                 mMediaEventWakeLock.acquire();
1048             }
1049             try {
1050                 if (voiceIntent != null) {
1051                     voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1052                             | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1053                     if (DEBUG) Log.d(TAG, "voiceIntent: " + voiceIntent);
1054                     getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
1055                 }
1056             } catch (ActivityNotFoundException e) {
1057                 Log.w(TAG, "No activity for search: " + e);
1058             } finally {
1059                 if (needWakeLock) {
1060                     mMediaEventWakeLock.release();
1061                 }
1062             }
1063         }
1064 
isVoiceKey(int keyCode)1065         private boolean isVoiceKey(int keyCode) {
1066             return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
1067         }
1068 
isUserSetupComplete()1069         private boolean isUserSetupComplete() {
1070             return Settings.Secure.getIntForUser(getContext().getContentResolver(),
1071                     Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
1072         }
1073 
1074         // we only handle public stream types, which are 0-5
isValidLocalStreamType(int streamType)1075         private boolean isValidLocalStreamType(int streamType) {
1076             return streamType >= AudioManager.STREAM_VOICE_CALL
1077                     && streamType <= AudioManager.STREAM_NOTIFICATION;
1078         }
1079 
1080         private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
1081 
1082         class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
1083                 PendingIntent.OnFinished {
1084             private final Handler mHandler;
1085             private int mRefCount = 0;
1086             private int mLastTimeoutId = 0;
1087 
KeyEventWakeLockReceiver(Handler handler)1088             public KeyEventWakeLockReceiver(Handler handler) {
1089                 super(handler);
1090                 mHandler = handler;
1091             }
1092 
onTimeout()1093             public void onTimeout() {
1094                 synchronized (mLock) {
1095                     if (mRefCount == 0) {
1096                         // We've already released it, so just return
1097                         return;
1098                     }
1099                     mLastTimeoutId++;
1100                     mRefCount = 0;
1101                     releaseWakeLockLocked();
1102                 }
1103             }
1104 
aquireWakeLockLocked()1105             public void aquireWakeLockLocked() {
1106                 if (mRefCount == 0) {
1107                     mMediaEventWakeLock.acquire();
1108                 }
1109                 mRefCount++;
1110                 mHandler.removeCallbacks(this);
1111                 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1112 
1113             }
1114 
1115             @Override
run()1116             public void run() {
1117                 onTimeout();
1118             }
1119 
1120             @Override
onReceiveResult(int resultCode, Bundle resultData)1121             protected void onReceiveResult(int resultCode, Bundle resultData) {
1122                 if (resultCode < mLastTimeoutId) {
1123                     // Ignore results from calls that were before the last
1124                     // timeout, just in case.
1125                     return;
1126                 } else {
1127                     synchronized (mLock) {
1128                         if (mRefCount > 0) {
1129                             mRefCount--;
1130                             if (mRefCount == 0) {
1131                                 releaseWakeLockLocked();
1132                             }
1133                         }
1134                     }
1135                 }
1136             }
1137 
releaseWakeLockLocked()1138             private void releaseWakeLockLocked() {
1139                 mMediaEventWakeLock.release();
1140                 mHandler.removeCallbacks(this);
1141             }
1142 
1143             @Override
onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode, String resultData, Bundle resultExtras)1144             public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
1145                     String resultData, Bundle resultExtras) {
1146                 onReceiveResult(resultCode, null);
1147             }
1148         };
1149 
1150         BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1151             @Override
1152             public void onReceive(Context context, Intent intent) {
1153                 if (intent == null) {
1154                     return;
1155                 }
1156                 Bundle extras = intent.getExtras();
1157                 if (extras == null) {
1158                     return;
1159                 }
1160                 synchronized (mLock) {
1161                     if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1162                             && mMediaEventWakeLock.isHeld()) {
1163                         mMediaEventWakeLock.release();
1164                     }
1165                 }
1166             }
1167         };
1168     }
1169 
1170     final class MessageHandler extends Handler {
1171         private static final int MSG_SESSIONS_CHANGED = 1;
1172 
1173         @Override
handleMessage(Message msg)1174         public void handleMessage(Message msg) {
1175             switch (msg.what) {
1176                 case MSG_SESSIONS_CHANGED:
1177                     pushSessionsChanged(msg.arg1);
1178                     break;
1179             }
1180         }
1181 
post(int what, int arg1, int arg2)1182         public void post(int what, int arg1, int arg2) {
1183             obtainMessage(what, arg1, arg2).sendToTarget();
1184         }
1185     }
1186 }
1187