/*
* 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.audio;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.media.AudioManager;
import android.media.IRemoteControlClient;
import android.media.IRemoteVolumeObserver;
import android.media.RemoteControlClient;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.util.Log;
import java.io.PrintWriter;
/**
* @hide
* Class to handle all the information about a media player, encapsulating information
* about its use RemoteControlClient, playback type and volume... The lifecycle of each
* instance is managed by android.media.MediaFocusControl, from its addition to the player stack
* stack to its release.
*/
class PlayerRecord implements DeathRecipient {
// on purpose not using this classe's name, as it will only be used from MediaFocusControl
private static final String TAG = "MediaFocusControl";
private static final boolean DEBUG = false;
/**
* A global counter for RemoteControlClient identifiers
*/
private static int sLastRccId = 0;
public static MediaFocusControl sController;
/**
* The target for the ACTION_MEDIA_BUTTON events.
* Always non null. //FIXME verify
*/
final private PendingIntent mMediaIntent;
/**
* The registered media button event receiver.
*/
final private ComponentName mReceiverComponent;
private int mRccId = -1;
/**
* A non-null token implies this record tracks a "live" player whose death is being monitored.
*/
private IBinder mToken;
private String mCallingPackageName;
private int mCallingUid;
/**
* Provides access to the information to display on the remote control.
* May be null (when a media button event receiver is registered,
* but no remote control client has been registered) */
private IRemoteControlClient mRcClient;
private RcClientDeathHandler mRcClientDeathHandler;
/**
* Information only used for non-local playback
*/
//FIXME private?
public int mPlaybackType;
public int mPlaybackVolume;
public int mPlaybackVolumeMax;
public int mPlaybackVolumeHandling;
public int mPlaybackStream;
public RccPlaybackState mPlaybackState;
public IRemoteVolumeObserver mRemoteVolumeObs;
protected static class RccPlaybackState {
public int mState;
public long mPositionMs;
public float mSpeed;
public RccPlaybackState(int state, long positionMs, float speed) {
mState = state;
mPositionMs = positionMs;
mSpeed = speed;
}
public void reset() {
mState = RemoteControlClient.PLAYSTATE_STOPPED;
mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID;
mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X;
}
@Override
public String toString() {
return stateToString() + ", " + posToString() + ", " + mSpeed + "X";
}
private String posToString() {
if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) {
return "PLAYBACK_POSITION_INVALID";
} else if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
return "PLAYBACK_POSITION_ALWAYS_UNKNOWN";
} else {
return (String.valueOf(mPositionMs) + "ms");
}
}
private String stateToString() {
switch (mState) {
case RemoteControlClient.PLAYSTATE_NONE:
return "PLAYSTATE_NONE";
case RemoteControlClient.PLAYSTATE_STOPPED:
return "PLAYSTATE_STOPPED";
case RemoteControlClient.PLAYSTATE_PAUSED:
return "PLAYSTATE_PAUSED";
case RemoteControlClient.PLAYSTATE_PLAYING:
return "PLAYSTATE_PLAYING";
case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
return "PLAYSTATE_FAST_FORWARDING";
case RemoteControlClient.PLAYSTATE_REWINDING:
return "PLAYSTATE_REWINDING";
case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
return "PLAYSTATE_SKIPPING_FORWARDS";
case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
return "PLAYSTATE_SKIPPING_BACKWARDS";
case RemoteControlClient.PLAYSTATE_BUFFERING:
return "PLAYSTATE_BUFFERING";
case RemoteControlClient.PLAYSTATE_ERROR:
return "PLAYSTATE_ERROR";
default:
return "[invalid playstate]";
}
}
}
/**
* Inner class to monitor remote control client deaths, and remove the client for the
* remote control stack if necessary.
*/
private class RcClientDeathHandler implements IBinder.DeathRecipient {
final private IBinder mCb; // To be notified of client's death
//FIXME needed?
final private PendingIntent mMediaIntent;
RcClientDeathHandler(IBinder cb, PendingIntent pi) {
mCb = cb;
mMediaIntent = pi;
}
public void binderDied() {
Log.w(TAG, " RemoteControlClient died");
// remote control client died, make sure the displays don't use it anymore
// by setting its remote control client to null
sController.registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/);
// the dead client was maybe handling remote playback, the controller should reevaluate
sController.postReevaluateRemote();
}
public IBinder getBinder() {
return mCb;
}
}
protected static class RemotePlaybackState {
int mRccId;
int mVolume;
int mVolumeMax;
int mVolumeHandling;
protected RemotePlaybackState(int id, int vol, int volMax) {
mRccId = id;
mVolume = vol;
mVolumeMax = volMax;
mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
}
}
void dump(PrintWriter pw, boolean registrationInfo) {
if (registrationInfo) {
pw.println(" pi: " + mMediaIntent +
" -- pack: " + mCallingPackageName +
" -- ercvr: " + mReceiverComponent +
" -- client: " + mRcClient +
" -- uid: " + mCallingUid +
" -- type: " + mPlaybackType +
" state: " + mPlaybackState);
} else {
// emphasis on state
pw.println(" uid: " + mCallingUid +
" -- id: " + mRccId +
" -- type: " + mPlaybackType +
" -- state: " + mPlaybackState +
" -- vol handling: " + mPlaybackVolumeHandling +
" -- vol: " + mPlaybackVolume +
" -- volMax: " + mPlaybackVolumeMax +
" -- volObs: " + mRemoteVolumeObs);
}
}
static protected void setMediaFocusControl(MediaFocusControl mfc) {
sController = mfc;
}
/** precondition: mediaIntent != null */
protected PlayerRecord(PendingIntent mediaIntent, ComponentName eventReceiver, IBinder token)
{
mMediaIntent = mediaIntent;
mReceiverComponent = eventReceiver;
mToken = token;
mCallingUid = -1;
mRcClient = null;
mRccId = ++sLastRccId;
mPlaybackState = new RccPlaybackState(
RemoteControlClient.PLAYSTATE_STOPPED,
RemoteControlClient.PLAYBACK_POSITION_INVALID,
RemoteControlClient.PLAYBACK_SPEED_1X);
resetPlaybackInfo();
if (mToken != null) {
try {
mToken.linkToDeath(this, 0);
} catch (RemoteException e) {
sController.unregisterMediaButtonIntentAsync(mMediaIntent);
}
}
}
//---------------------------------------------
// Accessors
protected int getRccId() {
return mRccId;
}
protected IRemoteControlClient getRcc() {
return mRcClient;
}
protected ComponentName getMediaButtonReceiver() {
return mReceiverComponent;
}
protected PendingIntent getMediaButtonIntent() {
return mMediaIntent;
}
protected boolean hasMatchingMediaButtonIntent(PendingIntent pi) {
if (mToken != null) {
return mMediaIntent.equals(pi);
} else {
if (mReceiverComponent != null) {
return mReceiverComponent.equals(pi.getIntent().getComponent());
} else {
return false;
}
}
}
protected boolean isPlaybackActive() {
return MediaFocusControl.isPlaystateActive(mPlaybackState.mState);
}
//---------------------------------------------
// Modify the records stored in the instance
protected void resetControllerInfoForRcc(IRemoteControlClient rcClient,
String callingPackageName, int uid) {
// already had a remote control client?
if (mRcClientDeathHandler != null) {
// stop monitoring the old client's death
unlinkToRcClientDeath();
}
// save the new remote control client
mRcClient = rcClient;
mCallingPackageName = callingPackageName;
mCallingUid = uid;
if (rcClient == null) {
// here mcse.mRcClientDeathHandler is null;
resetPlaybackInfo();
} else {
IBinder b = mRcClient.asBinder();
RcClientDeathHandler rcdh =
new RcClientDeathHandler(b, mMediaIntent);
try {
b.linkToDeath(rcdh, 0);
} catch (RemoteException e) {
// remote control client is DOA, disqualify it
Log.w(TAG, "registerRemoteControlClient() has a dead client " + b);
mRcClient = null;
}
mRcClientDeathHandler = rcdh;
}
}
protected void resetControllerInfoForNoRcc() {
// stop monitoring the RCC death
unlinkToRcClientDeath();
// reset the RCC-related fields
mRcClient = null;
mCallingPackageName = null;
}
public void resetPlaybackInfo() {
mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL;
mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
mPlaybackStream = AudioManager.STREAM_MUSIC;
mPlaybackState.reset();
mRemoteVolumeObs = null;
}
//---------------------------------------------
public void unlinkToRcClientDeath() {
if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) {
try {
mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0);
mRcClientDeathHandler = null;
} catch (java.util.NoSuchElementException e) {
// not much we can do here
Log.e(TAG, "Error in unlinkToRcClientDeath()", e);
}
}
}
// FIXME rename to "release"? (as in FocusRequester class)
public void destroy() {
unlinkToRcClientDeath();
if (mToken != null) {
mToken.unlinkToDeath(this, 0);
mToken = null;
}
}
@Override
public void binderDied() {
sController.unregisterMediaButtonIntentAsync(mMediaIntent);
}
@Override
protected void finalize() throws Throwable {
destroy(); // unlink exception handled inside method
super.finalize();
}
}