1 /*
2 **
3 ** Copyright 2013, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 **     http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17 
18 package com.android.commands.media;
19 
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.MediaController.PlaybackInfo;
26 import android.media.session.MediaSession.QueueItem;
27 import android.media.session.MediaSessionManager;
28 import android.media.session.PlaybackState;
29 import android.os.Bundle;
30 import android.os.HandlerThread;
31 import android.os.Looper;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.os.SystemClock;
35 import android.util.AndroidException;
36 import android.view.InputDevice;
37 import android.view.KeyCharacterMap;
38 import android.view.KeyEvent;
39 
40 import com.android.internal.os.BaseCommand;
41 
42 import java.io.BufferedReader;
43 import java.io.IOException;
44 import java.io.InputStreamReader;
45 import java.io.PrintStream;
46 import java.util.List;
47 
48 public class Media extends BaseCommand {
49     // This doesn't belongs to any package. Setting the package name to empty string.
50     private static final String PACKAGE_NAME = "";
51     private static ActivityThread sThread;
52     private static MediaSessionManager sMediaSessionManager;
53     private ISessionManager mSessionService;
54 
55     /**
56      * Command-line entry point.
57      *
58      * @param args The command-line arguments
59      */
main(String[] args)60     public static void main(String[] args) {
61         (new Media()).run(args);
62     }
63 
64     @Override
onShowUsage(PrintStream out)65     public void onShowUsage(PrintStream out) {
66         out.println(
67                 "usage: media [subcommand] [options]\n" +
68                 "       media dispatch KEY\n" +
69                 "       media list-sessions\n" +
70                 "       media monitor <tag>\n" +
71                 "       media volume [options]\n" +
72                 "\n" +
73                 "media dispatch: dispatch a media key to the system.\n" +
74                 "                KEY may be: play, pause, play-pause, mute, headsethook,\n" +
75                 "                stop, next, previous, rewind, record, fast-forword.\n" +
76                 "media list-sessions: print a list of the current sessions.\n" +
77                         "media monitor: monitor updates to the specified session.\n" +
78                 "                       Use the tag from list-sessions.\n" +
79                 "media volume:  " + VolumeCtrl.USAGE
80         );
81     }
82 
83     @Override
onRun()84     public void onRun() throws Exception {
85         if (sThread == null) {
86             Looper.prepareMainLooper();
87             sThread = ActivityThread.systemMain();
88             Context context = sThread.getSystemContext();
89             sMediaSessionManager =
90                     (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
91         }
92         mSessionService = ISessionManager.Stub.asInterface(ServiceManager.checkService(
93                 Context.MEDIA_SESSION_SERVICE));
94         if (mSessionService == null) {
95             System.err.println(NO_SYSTEM_ERROR_CODE);
96             throw new AndroidException(
97                     "Can't connect to media session service; is the system running?");
98         }
99 
100         String op = nextArgRequired();
101 
102         if (op.equals("dispatch")) {
103             runDispatch();
104         } else if (op.equals("list-sessions")) {
105             runListSessions();
106         } else if (op.equals("monitor")) {
107             runMonitor();
108         } else if (op.equals("volume")) {
109             runVolume();
110         } else {
111             showError("Error: unknown command '" + op + "'");
112             return;
113         }
114     }
115 
sendMediaKey(KeyEvent event)116     private void sendMediaKey(KeyEvent event) {
117         try {
118             mSessionService.dispatchMediaKeyEvent(PACKAGE_NAME, false, event, false);
119         } catch (RemoteException e) {
120         }
121     }
122 
runMonitor()123     private void runMonitor() throws Exception {
124         String id = nextArgRequired();
125         if (id == null) {
126             showError("Error: must include a session id");
127             return;
128         }
129 
130         boolean success = false;
131         try {
132             List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null);
133             for (MediaController controller : controllers) {
134                 try {
135                     if (controller != null && id.equals(controller.getTag())) {
136                         ControllerMonitor monitor = new ControllerMonitor(controller);
137                         monitor.run();
138                         success = true;
139                         break;
140                     }
141                 } catch (RemoteException e) {
142                     // ignore
143                 }
144             }
145         } catch (Exception e) {
146             System.out.println("***Error monitoring session*** " + e.getMessage());
147         }
148         if (!success) {
149             System.out.println("No session found with id " + id);
150         }
151     }
152 
runDispatch()153     private void runDispatch() throws Exception {
154         String cmd = nextArgRequired();
155         int keycode;
156         if ("play".equals(cmd)) {
157             keycode = KeyEvent.KEYCODE_MEDIA_PLAY;
158         } else if ("pause".equals(cmd)) {
159             keycode = KeyEvent.KEYCODE_MEDIA_PAUSE;
160         } else if ("play-pause".equals(cmd)) {
161             keycode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
162         } else if ("mute".equals(cmd)) {
163             keycode = KeyEvent.KEYCODE_MUTE;
164         } else if ("headsethook".equals(cmd)) {
165             keycode = KeyEvent.KEYCODE_HEADSETHOOK;
166         } else if ("stop".equals(cmd)) {
167             keycode = KeyEvent.KEYCODE_MEDIA_STOP;
168         } else if ("next".equals(cmd)) {
169             keycode = KeyEvent.KEYCODE_MEDIA_NEXT;
170         } else if ("previous".equals(cmd)) {
171             keycode = KeyEvent.KEYCODE_MEDIA_PREVIOUS;
172         } else if ("rewind".equals(cmd)) {
173             keycode = KeyEvent.KEYCODE_MEDIA_REWIND;
174         } else if ("record".equals(cmd)) {
175             keycode = KeyEvent.KEYCODE_MEDIA_RECORD;
176         } else if ("fast-forward".equals(cmd)) {
177             keycode = KeyEvent.KEYCODE_MEDIA_FAST_FORWARD;
178         } else {
179             showError("Error: unknown dispatch code '" + cmd + "'");
180             return;
181         }
182         final long now = SystemClock.uptimeMillis();
183         sendMediaKey(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keycode, 0, 0,
184                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
185         sendMediaKey(new KeyEvent(now, now, KeyEvent.ACTION_UP, keycode, 0, 0,
186                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
187     }
188 
189     class ControllerCallback extends MediaController.Callback {
190         @Override
onSessionDestroyed()191         public void onSessionDestroyed() {
192             System.out.println("onSessionDestroyed. Enter q to quit.");
193         }
194 
195         @Override
onSessionEvent(String event, Bundle extras)196         public void onSessionEvent(String event, Bundle extras) {
197             System.out.println("onSessionEvent event=" + event + ", extras=" + extras);
198         }
199 
200         @Override
onPlaybackStateChanged(PlaybackState state)201         public void onPlaybackStateChanged(PlaybackState state) {
202             System.out.println("onPlaybackStateChanged " + state);
203         }
204 
205         @Override
onMetadataChanged(MediaMetadata metadata)206         public void onMetadataChanged(MediaMetadata metadata) {
207             String mmString = metadata == null ? null : "title=" + metadata
208                     .getDescription();
209             System.out.println("onMetadataChanged " + mmString);
210         }
211 
212         @Override
onQueueChanged(List<QueueItem> queue)213         public void onQueueChanged(List<QueueItem> queue) {
214             System.out.println("onQueueChanged, "
215                     + (queue == null ? "null queue" : " size=" + queue.size()));
216         }
217 
218         @Override
onQueueTitleChanged(CharSequence title)219         public void onQueueTitleChanged(CharSequence title) {
220             System.out.println("onQueueTitleChange " + title);
221         }
222 
223         @Override
onExtrasChanged(Bundle extras)224         public void onExtrasChanged(Bundle extras) {
225             System.out.println("onExtrasChanged " + extras);
226         }
227 
228         @Override
onAudioInfoChanged(PlaybackInfo info)229         public void onAudioInfoChanged(PlaybackInfo info) {
230             System.out.println("onAudioInfoChanged " + info);
231         }
232     }
233 
234     private class ControllerMonitor {
235         private final MediaController mController;
236         private final ControllerCallback mControllerCallback;
237 
ControllerMonitor(MediaController controller)238         ControllerMonitor(MediaController controller) {
239             mController = controller;
240             mControllerCallback = new ControllerCallback();
241         }
242 
printUsageMessage()243         void printUsageMessage() {
244             try {
245                 System.out.println("V2Monitoring session " + mController.getTag()
246                         + "...  available commands: play, pause, next, previous");
247             } catch (RuntimeException e) {
248                 System.out.println("Error trying to monitor session!");
249             }
250             System.out.println("(q)uit: finish monitoring");
251         }
252 
run()253         void run() throws RemoteException {
254             printUsageMessage();
255             HandlerThread cbThread = new HandlerThread("MediaCb") {
256                 @Override
257                 protected void onLooperPrepared() {
258                     try {
259                         mController.registerCallback(mControllerCallback);
260                     } catch (RuntimeException e) {
261                         System.out.println("Error registering monitor callback");
262                     }
263                 }
264             };
265             cbThread.start();
266 
267             try {
268                 InputStreamReader converter = new InputStreamReader(System.in);
269                 BufferedReader in = new BufferedReader(converter);
270                 String line;
271 
272                 while ((line = in.readLine()) != null) {
273                     boolean addNewline = true;
274                     if (line.length() <= 0) {
275                         addNewline = false;
276                     } else if ("q".equals(line) || "quit".equals(line)) {
277                         break;
278                     } else if ("play".equals(line)) {
279                         dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PLAY);
280                     } else if ("pause".equals(line)) {
281                         dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE);
282                     } else if ("next".equals(line)) {
283                         dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_NEXT);
284                     } else if ("previous".equals(line)) {
285                         dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
286                     } else {
287                         System.out.println("Invalid command: " + line);
288                     }
289 
290                     synchronized (this) {
291                         if (addNewline) {
292                             System.out.println("");
293                         }
294                         printUsageMessage();
295                     }
296                 }
297             } catch (IOException e) {
298                 e.printStackTrace();
299             } finally {
300                 cbThread.getLooper().quit();
301                 try {
302                     mController.unregisterCallback(mControllerCallback);
303                 } catch (Exception e) {
304                     // ignoring
305                 }
306             }
307         }
308 
dispatchKeyCode(int keyCode)309         private void dispatchKeyCode(int keyCode) {
310             final long now = SystemClock.uptimeMillis();
311             KeyEvent down = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
312                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
313             KeyEvent up = new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
314                     KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
315             try {
316                 mController.dispatchMediaButtonEvent(down);
317                 mController.dispatchMediaButtonEvent(up);
318             } catch (RuntimeException e) {
319                 System.out.println("Failed to dispatch " + keyCode);
320             }
321         }
322     }
323 
runListSessions()324     private void runListSessions() {
325         System.out.println("Sessions:");
326         try {
327             List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null);
328             for (MediaController controller : controllers) {
329                 if (controller != null) {
330                     try {
331                         System.out.println("  tag=" + controller.getTag()
332                                 + ", package=" + controller.getPackageName());
333                     } catch (RuntimeException e) {
334                         // ignore
335                     }
336                 }
337             }
338         } catch (Exception e) {
339             System.out.println("***Error listing sessions***");
340         }
341     }
342 
343     //=================================
344     // "volume" command for stream volume control
runVolume()345     private void runVolume() throws Exception {
346         VolumeCtrl.run(this);
347     }
348 }
349