blob: 16fc3274fe4fc7db50d46c5d2afcc5af602b0f54 [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 java.util.Timer;
import java.util.TimerTask;
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 = true;
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 long mSkipStartTime;
private int mFeatures;
private int mAbsoluteVolume;
private int mLastSetVolume;
private int mLastDirection;
private final int mVolumeStep;
private final int mAudioStreamMax;
private boolean mVolCmdInProgress;
private int mAbsVolRetryTimes;
private int mSkipAmount;
/* AVRC IDs from avrc_defs.h */
private static final int AVRC_ID_REWIND = 0x48;
private static final int AVRC_ID_FAST_FOR = 0x49;
/* BTRC features */
public static final int BTRC_FEAT_METADATA = 0x01;
public static final int BTRC_FEAT_ABSOLUTE_VOLUME = 0x02;
public static final int BTRC_FEAT_BROWSE = 0x04;
/* AVRC response codes, from avrc_defs */
private static final int AVRC_RSP_NOT_IMPL = 8;
private static final int AVRC_RSP_ACCEPT = 9;
private static final int AVRC_RSP_REJ = 10;
private static final int AVRC_RSP_IN_TRANS = 11;
private static final int AVRC_RSP_IMPL_STBL = 12;
private static final int AVRC_RSP_CHANGED = 13;
private static final int AVRC_RSP_INTERIM = 15;
private static final int MESSAGE_GET_RC_FEATURES = 1;
private static final int MESSAGE_GET_PLAY_STATUS = 2;
private static final int MESSAGE_GET_ELEM_ATTRS = 3;
private static final int MESSAGE_REGISTER_NOTIFICATION = 4;
private static final int MESSAGE_PLAY_INTERVAL_TIMEOUT = 5;
private static final int MESSAGE_VOLUME_CHANGED = 6;
private static final int MESSAGE_ADJUST_VOLUME = 7;
private static final int MESSAGE_SET_ABSOLUTE_VOLUME = 8;
private static final int MESSAGE_ABS_VOL_TIMEOUT = 9;
private static final int MESSAGE_FAST_FORWARD = 10;
private static final int MESSAGE_REWIND = 11;
private static final int MESSAGE_CHANGE_PLAY_POS = 12;
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;
private static final int BUTTON_TIMEOUT_TIME = 2000;
private static final int BASE_SKIP_AMOUNT = 2000;
private static final int KEY_STATE_PRESS = 1;
private static final int KEY_STATE_RELEASE = 0;
private static final int SKIP_PERIOD = 400;
private static final int SKIP_DOUBLE_INTERVAL = 3000;
private static final long MAX_MULTIPLIER_VALUE = 128L;
private static final int CMD_TIMEOUT_DELAY = 2000;
private static final int MAX_ERROR_RETRY_TIMES = 3;
private static final int AVRCP_MAX_VOL = 127;
private static final int AVRCP_BASE_VOLUME_STEP = 1;
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 = 0L;
mPlayStartTimeMs = -1L;
mSongLengthMs = 0L;
mPlaybackIntervalMs = 0L;
mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED;
mFeatures = 0;
mAbsoluteVolume = -1;
mLastSetVolume = -1;
mLastDirection = 0;
mVolCmdInProgress = false;
mAbsVolRetryTimes = 0;
mContext = context;
initNative();
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mAudioStreamMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
mVolumeStep = Math.max(AVRCP_BASE_VOLUME_STEP, AVRCP_MAX_VOL/mAudioStreamMax);
}
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();
}
}
@Override
public void setEnabled(boolean enabled) {
// no-op: this RemoteControlDisplay is not subject to being disabled.
}
}
/** 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_RC_FEATURES:
String address = (String) msg.obj;
if (DEBUG) Log.v(TAG, "MESSAGE_GET_RC_FEATURES: address="+address+
", features="+msg.arg1);
mFeatures = msg.arg1;
mAudioManager.avrcpSupportsAbsoluteVolume(address, isAbsoluteVolumeSupported());
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;
case MESSAGE_VOLUME_CHANGED:
if (DEBUG) Log.v(TAG, "MESSAGE_VOLUME_CHANGED: volume=" + msg.arg1 +
" ctype=" + msg.arg2);
if (msg.arg2 == AVRC_RSP_ACCEPT || msg.arg2 == AVRC_RSP_REJ) {
if (mVolCmdInProgress == false) {
Log.e(TAG, "Unsolicited response, ignored");
break;
}
removeMessages(MESSAGE_ABS_VOL_TIMEOUT);
mVolCmdInProgress = false;
mAbsVolRetryTimes = 0;
}
if (mAbsoluteVolume != msg.arg1 && (msg.arg2 == AVRC_RSP_ACCEPT ||
msg.arg2 == AVRC_RSP_CHANGED ||
msg.arg2 == AVRC_RSP_INTERIM)) {
notifyVolumeChanged(msg.arg1);
mAbsoluteVolume = msg.arg1;
} else if (msg.arg2 == AVRC_RSP_REJ) {
Log.e(TAG, "setAbsoluteVolume call rejected");
}
break;
case MESSAGE_ADJUST_VOLUME:
if (DEBUG) Log.d(TAG, "MESSAGE_ADJUST_VOLUME: direction=" + msg.arg1);
if (mVolCmdInProgress) {
if (DEBUG) Log.w(TAG, "There is already a volume command in progress.");
break;
}
// Wait on verification on volume from device, before changing the volume.
if (mAbsoluteVolume != -1 && (msg.arg1 == -1 || msg.arg1 == 1)) {
int setVol = Math.min(AVRCP_MAX_VOL,
Math.max(0, mAbsoluteVolume + msg.arg1*mVolumeStep));
if (setVolumeNative(setVol)) {
sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT),
CMD_TIMEOUT_DELAY);
mVolCmdInProgress = true;
mLastDirection = msg.arg1;
mLastSetVolume = setVol;
}
} else {
Log.e(TAG, "Unknown direction in MESSAGE_ADJUST_VOLUME");
}
break;
case MESSAGE_SET_ABSOLUTE_VOLUME:
if (DEBUG) Log.v(TAG, "MESSAGE_SET_ABSOLUTE_VOLUME");
if (mVolCmdInProgress) {
if (DEBUG) Log.w(TAG, "There is already a volume command in progress.");
break;
}
if (setVolumeNative(msg.arg1)) {
sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT), CMD_TIMEOUT_DELAY);
mVolCmdInProgress = true;
mLastSetVolume = msg.arg1;
}
break;
case MESSAGE_ABS_VOL_TIMEOUT:
if (DEBUG) Log.v(TAG, "MESSAGE_ABS_VOL_TIMEOUT: Volume change cmd timed out.");
mVolCmdInProgress = false;
if (mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) {
mAbsVolRetryTimes = 0;
} else {
mAbsVolRetryTimes += 1;
if (setVolumeNative(mLastSetVolume)) {
sendMessageDelayed(obtainMessage(MESSAGE_ABS_VOL_TIMEOUT),
CMD_TIMEOUT_DELAY);
mVolCmdInProgress = true;
}
}
break;
case MESSAGE_FAST_FORWARD:
case MESSAGE_REWIND:
int skipAmount;
if (msg.what == MESSAGE_FAST_FORWARD) {
if (DEBUG) Log.v(TAG, "MESSAGE_FAST_FORWARD");
skipAmount = BASE_SKIP_AMOUNT;
} else {
if (DEBUG) Log.v(TAG, "MESSAGE_REWIND");
skipAmount = -BASE_SKIP_AMOUNT;
}
if (hasMessages(MESSAGE_CHANGE_PLAY_POS) &&
(skipAmount != mSkipAmount)) {
Log.w(TAG, "missing release button event:" + mSkipAmount);
}
if ((!hasMessages(MESSAGE_CHANGE_PLAY_POS)) ||
(skipAmount != mSkipAmount)) {
mSkipStartTime = SystemClock.elapsedRealtime();
}
removeMessages(MESSAGE_CHANGE_PLAY_POS);
if (msg.arg1 == KEY_STATE_PRESS) {
mSkipAmount = skipAmount;
changePositionBy(mSkipAmount * getSkipMultiplier());
Message posMsg = obtainMessage(MESSAGE_CHANGE_PLAY_POS);
posMsg.arg1 = 1;
sendMessageDelayed(posMsg, SKIP_PERIOD);
}
break;
case MESSAGE_CHANGE_PLAY_POS:
if (DEBUG) Log.v(TAG, "MESSAGE_CHANGE_PLAY_POS:" + msg.arg1);
changePositionBy(mSkipAmount * getSkipMultiplier());
if (msg.arg1 * SKIP_PERIOD < BUTTON_TIMEOUT_TIME) {
Message posMsg = obtainMessage(MESSAGE_CHANGE_PLAY_POS);
posMsg.arg1 = msg.arg1 + 1;
sendMessageDelayed(posMsg, SKIP_PERIOD);
}
break;
}
}
}
private void updatePlayPauseState(int state, long currentPosMs) {
if (DEBUG) Log.v(TAG,
"updatePlayPauseState, old=" + mCurrentPlayState + ", state=" + state);
boolean oldPosValid = (mCurrentPosMs !=
RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN);
int oldPlayStatus = convertPlayStateToPlayStatus(mCurrentPlayState);
int newPlayStatus = convertPlayStateToPlayStatus(state);
if ((mCurrentPlayState == RemoteControlClient.PLAYSTATE_PLAYING) &&
(mCurrentPlayState != state) && oldPosValid) {
mCurrentPosMs = getPlayPosition();
}
mCurrentPlayState = state;
if (currentPosMs != RemoteControlClient.PLAYBACK_POSITION_INVALID) {
mCurrentPosMs = currentPosMs;
}
if (state == RemoteControlClient.PLAYSTATE_PLAYING) {
mPlayStartTimeMs = SystemClock.elapsedRealtime();
}
boolean newPosValid = (mCurrentPosMs !=
RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN);
long playPosition = getPlayPosition();
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 && ((playPosition >= mNextPosMs) || (playPosition <= mPrevPosMs))))) {
mPlayPosChangedNT = NOTIFICATION_TYPE_CHANGED;
registerNotificationRspPlayPosNative(mPlayPosChangedNT, (int)playPosition);
}
if ((mPlayPosChangedNT == NOTIFICATION_TYPE_INTERIM) && newPosValid &&
(state == RemoteControlClient.PLAYSTATE_PLAYING)) {
Message msg = mHandler.obtainMessage(MESSAGE_PLAY_INTERVAL_TIMEOUT);
mHandler.sendMessageDelayed(msg, mNextPosMs - playPosition);
}
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_ALWAYS_UNKNOWN) {
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 getRcFeatures(byte[] address, int features) {
Message msg = mHandler.obtainMessage(MESSAGE_GET_RC_FEATURES, features, 0,
Utils.getAddressStringFromByte(address));
mHandler.sendMessage(msg);
}
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_ALWAYS_UNKNOWN) {
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 handlePassthroughCmd(int id, int keyState) {
switch (id) {
case AVRC_ID_REWIND:
rewind(keyState);
break;
case AVRC_ID_FAST_FOR:
fastForward(keyState);
break;
}
}
private void fastForward(int keyState) {
Message msg = mHandler.obtainMessage(MESSAGE_FAST_FORWARD, keyState, 0);
mHandler.sendMessage(msg);
}
private void rewind(int keyState) {
Message msg = mHandler.obtainMessage(MESSAGE_REWIND, keyState, 0);
mHandler.sendMessage(msg);
}
private void changePositionBy(long amount) {
long currentPosMs = getPlayPosition();
if (currentPosMs == -1L) return;
long newPosMs = Math.max(0L, currentPosMs + amount);
mAudioManager.setRemoteControlClientPlaybackPosition(mClientGeneration,
newPosMs);
}
private int getSkipMultiplier() {
long currentTime = SystemClock.elapsedRealtime();
long multi = (long) Math.pow(2, (currentTime - mSkipStartTime)/SKIP_DOUBLE_INTERVAL);
return (int) Math.min(MAX_MULTIPLIER_VALUE, multi);
}
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_ALWAYS_UNKNOWN) {
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;
}
/**
* This is called from AudioService. It will return whether this device supports abs volume.
* NOT USED AT THE MOMENT.
*/
public boolean isAbsoluteVolumeSupported() {
return ((mFeatures & BTRC_FEAT_ABSOLUTE_VOLUME) != 0);
}
/**
* We get this call from AudioService. This will send a message to our handler object,
* requesting our handler to call setVolumeNative()
*/
public void adjustVolume(int direction) {
Message msg = mHandler.obtainMessage(MESSAGE_ADJUST_VOLUME, direction, 0);
mHandler.sendMessage(msg);
}
public void setAbsoluteVolume(int volume) {
int avrcpVolume = convertToAvrcpVolume(volume);
avrcpVolume = Math.min(AVRCP_MAX_VOL, Math.max(0, avrcpVolume));
mHandler.removeMessages(MESSAGE_ADJUST_VOLUME);
Message msg = mHandler.obtainMessage(MESSAGE_SET_ABSOLUTE_VOLUME, avrcpVolume, 0);
mHandler.sendMessage(msg);
}
/* Called in the native layer as a btrc_callback to return the volume set on the carkit in the
* case when the volume is change locally on the carkit. This notification is not called when
* the volume is changed from the phone.
*
* This method will send a message to our handler to change the local stored volume and notify
* AudioService to update the UI
*/
private void volumeChangeCallback(int volume, int ctype) {
Message msg = mHandler.obtainMessage(MESSAGE_VOLUME_CHANGED, volume, ctype);
mHandler.sendMessage(msg);
}
private void notifyVolumeChanged(int volume) {
volume = convertToAudioStreamVolume(volume);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
}
private int convertToAudioStreamVolume(int volume) {
// Rescale volume to match AudioSystem's volume
return (int) Math.ceil((double) volume*mAudioStreamMax/AVRCP_MAX_VOL);
}
private int convertToAvrcpVolume(int volume) {
return (int) Math.ceil((double) volume*AVRCP_MAX_VOL/mAudioStreamMax);
}
// 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);
private native boolean setVolumeNative(int volume);
}