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