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