Home | History | Annotate | Download | only in media
      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