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