• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 com.android.server.media;
18 
19 import android.app.ActivityThread;
20 import android.content.Context;
21 import android.media.MediaMetadata;
22 import android.media.session.ISessionManager;
23 import android.media.session.MediaController;
24 import android.media.session.MediaSession;
25 import android.media.session.MediaSessionManager;
26 import android.media.session.PlaybackState;
27 import android.os.Bundle;
28 import android.os.HandlerThread;
29 import android.os.Looper;
30 import android.os.RemoteException;
31 import android.os.ServiceManager;
32 import android.os.ShellCommand;
33 import android.os.SystemClock;
34 import android.text.TextUtils;
35 import android.view.InputDevice;
36 import android.view.KeyCharacterMap;
37 import android.view.KeyEvent;
38 
39 import java.io.BufferedReader;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.io.InputStreamReader;
43 import java.io.PrintWriter;
44 import java.util.List;
45 
46 /**
47  * ShellCommand for MediaSessionService.
48  */
49 public class MediaShellCommand extends ShellCommand {
50     // This doesn't belongs to any package. Setting the package name to empty string.
51     private static final String PACKAGE_NAME = "";
52     private static ActivityThread sThread;
53     private static MediaSessionManager sMediaSessionManager;
54     private ISessionManager mSessionService;
55     private PrintWriter mWriter;
56     private PrintWriter mErrorWriter;
57     private InputStream mInput;
58 
59     @Override
onCommand(String cmd)60     public int onCommand(String cmd) {
61         mWriter = getOutPrintWriter();
62         mErrorWriter = getErrPrintWriter();
63         mInput = getRawInputStream();
64 
65         if (TextUtils.isEmpty(cmd)) {
66             return handleDefaultCommands(cmd);
67         }
68         if (sThread == null) {
69             Looper.prepare();
70             sThread = ActivityThread.currentActivityThread();
71             Context context = sThread.getSystemContext();
72             sMediaSessionManager =
73                     (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
74         }
75         mSessionService = ISessionManager.Stub.asInterface(ServiceManager.checkService(
76                 Context.MEDIA_SESSION_SERVICE));
77         if (mSessionService == null) {
78             throw new IllegalStateException(
79                     "Can't connect to media session service; is the system running?");
80         }
81 
82         try {
83             if (cmd.equals("dispatch")) {
84                 runDispatch();
85             } else if (cmd.equals("list-sessions")) {
86                 runListSessions();
87             } else if (cmd.equals("monitor")) {
88                 runMonitor();
89             } else if (cmd.equals("volume")) {
90                 runVolume();
91             } else {
92                 showError("Error: unknown command '" + cmd + "'");
93                 return -1;
94             }
95         } catch (Exception e) {
96             showError(e.toString());
97             return -1;
98         }
99         return 0;
100     }
101 
102     @Override
onHelp()103     public void onHelp() {
104         mWriter.println("usage: media_session [subcommand] [options]");
105         mWriter.println("       media_session dispatch KEY");
106         mWriter.println("       media_session dispatch KEY");
107         mWriter.println("       media_session list-sessions");
108         mWriter.println("       media_session monitor <tag>");
109         mWriter.println("       media_session volume [options]");
110         mWriter.println();
111         mWriter.println("media_session dispatch: dispatch a media key to the system.");
112         mWriter.println("                KEY may be: play, pause, play-pause, mute, headsethook,");
113         mWriter.println("                stop, next, previous, rewind, record, fast-forword.");
114         mWriter.println("media_session list-sessions: print a list of the current sessions.");
115         mWriter.println("media_session monitor: monitor updates to the specified session.");
116         mWriter.println("                       Use the tag from list-sessions.");
117         mWriter.println("media_session volume:  " + VolumeCtrl.USAGE);
118         mWriter.println();
119     }
120 
sendMediaKey(KeyEvent event)121     private void sendMediaKey(KeyEvent event) {
122         try {
123             mSessionService.dispatchMediaKeyEvent(PACKAGE_NAME, false, event, false);
124         } catch (RemoteException e) {
125         }
126     }
127 
runMonitor()128     private void runMonitor() throws Exception {
129         String id = getNextArgRequired();
130         if (id == null) {
131             showError("Error: must include a session id");
132             return;
133         }
134 
135         boolean success = false;
136         try {
137             List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null);
138             for (MediaController controller : controllers) {
139                 try {
140                     if (controller != null && id.equals(controller.getTag())) {
141                         MediaShellCommand.ControllerMonitor monitor =
142                                 new MediaShellCommand.ControllerMonitor(controller);
143                         monitor.run();
144                         success = true;
145                         break;
146                     }
147                 } catch (RemoteException e) {
148                     // ignore
149                 }
150             }
151         } catch (Exception e) {
152             mErrorWriter.println("***Error monitoring session*** " + e.getMessage());
153         }
154         if (!success) {
155             mErrorWriter.println("No session found with id " + id);
156         }
157     }
158 
runDispatch()159     private void runDispatch() throws Exception {
160         String cmd = getNextArgRequired();
161         int keycode;
162         if ("play".equals(cmd)) {
163             keycode = KeyEvent.KEYCODE_MEDIA_PLAY;
164         } else if ("pause".equals(cmd)) {
165             keycode = KeyEvent.KEYCODE_MEDIA_PAUSE;
166         } else if ("play-pause".equals(cmd)) {
167             keycode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
168         } else if ("mute".equals(cmd)) {
169             keycode = KeyEvent.KEYCODE_MUTE;
170         } else if ("headsethook".equals(cmd)) {
171             keycode = KeyEvent.KEYCODE_HEADSETHOOK;
172         } else if ("stop".equals(cmd)) {
173             keycode = KeyEvent.KEYCODE_MEDIA_STOP;
174         } else if ("next".equals(cmd)) {
175             keycode = KeyEvent.KEYCODE_MEDIA_NEXT;
176         } else if ("previous".equals(cmd)) {
177             keycode = KeyEvent.KEYCODE_MEDIA_PREVIOUS;
178         } else if ("rewind".equals(cmd)) {
179             keycode = KeyEvent.KEYCODE_MEDIA_REWIND;
180         } else if ("record".equals(cmd)) {
181             keycode = KeyEvent.KEYCODE_MEDIA_RECORD;
182         } else if ("fast-forward".equals(cmd)) {
183             keycode = KeyEvent.KEYCODE_MEDIA_FAST_FORWARD;
184         } else {
185             showError("Error: unknown dispatch code '" + cmd + "'");
186             return;
187         }
188         final long now = SystemClock.uptimeMillis();
189         sendMediaKey(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keycode, 0, 0,
190                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
191         sendMediaKey(new KeyEvent(now, now, KeyEvent.ACTION_UP, keycode, 0, 0,
192                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
193     }
194 
log(String code, String msg)195     void log(String code, String msg) {
196         mWriter.println(code + " " + msg);
197     }
198 
showError(String errMsg)199     void showError(String errMsg) {
200         onHelp();
201         mErrorWriter.println(errMsg);
202     }
203 
204     class ControllerCallback extends MediaController.Callback {
205         @Override
onSessionDestroyed()206         public void onSessionDestroyed() {
207             mWriter.println("onSessionDestroyed. Enter q to quit.");
208         }
209 
210         @Override
onSessionEvent(String event, Bundle extras)211         public void onSessionEvent(String event, Bundle extras) {
212             mWriter.println("onSessionEvent event=" + event + ", extras=" + extras);
213         }
214 
215         @Override
onPlaybackStateChanged(PlaybackState state)216         public void onPlaybackStateChanged(PlaybackState state) {
217             mWriter.println("onPlaybackStateChanged " + state);
218         }
219 
220         @Override
onMetadataChanged(MediaMetadata metadata)221         public void onMetadataChanged(MediaMetadata metadata) {
222             String mmString = metadata == null ? null : "title=" + metadata
223                     .getDescription();
224             mWriter.println("onMetadataChanged " + mmString);
225         }
226 
227         @Override
onQueueChanged(List<MediaSession.QueueItem> queue)228         public void onQueueChanged(List<MediaSession.QueueItem> queue) {
229             mWriter.println("onQueueChanged, "
230                     + (queue == null ? "null queue" : " size=" + queue.size()));
231         }
232 
233         @Override
onQueueTitleChanged(CharSequence title)234         public void onQueueTitleChanged(CharSequence title) {
235             mWriter.println("onQueueTitleChange " + title);
236         }
237 
238         @Override
onExtrasChanged(Bundle extras)239         public void onExtrasChanged(Bundle extras) {
240             mWriter.println("onExtrasChanged " + extras);
241         }
242 
243         @Override
onAudioInfoChanged(MediaController.PlaybackInfo info)244         public void onAudioInfoChanged(MediaController.PlaybackInfo info) {
245             mWriter.println("onAudioInfoChanged " + info);
246         }
247     }
248 
249     private class ControllerMonitor {
250         private final MediaController mController;
251         private final MediaShellCommand.ControllerCallback mControllerCallback;
252 
ControllerMonitor(MediaController controller)253         ControllerMonitor(MediaController controller) {
254             mController = controller;
255             mControllerCallback = new MediaShellCommand.ControllerCallback();
256         }
257 
printUsageMessage()258         void printUsageMessage() {
259             try {
260                 mWriter.println("V2Monitoring session " + mController.getTag()
261                         + "...  available commands: play, pause, next, previous");
262             } catch (RuntimeException e) {
263                 mWriter.println("Error trying to monitor session!");
264             }
265             mWriter.println("(q)uit: finish monitoring");
266         }
267 
run()268         void run() throws RemoteException {
269             printUsageMessage();
270             HandlerThread cbThread = new HandlerThread("MediaCb") {
271                 @Override
272                 protected void onLooperPrepared() {
273                     try {
274                         mController.registerCallback(mControllerCallback);
275                     } catch (RuntimeException e) {
276                         mErrorWriter.println("Error registering monitor callback");
277                     }
278                 }
279             };
280             cbThread.start();
281 
282             try {
283                 InputStreamReader converter = new InputStreamReader(mInput);
284                 BufferedReader in = new BufferedReader(converter);
285                 String line;
286 
287                 while (true) {
288                     mWriter.flush();
289                     mErrorWriter.flush();
290                     if ((line = in.readLine()) == null) break;
291                     boolean addNewline = true;
292                     if (line.length() <= 0) {
293                         addNewline = false;
294                     } else if ("q".equals(line) || "quit".equals(line)) {
295                         break;
296                     } else if ("play".equals(line)) {
297                         dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PLAY);
298                     } else if ("pause".equals(line)) {
299                         dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE);
300                     } else if ("next".equals(line)) {
301                         dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_NEXT);
302                     } else if ("previous".equals(line)) {
303                         dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
304                     } else {
305                         mErrorWriter.println("Invalid command: " + line);
306                     }
307 
308                     synchronized (this) {
309                         if (addNewline) {
310                             mWriter.println("");
311                         }
312                         printUsageMessage();
313                     }
314                 }
315             } catch (IOException e) {
316                 e.printStackTrace();
317             } finally {
318                 cbThread.getLooper().quit();
319                 try {
320                     mController.unregisterCallback(mControllerCallback);
321                 } catch (Exception e) {
322                     // ignoring
323                 }
324             }
325         }
326 
dispatchKeyCode(int keyCode)327         private void dispatchKeyCode(int keyCode) {
328             final long now = SystemClock.uptimeMillis();
329             KeyEvent down = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
330                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
331             KeyEvent up = new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
332                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
333             try {
334                 mController.dispatchMediaButtonEvent(down);
335                 mController.dispatchMediaButtonEvent(up);
336             } catch (RuntimeException e) {
337                 mErrorWriter.println("Failed to dispatch " + keyCode);
338             }
339         }
340     }
341 
runListSessions()342     private void runListSessions() {
343         mWriter.println("Sessions:");
344         try {
345             List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null);
346             for (MediaController controller : controllers) {
347                 if (controller != null) {
348                     try {
349                         mWriter.println("  tag=" + controller.getTag()
350                                 + ", package=" + controller.getPackageName());
351                     } catch (RuntimeException e) {
352                         // ignore
353                     }
354                 }
355             }
356         } catch (Exception e) {
357             mErrorWriter.println("***Error listing sessions***");
358         }
359     }
360 
361     //=================================
362     // "volume" command for stream volume control
runVolume()363     private void runVolume() throws Exception {
364         VolumeCtrl.run(this);
365     }
366 }
367