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