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