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.ActivityManager; 21 import android.content.Context; 22 import android.content.pm.ParceledListSlice; 23 import android.media.MediaMetadata; 24 import android.media.session.ISessionController; 25 import android.media.session.ISessionControllerCallback; 26 import android.media.session.ISessionManager; 27 import android.media.session.ParcelableVolumeInfo; 28 import android.media.session.PlaybackState; 29 import android.os.Bundle; 30 import android.os.HandlerThread; 31 import android.os.IBinder; 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 ISessionManager mSessionService; 52 53 /** 54 * Command-line entry point. 55 * 56 * @param args The command-line arguments 57 */ 58 public static void main(String[] args) { 59 (new Media()).run(args); 60 } 61 62 @Override 63 public void onShowUsage(PrintStream out) { 64 out.println( 65 "usage: media [subcommand] [options]\n" + 66 " media dispatch KEY\n" + 67 " media list-sessions\n" + 68 " media monitor <tag>\n" + 69 " media volume [options]\n" + 70 "\n" + 71 "media dispatch: dispatch a media key to the system.\n" + 72 " KEY may be: play, pause, play-pause, mute, headsethook,\n" + 73 " stop, next, previous, rewind, record, fast-forword.\n" + 74 "media list-sessions: print a list of the current sessions.\n" + 75 "media monitor: monitor updates to the specified session.\n" + 76 " Use the tag from list-sessions.\n" + 77 "media volume: " + VolumeCtrl.USAGE 78 ); 79 } 80 81 @Override 82 public void onRun() throws Exception { 83 mSessionService = ISessionManager.Stub.asInterface(ServiceManager.checkService( 84 Context.MEDIA_SESSION_SERVICE)); 85 if (mSessionService == null) { 86 System.err.println(NO_SYSTEM_ERROR_CODE); 87 throw new AndroidException( 88 "Can't connect to media session service; is the system running?"); 89 } 90 91 String op = nextArgRequired(); 92 93 if (op.equals("dispatch")) { 94 runDispatch(); 95 } else if (op.equals("list-sessions")) { 96 runListSessions(); 97 } else if (op.equals("monitor")) { 98 runMonitor(); 99 } else if (op.equals("volume")) { 100 runVolume(); 101 } else { 102 showError("Error: unknown command '" + op + "'"); 103 return; 104 } 105 } 106 107 private void sendMediaKey(KeyEvent event) { 108 try { 109 mSessionService.dispatchMediaKeyEvent(PACKAGE_NAME, false, event, false); 110 } catch (RemoteException e) { 111 } 112 } 113 114 private void runMonitor() throws Exception { 115 String id = nextArgRequired(); 116 if (id == null) { 117 showError("Error: must include a session id"); 118 return; 119 } 120 boolean success = false; 121 try { 122 List<IBinder> sessions = mSessionService 123 .getSessions(null, ActivityManager.getCurrentUser()); 124 for (IBinder session : sessions) { 125 ISessionController controller = ISessionController.Stub.asInterface(session); 126 try { 127 if (controller != null && id.equals(controller.getTag())) { 128 ControllerMonitor monitor = new ControllerMonitor(controller); 129 monitor.run(); 130 success = true; 131 break; 132 } 133 } catch (RemoteException e) { 134 // ignore 135 } 136 } 137 } catch (Exception e) { 138 System.out.println("***Error monitoring session*** " + e.getMessage()); 139 } 140 if (!success) { 141 System.out.println("No session found with id " + id); 142 } 143 } 144 145 private void runDispatch() throws Exception { 146 String cmd = nextArgRequired(); 147 int keycode; 148 if ("play".equals(cmd)) { 149 keycode = KeyEvent.KEYCODE_MEDIA_PLAY; 150 } else if ("pause".equals(cmd)) { 151 keycode = KeyEvent.KEYCODE_MEDIA_PAUSE; 152 } else if ("play-pause".equals(cmd)) { 153 keycode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE; 154 } else if ("mute".equals(cmd)) { 155 keycode = KeyEvent.KEYCODE_MUTE; 156 } else if ("headsethook".equals(cmd)) { 157 keycode = KeyEvent.KEYCODE_HEADSETHOOK; 158 } else if ("stop".equals(cmd)) { 159 keycode = KeyEvent.KEYCODE_MEDIA_STOP; 160 } else if ("next".equals(cmd)) { 161 keycode = KeyEvent.KEYCODE_MEDIA_NEXT; 162 } else if ("previous".equals(cmd)) { 163 keycode = KeyEvent.KEYCODE_MEDIA_PREVIOUS; 164 } else if ("rewind".equals(cmd)) { 165 keycode = KeyEvent.KEYCODE_MEDIA_REWIND; 166 } else if ("record".equals(cmd)) { 167 keycode = KeyEvent.KEYCODE_MEDIA_RECORD; 168 } else if ("fast-forward".equals(cmd)) { 169 keycode = KeyEvent.KEYCODE_MEDIA_FAST_FORWARD; 170 } else { 171 showError("Error: unknown dispatch code '" + cmd + "'"); 172 return; 173 } 174 final long now = SystemClock.uptimeMillis(); 175 sendMediaKey(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keycode, 0, 0, 176 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD)); 177 sendMediaKey(new KeyEvent(now, now, KeyEvent.ACTION_UP, keycode, 0, 0, 178 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD)); 179 } 180 181 class ControllerMonitor extends ISessionControllerCallback.Stub { 182 private final ISessionController mController; 183 184 public ControllerMonitor(ISessionController controller) { 185 mController = controller; 186 } 187 188 @Override 189 public void onSessionDestroyed() { 190 System.out.println("onSessionDestroyed. Enter q to quit."); 191 } 192 193 @Override 194 public void onEvent(String event, Bundle extras) { 195 System.out.println("onSessionEvent event=" + event + ", extras=" + extras); 196 } 197 198 @Override 199 public void onPlaybackStateChanged(PlaybackState state) { 200 System.out.println("onPlaybackStateChanged " + state); 201 } 202 203 @Override 204 public void onMetadataChanged(MediaMetadata metadata) { 205 String mmString = metadata == null ? null : "title=" + metadata 206 .getDescription(); 207 System.out.println("onMetadataChanged " + mmString); 208 } 209 210 @Override 211 public void onQueueChanged(ParceledListSlice queue) throws RemoteException { 212 System.out.println("onQueueChanged, " 213 + (queue == null ? "null queue" : " size=" + queue.getList().size())); 214 } 215 216 @Override 217 public void onQueueTitleChanged(CharSequence title) throws RemoteException { 218 System.out.println("onQueueTitleChange " + title); 219 } 220 221 @Override 222 public void onExtrasChanged(Bundle extras) throws RemoteException { 223 System.out.println("onExtrasChanged " + extras); 224 } 225 226 @Override 227 public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException { 228 System.out.println("onVolumeInfoChanged " + info); 229 } 230 231 void printUsageMessage() { 232 try { 233 System.out.println("V2Monitoring session " + mController.getTag() 234 + "... available commands: play, pause, next, previous"); 235 } catch (RemoteException e) { 236 System.out.println("Error trying to monitor session!"); 237 } 238 System.out.println("(q)uit: finish monitoring"); 239 } 240 241 void run() throws RemoteException { 242 printUsageMessage(); 243 HandlerThread cbThread = new HandlerThread("MediaCb") { 244 @Override 245 protected void onLooperPrepared() { 246 try { 247 mController.registerCallbackListener(PACKAGE_NAME, ControllerMonitor.this); 248 } catch (RemoteException e) { 249 System.out.println("Error registering monitor callback"); 250 } 251 } 252 }; 253 cbThread.start(); 254 255 try { 256 InputStreamReader converter = new InputStreamReader(System.in); 257 BufferedReader in = new BufferedReader(converter); 258 String line; 259 260 while ((line = in.readLine()) != null) { 261 boolean addNewline = true; 262 if (line.length() <= 0) { 263 addNewline = false; 264 } else if ("q".equals(line) || "quit".equals(line)) { 265 break; 266 } else if ("play".equals(line)) { 267 dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PLAY); 268 } else if ("pause".equals(line)) { 269 dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE); 270 } else if ("next".equals(line)) { 271 dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_NEXT); 272 } else if ("previous".equals(line)) { 273 dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PREVIOUS); 274 } else { 275 System.out.println("Invalid command: " + line); 276 } 277 278 synchronized (this) { 279 if (addNewline) { 280 System.out.println(""); 281 } 282 printUsageMessage(); 283 } 284 } 285 } catch (IOException e) { 286 e.printStackTrace(); 287 } finally { 288 cbThread.getLooper().quit(); 289 try { 290 mController.unregisterCallbackListener(this); 291 } catch (Exception e) { 292 // ignoring 293 } 294 } 295 } 296 297 private void dispatchKeyCode(int keyCode) { 298 final long now = SystemClock.uptimeMillis(); 299 KeyEvent down = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0, 300 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD); 301 KeyEvent up = new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0, 302 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD); 303 try { 304 mController.sendMediaButton(PACKAGE_NAME, null, false, down); 305 mController.sendMediaButton(PACKAGE_NAME, null, false, up); 306 } catch (RemoteException e) { 307 System.out.println("Failed to dispatch " + keyCode); 308 } 309 } 310 } 311 312 private void runListSessions() { 313 System.out.println("Sessions:"); 314 try { 315 List<IBinder> sessions = mSessionService 316 .getSessions(null, ActivityManager.getCurrentUser()); 317 for (IBinder session : sessions) { 318 319 ISessionController controller = ISessionController.Stub.asInterface(session); 320 if (controller != null) { 321 try { 322 System.out.println(" tag=" + controller.getTag() 323 + ", package=" + controller.getPackageName()); 324 } catch (RemoteException e) { 325 // ignore 326 } 327 } 328 } 329 } catch (Exception e) { 330 System.out.println("***Error listing sessions***"); 331 } 332 } 333 334 //================================= 335 // "volume" command for stream volume control 336 private void runVolume() throws Exception { 337 VolumeCtrl.run(this); 338 } 339 } 340