blob: 20df271a1de2a88524fe39c7ecc3de4899ffb0b3 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.media;
import android.app.ActivityThread;
import android.content.Context;
import android.media.MediaMetadata;
import android.media.session.ISessionManager;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.text.TextUtils;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.List;
/**
* ShellCommand for MediaSessionService.
*/
public class MediaShellCommand extends ShellCommand {
// This doesn't belongs to any package. Setting the package name to empty string.
private static final String PACKAGE_NAME = "";
private static ActivityThread sThread;
private static MediaSessionManager sMediaSessionManager;
private ISessionManager mSessionService;
private PrintWriter mWriter;
private PrintWriter mErrorWriter;
@Override
public int onCommand(String cmd) {
mWriter = getOutPrintWriter();
mErrorWriter = getErrPrintWriter();
if (TextUtils.isEmpty(cmd)) {
return handleDefaultCommands(cmd);
}
if (sThread == null) {
Looper.prepare();
sThread = ActivityThread.systemMain();
Context context = sThread.getSystemContext();
sMediaSessionManager =
(MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
}
mSessionService = ISessionManager.Stub.asInterface(ServiceManager.checkService(
Context.MEDIA_SESSION_SERVICE));
if (mSessionService == null) {
throw new IllegalStateException(
"Can't connect to media session service; is the system running?");
}
try {
if (cmd.equals("dispatch")) {
runDispatch();
} else if (cmd.equals("list-sessions")) {
runListSessions();
} else if (cmd.equals("monitor")) {
runMonitor();
} else if (cmd.equals("volume")) {
runVolume();
} else {
showError("Error: unknown command '" + cmd + "'");
return -1;
}
} catch (Exception e) {
showError(e.toString());
return -1;
}
return 0;
}
@Override
public void onHelp() {
mWriter.println("usage: media_session [subcommand] [options]");
mWriter.println(" media_session dispatch KEY");
mWriter.println(" media_session dispatch KEY");
mWriter.println(" media_session list-sessions");
mWriter.println(" media_session monitor <tag>");
mWriter.println(" media_session volume [options]");
mWriter.println();
mWriter.println("media_session dispatch: dispatch a media key to the system.");
mWriter.println(" KEY may be: play, pause, play-pause, mute, headsethook,");
mWriter.println(" stop, next, previous, rewind, record, fast-forword.");
mWriter.println("media_session list-sessions: print a list of the current sessions.");
mWriter.println("media_session monitor: monitor updates to the specified session.");
mWriter.println(" Use the tag from list-sessions.");
mWriter.println("media_session volume: " + VolumeCtrl.USAGE);
mWriter.println();
}
private void sendMediaKey(KeyEvent event) {
try {
mSessionService.dispatchMediaKeyEvent(PACKAGE_NAME, false, event, false);
} catch (RemoteException e) {
}
}
private void runMonitor() throws Exception {
String id = getNextArgRequired();
if (id == null) {
showError("Error: must include a session id");
return;
}
boolean success = false;
try {
List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null);
for (MediaController controller : controllers) {
try {
if (controller != null && id.equals(controller.getTag())) {
MediaShellCommand.ControllerMonitor monitor =
new MediaShellCommand.ControllerMonitor(controller);
monitor.run();
success = true;
break;
}
} catch (RemoteException e) {
// ignore
}
}
} catch (Exception e) {
mErrorWriter.println("***Error monitoring session*** " + e.getMessage());
}
if (!success) {
mErrorWriter.println("No session found with id " + id);
}
}
private void runDispatch() throws Exception {
String cmd = getNextArgRequired();
int keycode;
if ("play".equals(cmd)) {
keycode = KeyEvent.KEYCODE_MEDIA_PLAY;
} else if ("pause".equals(cmd)) {
keycode = KeyEvent.KEYCODE_MEDIA_PAUSE;
} else if ("play-pause".equals(cmd)) {
keycode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
} else if ("mute".equals(cmd)) {
keycode = KeyEvent.KEYCODE_MUTE;
} else if ("headsethook".equals(cmd)) {
keycode = KeyEvent.KEYCODE_HEADSETHOOK;
} else if ("stop".equals(cmd)) {
keycode = KeyEvent.KEYCODE_MEDIA_STOP;
} else if ("next".equals(cmd)) {
keycode = KeyEvent.KEYCODE_MEDIA_NEXT;
} else if ("previous".equals(cmd)) {
keycode = KeyEvent.KEYCODE_MEDIA_PREVIOUS;
} else if ("rewind".equals(cmd)) {
keycode = KeyEvent.KEYCODE_MEDIA_REWIND;
} else if ("record".equals(cmd)) {
keycode = KeyEvent.KEYCODE_MEDIA_RECORD;
} else if ("fast-forward".equals(cmd)) {
keycode = KeyEvent.KEYCODE_MEDIA_FAST_FORWARD;
} else {
showError("Error: unknown dispatch code '" + cmd + "'");
return;
}
final long now = SystemClock.uptimeMillis();
sendMediaKey(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keycode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
sendMediaKey(new KeyEvent(now, now, KeyEvent.ACTION_UP, keycode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
}
void showError(String errMsg) {
onHelp();
mErrorWriter.println(errMsg);
}
class ControllerCallback extends MediaController.Callback {
@Override
public void onSessionDestroyed() {
mWriter.println("onSessionDestroyed. Enter q to quit.");
}
@Override
public void onSessionEvent(String event, Bundle extras) {
mWriter.println("onSessionEvent event=" + event + ", extras=" + extras);
}
@Override
public void onPlaybackStateChanged(PlaybackState state) {
mWriter.println("onPlaybackStateChanged " + state);
}
@Override
public void onMetadataChanged(MediaMetadata metadata) {
String mmString = metadata == null ? null : "title=" + metadata
.getDescription();
mWriter.println("onMetadataChanged " + mmString);
}
@Override
public void onQueueChanged(List<MediaSession.QueueItem> queue) {
mWriter.println("onQueueChanged, "
+ (queue == null ? "null queue" : " size=" + queue.size()));
}
@Override
public void onQueueTitleChanged(CharSequence title) {
mWriter.println("onQueueTitleChange " + title);
}
@Override
public void onExtrasChanged(Bundle extras) {
mWriter.println("onExtrasChanged " + extras);
}
@Override
public void onAudioInfoChanged(MediaController.PlaybackInfo info) {
mWriter.println("onAudioInfoChanged " + info);
}
}
private class ControllerMonitor {
private final MediaController mController;
private final MediaShellCommand.ControllerCallback mControllerCallback;
ControllerMonitor(MediaController controller) {
mController = controller;
mControllerCallback = new MediaShellCommand.ControllerCallback();
}
void printUsageMessage() {
try {
mWriter.println("V2Monitoring session " + mController.getTag()
+ "... available commands: play, pause, next, previous");
} catch (RuntimeException e) {
mWriter.println("Error trying to monitor session!");
}
mWriter.println("(q)uit: finish monitoring");
}
void run() throws RemoteException {
printUsageMessage();
HandlerThread cbThread = new HandlerThread("MediaCb") {
@Override
protected void onLooperPrepared() {
try {
mController.registerCallback(mControllerCallback);
} catch (RuntimeException e) {
mErrorWriter.println("Error registering monitor callback");
}
}
};
cbThread.start();
try {
InputStreamReader converter = new InputStreamReader(System.in);
BufferedReader in = new BufferedReader(converter);
String line;
while ((line = in.readLine()) != null) {
boolean addNewline = true;
if (line.length() <= 0) {
addNewline = false;
} else if ("q".equals(line) || "quit".equals(line)) {
break;
} else if ("play".equals(line)) {
dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PLAY);
} else if ("pause".equals(line)) {
dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PAUSE);
} else if ("next".equals(line)) {
dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_NEXT);
} else if ("previous".equals(line)) {
dispatchKeyCode(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
} else {
mErrorWriter.println("Invalid command: " + line);
}
synchronized (this) {
if (addNewline) {
System.out.println("");
}
printUsageMessage();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
cbThread.getLooper().quit();
try {
mController.unregisterCallback(mControllerCallback);
} catch (Exception e) {
// ignoring
}
}
}
private void dispatchKeyCode(int keyCode) {
final long now = SystemClock.uptimeMillis();
KeyEvent down = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
KeyEvent up = new KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
try {
mController.dispatchMediaButtonEvent(down);
mController.dispatchMediaButtonEvent(up);
} catch (RuntimeException e) {
mErrorWriter.println("Failed to dispatch " + keyCode);
}
}
}
private void runListSessions() {
mWriter.println("Sessions:");
try {
List<MediaController> controllers = sMediaSessionManager.getActiveSessions(null);
for (MediaController controller : controllers) {
if (controller != null) {
try {
mWriter.println(" tag=" + controller.getTag()
+ ", package=" + controller.getPackageName());
} catch (RuntimeException e) {
// ignore
}
}
}
} catch (Exception e) {
mErrorWriter.println("***Error listing sessions***");
}
}
//=================================
// "volume" command for stream volume control
private void runVolume() throws Exception {
VolumeCtrl.run(this);
}
}