blob: 1ff925c2f578be505d9cdbea4430d565d424f298 [file] [log] [blame]
/*
* Copyright (C) 2014 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.content.Intent;
import android.media.session.IMediaController;
import android.media.session.IMediaControllerCallback;
import android.media.session.IMediaSession;
import android.media.session.IMediaSessionCallback;
import android.media.session.MediaMetadata;
import android.media.session.PlaybackState;
import android.media.Rating;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Log;
import android.util.Slog;
import android.view.KeyEvent;
import java.util.ArrayList;
import java.util.List;
/**
* This is the system implementation of a Session. Apps will interact with the
* MediaSession wrapper class instead.
*/
public class MediaSessionRecord implements IBinder.DeathRecipient {
private static final String TAG = "MediaSessionImpl";
private final MessageHandler mHandler;
private final int mPid;
private final String mPackageName;
private final String mTag;
private final ControllerStub mController;
private final SessionStub mSession;
private final SessionCb mSessionCb;
private final MediaSessionService mService;
private final Object mControllerLock = new Object();
private final ArrayList<IMediaControllerCallback> mControllerCallbacks =
new ArrayList<IMediaControllerCallback>();
private final ArrayList<String> mInterfaces = new ArrayList<String>();
private boolean mTransportPerformerEnabled = false;
private Bundle mRoute;
// TransportPerformer fields
private MediaMetadata mMetadata;
private PlaybackState mPlaybackState;
private int mRatingType;
// End TransportPerformer fields
private boolean mIsPublished = false;
public MediaSessionRecord(int pid, String packageName, IMediaSessionCallback cb, String tag,
MediaSessionService service, Handler handler) {
mPid = pid;
mPackageName = packageName;
mTag = tag;
mController = new ControllerStub();
mSession = new SessionStub();
mSessionCb = new SessionCb(cb);
mService = service;
mHandler = new MessageHandler(handler.getLooper());
}
public IMediaSession getSessionBinder() {
return mSession;
}
public IMediaController getControllerBinder() {
return mController;
}
@Override
public void binderDied() {
mService.sessionDied(this);
}
public boolean isPublished() {
return mIsPublished;
}
private void onDestroy() {
mService.destroySession(this);
}
private void pushPlaybackStateUpdate() {
synchronized (mControllerLock) {
for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
IMediaControllerCallback cb = mControllerCallbacks.get(i);
try {
cb.onPlaybackStateChanged(mPlaybackState);
} catch (RemoteException e) {
Log.w(TAG, "Removing dead callback in pushPlaybackStateUpdate.", e);
mControllerCallbacks.remove(i);
}
}
}
}
private void pushMetadataUpdate() {
synchronized (mControllerLock) {
for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
IMediaControllerCallback cb = mControllerCallbacks.get(i);
try {
cb.onMetadataChanged(mMetadata);
} catch (RemoteException e) {
Log.w(TAG, "Removing dead callback in pushMetadataUpdate.", e);
mControllerCallbacks.remove(i);
}
}
}
}
private void pushRouteUpdate() {
synchronized (mControllerLock) {
for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
IMediaControllerCallback cb = mControllerCallbacks.get(i);
try {
cb.onRouteChanged(mRoute);
} catch (RemoteException e) {
Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e);
mControllerCallbacks.remove(i);
}
}
}
}
private void pushEvent(String event, Bundle data) {
synchronized (mControllerLock) {
for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
IMediaControllerCallback cb = mControllerCallbacks.get(i);
try {
cb.onEvent(event, data);
} catch (RemoteException e) {
Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e);
mControllerCallbacks.remove(i);
}
}
}
}
private final class SessionStub extends IMediaSession.Stub {
@Override
public void destroy() {
onDestroy();
}
@Override
public void sendEvent(String event, Bundle data) {
mHandler.post(MessageHandler.MSG_SEND_EVENT, event, data);
}
@Override
public IMediaController getMediaController() {
return mController;
}
@Override
public void setRouteState(Bundle routeState) {
}
@Override
public void setRoute(Bundle mediaRouteDescriptor) {
mRoute = mediaRouteDescriptor;
mHandler.post(MessageHandler.MSG_UPDATE_ROUTE);
}
@Override
public void publish() {
mIsPublished = true; // TODO push update to service
}
@Override
public void setTransportPerformerEnabled() {
mTransportPerformerEnabled = true;
}
@Override
public List<String> getSupportedInterfaces() {
return mInterfaces;
}
@Override
public void setMetadata(MediaMetadata metadata) {
mMetadata = metadata;
mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
}
@Override
public void setPlaybackState(PlaybackState state) {
mPlaybackState = state;
mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
}
@Override
public void setRatingType(int type) {
mRatingType = type;
}
}
class SessionCb {
private final IMediaSessionCallback mCb;
public SessionCb(IMediaSessionCallback cb) {
mCb = cb;
}
public void sendMediaButton(KeyEvent keyEvent) {
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
try {
mCb.onMediaButton(mediaButtonIntent);
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
}
}
public void sendCommand(String command, Bundle extras, ResultReceiver cb) {
try {
mCb.onCommand(command, extras, cb);
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in sendCommand.", e);
}
}
public void play() {
try {
mCb.onPlay();
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in play.", e);
}
}
public void pause() {
try {
mCb.onPause();
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in pause.", e);
}
}
public void stop() {
try {
mCb.onStop();
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in stop.", e);
}
}
public void next() {
try {
mCb.onNext();
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in next.", e);
}
}
public void previous() {
try {
mCb.onPrevious();
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in previous.", e);
}
}
public void fastForward() {
try {
mCb.onFastForward();
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in fastForward.", e);
}
}
public void rewind() {
try {
mCb.onRewind();
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in rewind.", e);
}
}
public void seekTo(long pos) {
try {
mCb.onSeekTo(pos);
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in seekTo.", e);
}
}
public void rate(Rating rating) {
try {
mCb.onRate(rating);
} catch (RemoteException e) {
Slog.e(TAG, "Remote failure in rate.", e);
}
}
}
class ControllerStub extends IMediaController.Stub {
@Override
public void sendCommand(String command, Bundle extras, ResultReceiver cb)
throws RemoteException {
mSessionCb.sendCommand(command, extras, cb);
}
@Override
public void sendMediaButton(KeyEvent mediaButtonIntent) {
mSessionCb.sendMediaButton(mediaButtonIntent);
}
@Override
public void registerCallbackListener(IMediaControllerCallback cb) {
synchronized (mControllerLock) {
if (!mControllerCallbacks.contains(cb)) {
mControllerCallbacks.add(cb);
}
}
}
@Override
public void unregisterCallbackListener(IMediaControllerCallback cb)
throws RemoteException {
synchronized (mControllerLock) {
mControllerCallbacks.remove(cb);
}
}
@Override
public void play() throws RemoteException {
mSessionCb.play();
}
@Override
public void pause() throws RemoteException {
mSessionCb.pause();
}
@Override
public void stop() throws RemoteException {
mSessionCb.stop();
}
@Override
public void next() throws RemoteException {
mSessionCb.next();
}
@Override
public void previous() throws RemoteException {
mSessionCb.previous();
}
@Override
public void fastForward() throws RemoteException {
mSessionCb.fastForward();
}
@Override
public void rewind() throws RemoteException {
mSessionCb.rewind();
}
@Override
public void seekTo(long pos) throws RemoteException {
mSessionCb.seekTo(pos);
}
@Override
public void rate(Rating rating) throws RemoteException {
mSessionCb.rate(rating);
}
@Override
public MediaMetadata getMetadata() {
return mMetadata;
}
@Override
public PlaybackState getPlaybackState() {
return mPlaybackState;
}
@Override
public int getRatingType() {
return mRatingType;
}
@Override
public boolean isTransportControlEnabled() throws RemoteException {
return mTransportPerformerEnabled;
}
}
private class MessageHandler extends Handler {
private static final int MSG_UPDATE_METADATA = 1;
private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
private static final int MSG_UPDATE_ROUTE = 3;
private static final int MSG_SEND_EVENT = 4;
public MessageHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_METADATA:
pushMetadataUpdate();
break;
case MSG_UPDATE_PLAYBACK_STATE:
pushPlaybackStateUpdate();
break;
case MSG_UPDATE_ROUTE:
pushRouteUpdate();
break;
case MSG_SEND_EVENT:
pushEvent((String) msg.obj, msg.getData());
break;
}
}
public void post(int what) {
post(what, null);
}
public void post(int what, Object obj) {
obtainMessage(what, obj).sendToTarget();
}
public void post(int what, Object obj, Bundle data) {
Message msg = obtainMessage(what, obj);
msg.setData(data);
msg.sendToTarget();
}
}
}