blob: c2e818772c692b43f353d98e1dba3acf39285634 [file] [log] [blame]
/*
* Copyright (C) 2012 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.bluetooth.a2dp;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.IRemoteControlDisplay;
import android.media.MediaMetadataRetriever;
import android.media.RemoteControlClient;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.Log;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.Utils;
import com.android.internal.util.IState;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* support Bluetooth AVRCP profile.
* support metadata, play status and event notification
*/
final class Avrcp {
private static final boolean DEBUG = false;
private static final String TAG = "Avrcp";
private Context mContext;
private final AudioManager mAudioManager;
private AvrcpMessageHandler mHandler;
private IRemoteControlDisplayWeak mRemoteControlDisplay;
private int mClientGeneration;
private Metadata mMetadata;
private int mTransportControlFlags;
private int mCurrentPlayState;
private int mPlayStatusChangedNT;
private int mTrackChangedNT;
private long mTrackNumber;
private long mCurrentPosMs;
private long mPlayStartTimeMs;
private long mSongLengthMs;
private long mPlaybackIntervalMs;
private int mPlayPosChangedNT;
private long mNextPosMs;
private long mPrevPosMs;
private static final int MESSAGE_GET_PLAY_STATUS = 1;
private static final int MESSAGE_GET_ELEM_ATTRS = 2;
private static final int MESSAGE_REGISTER_NOTIFICATION = 3;
private static final int MESSAGE_PLAY_INTERVAL_TIMEOUT = 4;
private static final int MSG_UPDATE_STATE = 100;
private static final int MSG_SET_METADATA = 101;
private static final int MSG_SET_TRANSPORT_CONTROLS = 102;
private static final int MSG_SET_ARTWORK = 103;
private static final int MSG_SET_GENERATION_ID = 104;
static {
classInitNative();
}
private Avrcp(Context context) {
mMetadata = new Metadata();
mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback
mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED;
mTrackChangedNT = NOTIFICATION_TYPE_CHANGED;
mTrackNumber = -1L;
mCurrentPosMs = RemoteControlClient.PLAYBACK_POSITION_INVALID;
mPlayStartTimeMs = -1L;
mSongLengthMs = 0L;
mPlaybackIntervalMs = 0L;
mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED;
mContext = context;
initNative();
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
private void start() {
HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
thread.start();
Looper looper = thread.getLooper();
mHandler = new AvrcpMessageHandler(looper);
mRemoteControlDisplay = new IRemoteControlDisplayWeak(mHandler);
mAudioManager.registerRemoteControlDisplay(mRemoteControlDisplay);
mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(
mRemoteControlDisplay, true);
}
static Avrcp make(Context context) {
if (DEBUG) Log.v(TAG, "make");
Avrcp ar = new Avrcp(context);
ar.start();
return ar;
}
public void doQuit() {
mHandler.removeCallbacksAndMessages(null);
Looper looper = mHandler.getLooper();
if (looper != null) {
looper.quit();
}
mAudioManager.unregisterRemoteControlDisplay(mRemoteControlDisplay);
}
public void cleanup() {
cleanupNative();
}
private static class IRemoteControlDisplayWeak extends IRemoteControlDisplay.Stub {
private WeakReference<Handler> mLocalHandler;
IRemoteControlDisplayWeak(Handler handler) {
mLocalHandler = new WeakReference<Handler>(handler);
}
@Override
public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
long currentPosMs, float speed) {
Handler handler = mLocalHandler.get();
if (handler != null) {
handler.obtainMessage(MSG_UPDATE_STATE, generationId, state,
new Long(currentPosMs)).sendToTarget();
}
}
@Override
public void setMetadata(int generationId, Bundle metadata) {
Handler handler = mLocalHandler.get();
if (handler != null) {
handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget();
}
}
@Override
public void setTransportControlInfo(int generationId, int flags, int posCapabilities) {
Handler handler = mLocalHandler.get();
if (handler != null) {
handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags)
.sendToTarget();
}
}
@Override
public void setArtwork(int generationId, Bitmap bitmap) {
}
@Override
public void setAllMetadata(int generationId, Bundle metadata, Bitmap bitmap) {
Handler handler = mLocalHandler.get();
if (handler != null) {
handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget();
handler.obtainMessage(MSG_SET_ARTWORK, generationId, 0, bitmap).sendToTarget();
}
}
@Override
public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent,
boolean clearing) throws RemoteException {
Handler handler = mLocalHandler.get();
if (handler != null) {
handler.obtainMessage(MSG_SET_GENERATION_ID,
clientGeneration, (clearing ? 1 : 0), mediaIntent).sendToTarget();
}
}
}
/** Handles Avrcp messages. */
private final class AvrcpMessageHandler extends Handler {
private AvrcpMessageHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_STATE:
if (mClientGeneration == msg.arg1) {
updatePlayPauseState(msg.arg2, ((Long)msg.obj).longValue());
}
break;
case MSG_SET_METADATA:
if (mClientGeneration == msg.arg1) updateMetadata((Bundle) msg.obj);
break;
case MSG_SET_TRANSPORT_CONTROLS:
if (mClientGeneration == msg.arg1) updateTransportControls(msg.arg2);
break;
case MSG_SET_ARTWORK:
if (mClientGeneration == msg.arg1) {
}
break;
case MSG_SET_GENERATION_ID:
if (DEBUG) Log.v(TAG, "New genId = " + msg.arg1 + ", clearing = " + msg.arg2);
mClientGeneration = msg.arg1;
break;
case MESSAGE_GET_PLAY_STATUS:
if (DEBUG) Log.v(TAG, "MESSAGE_GET_PLAY_STATUS");
getPlayStatusRspNative(convertPlayStateToPlayStatus(mCurrentPlayState),
(int)mSongLengthMs, (int)getPlayPosition());
break;
case MESSAGE_GET_ELEM_ATTRS:
{
String[] textArray;
int[] attrIds;
byte numAttr = (byte) msg.arg1;
ArrayList<Integer> attrList = (ArrayList<Integer>) msg.obj;
if (DEBUG) Log.v(TAG, "MESSAGE_GET_ELEM_ATTRS:numAttr=" + numAttr);
attrIds = new int[numAttr];
textArray = new String[numAttr];
for (int i = 0; i < numAttr; ++i) {
attrIds[i] = attrList.get(i).intValue();
textArray[i] = getAttributeString(attrIds[i]);
}
getElementAttrRspNative(numAttr, attrIds, textArray);
break;
}
case MESSAGE_REGISTER_NOTIFICATION:
if (DEBUG) Log.v(TAG, "MESSAGE_REGISTER_NOTIFICATION:event=" + msg.arg1 +
" param=" + msg.arg2);
processRegisterNotification(msg.arg1, msg.arg2);
break;
case MESSAGE_PLAY_INTERVAL_TIMEOUT:
if (DEBUG) Log.v(TAG, "MESSAGE_PLAY_INTERVAL_TIMEOUT");
mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED;
registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int)getPlayPosition());
break;
}
}
}
private void updatePlayPauseState(int state, long currentPosMs) {
if (DEBUG) Log.v(TAG,
"updatePlayPauseState, old=" + mCurrentPlayState + ", state=" + state);
boolean oldPosValid = (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_INVALID);
boolean newPosValid = (currentPosMs != RemoteControlClient.PLAYBACK_POSITION_INVALID);
int oldPlayStatus = convertPlayStateToPlayStatus(mCurrentPlayState);
int newPlayStatus = convertPlayStateToPlayStatus(state);
mCurrentPlayState = state;
mCurrentPosMs = currentPosMs;
if (state == RemoteControlClient.PLAYSTATE_PLAYING) {
mPlayStartTimeMs = SystemClock.elapsedRealtime();
}
mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT);
/* need send play position changed notification when play status is changed */
if ((mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) &&
((oldPlayStatus != newPlayStatus) || (oldPosValid != newPosValid) ||
(newPosValid && ((mCurrentPosMs >= mNextPosMs) || (mCurrentPosMs <= mPrevPosMs))))) {
mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED;
registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int)getPlayPosition());
}
if ((mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) && newPosValid &&
(state == RemoteControlClient.PLAYSTATE_PLAYING)) {
Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT);
mHandler.sendMessageDelayed(msg, mNextPosMs - mCurrentPosMs);
}
if ((mPlayStatusChangedNT == NOTIFICATION_TYPE_INTERIM) && (oldPlayStatus != newPlayStatus)) {
mPlayStatusChangedNT = NOTIFICATION_TYPE_CHANGED;
registerNotificationRspPlayStatusNative(mPlayStatusChangedNT, newPlayStatus);
}
}
private void updateTransportControls(int transportControlFlags) {
mTransportControlFlags = transportControlFlags;
}
class Metadata {
private String artist;
private String trackTitle;
private String albumTitle;
public Metadata() {
artist = null;
trackTitle = null;
albumTitle = null;
}
public String toString() {
return "Metadata[artist=" + artist + " trackTitle=" + trackTitle + " albumTitle=" +
albumTitle + "]";
}
}
private String getMdString(Bundle data, int id) {
return data.getString(Integer.toString(id));
}
private long getMdLong(Bundle data, int id) {
return data.getLong(Integer.toString(id));
}
private void updateMetadata(Bundle data) {
String oldMetadata = mMetadata.toString();
mMetadata.artist = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST);
mMetadata.trackTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_TITLE);
mMetadata.albumTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUM);
if (!oldMetadata.equals(mMetadata.toString())) {
mTrackNumber++;
if (mTrackChangedNT == NOTIFICATION_TYPE_INTERIM) {
mTrackChangedNT = NOTIFICATION_TYPE_CHANGED;
sendTrackChangedRsp();
}
if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_INVALID) {
mCurrentPosMs = 0L;
if (mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) {
mPlayStartTimeMs = SystemClock.elapsedRealtime();
}
}
/* need send play position changed notification when track is changed */
if (mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) {
mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED;
registerNotificationRspPlayPosNative(mPlayPosChangedNT,
(int)getPlayPosition());
mHandler.removeMessages(MESSAGE_PLAY_INTERVAL_TIMEOUT);
}
}
if (DEBUG) Log.v(TAG, "mMetadata=" + mMetadata.toString());
mSongLengthMs = getMdLong(data, MediaMetadataRetriever.METADATA_KEY_DURATION);
if (DEBUG) Log.v(TAG, "duration=" + mSongLengthMs);
}
private void getPlayStatus() {
Message msg = mHandler.obtainMessage(MESSAGE_GET_PLAY_STATUS);
mHandler.sendMessage(msg);
}
private void getElementAttr(byte numAttr, int[] attrs) {
int i;
ArrayList<Integer> attrList = new ArrayList<Integer>();
for (i = 0; i < numAttr; ++i) {
attrList.add(attrs[i]);
}
Message msg = mHandler.obtainMessage(MESSAGE_GET_ELEM_ATTRS, (int)numAttr, 0, attrList);
mHandler.sendMessage(msg);
}
private void registerNotification(int eventId, int param) {
Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_NOTIFICATION, eventId, param);
mHandler.sendMessage(msg);
}
private void processRegisterNotification(int eventId, int param) {
switch (eventId) {
case EVT_PLAY_STATUS_CHANGED:
mPlayStatusChangedNT = NOTIFICATION_TYPE_INTERIM;
registerNotificationRspPlayStatusNative(mPlayStatusChangedNT,
convertPlayStateToPlayStatus(mCurrentPlayState));
break;
case EVT_TRACK_CHANGED:
mTrackChangedNT = NOTIFICATION_TYPE_INTERIM;
sendTrackChangedRsp();
break;
case EVT_PLAY_POS_CHANGED:
long songPosition = getPlayPosition();
mPlayPosChangedNT = NOTIFICATION_TYPE_INTERIM;
mPlaybackIntervalMs = (long)param * 1000L;
if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_INVALID) {
mNextPosMs = songPosition + mPlaybackIntervalMs;
mPrevPosMs = songPosition - mPlaybackIntervalMs;
if (mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) {
Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT);
mHandler.sendMessageDelayed(msg, mPlaybackIntervalMs);
}
}
registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int)songPosition);
break;
}
}
private void sendTrackChangedRsp() {
byte[] track = new byte[TRACK_ID_SIZE];
/* track is stored in big endian format */
for (int i = 0; i < TRACK_ID_SIZE; ++i) {
track[i] = (byte) (mTrackNumber >> (56 - 8 * i));
}
registerNotificationRspTrackChangeNative(mTrackChangedNT, track);
}
private long getPlayPosition() {
long songPosition = -1L;
if (mCurrentPosMs != RemoteControlClient.PLAYBACK_POSITION_INVALID) {
if (mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) {
songPosition = SystemClock.elapsedRealtime() -
mPlayStartTimeMs + mCurrentPosMs;
} else {
songPosition = mCurrentPosMs;
}
}
if (DEBUG) Log.v(TAG, "position=" + songPosition);
return songPosition;
}
private String getAttributeString(int attrId) {
String attrStr = null;
switch (attrId) {
case MEDIA_ATTR_TITLE:
attrStr = mMetadata.trackTitle;
break;
case MEDIA_ATTR_ARTIST:
attrStr = mMetadata.artist;
break;
case MEDIA_ATTR_ALBUM:
attrStr = mMetadata.albumTitle;
break;
case MEDIA_ATTR_PLAYING_TIME:
if (mSongLengthMs != 0L) {
attrStr = Long.toString(mSongLengthMs);
}
break;
}
if (attrStr == null) {
attrStr = new String();
}
if (DEBUG) Log.v(TAG, "getAttributeString:attrId=" + attrId + " str=" + attrStr);
return attrStr;
}
private int convertPlayStateToPlayStatus(int playState) {
int playStatus = PLAYSTATUS_ERROR;
switch (playState) {
case RemoteControlClient.PLAYSTATE_PLAYING:
case RemoteControlClient.PLAYSTATE_BUFFERING:
playStatus = PLAYSTATUS_PLAYING;
break;
case RemoteControlClient.PLAYSTATE_STOPPED:
case RemoteControlClient.PLAYSTATE_NONE:
playStatus = PLAYSTATUS_STOPPED;
break;
case RemoteControlClient.PLAYSTATE_PAUSED:
playStatus = PLAYSTATUS_PAUSED;
break;
case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
playStatus = PLAYSTATUS_FWD_SEEK;
break;
case RemoteControlClient.PLAYSTATE_REWINDING:
case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
playStatus = PLAYSTATUS_REV_SEEK;
break;
case RemoteControlClient.PLAYSTATE_ERROR:
playStatus = PLAYSTATUS_ERROR;
break;
}
return playStatus;
}
// Do not modify without updating the HAL bt_rc.h files.
// match up with btrc_play_status_t enum of bt_rc.h
final static int PLAYSTATUS_STOPPED = 0;
final static int PLAYSTATUS_PLAYING = 1;
final static int PLAYSTATUS_PAUSED = 2;
final static int PLAYSTATUS_FWD_SEEK = 3;
final static int PLAYSTATUS_REV_SEEK = 4;
final static int PLAYSTATUS_ERROR = 255;
// match up with btrc_media_attr_t enum of bt_rc.h
final static int MEDIA_ATTR_TITLE = 1;
final static int MEDIA_ATTR_ARTIST = 2;
final static int MEDIA_ATTR_ALBUM = 3;
final static int MEDIA_ATTR_TRACK_NUM = 4;
final static int MEDIA_ATTR_NUM_TRACKS = 5;
final static int MEDIA_ATTR_GENRE = 6;
final static int MEDIA_ATTR_PLAYING_TIME = 7;
// match up with btrc_event_id_t enum of bt_rc.h
final static int EVT_PLAY_STATUS_CHANGED = 1;
final static int EVT_TRACK_CHANGED = 2;
final static int EVT_TRACK_REACHED_END = 3;
final static int EVT_TRACK_REACHED_START = 4;
final static int EVT_PLAY_POS_CHANGED = 5;
final static int EVT_BATT_STATUS_CHANGED = 6;
final static int EVT_SYSTEM_STATUS_CHANGED = 7;
final static int EVT_APP_SETTINGS_CHANGED = 8;
// match up with btrc_notification_type_t enum of bt_rc.h
final static int NOTIFICATION_TYPE_INTERIM = 0;
final static int NOTIFICATION_TYPE_CHANGED = 1;
// match up with BTRC_UID_SIZE of bt_rc.h
final static int TRACK_ID_SIZE = 8;
private native static void classInitNative();
private native void initNative();
private native void cleanupNative();
private native boolean getPlayStatusRspNative(int playStatus, int songLen, int songPos);
private native boolean getElementAttrRspNative(byte numAttr, int[] attrIds, String[] textArray);
private native boolean registerNotificationRspPlayStatusNative(int type, int playStatus);
private native boolean registerNotificationRspTrackChangeNative(int type, byte[] track);
private native boolean registerNotificationRspPlayPosNative(int type, int playPos);
}