summaryrefslogtreecommitdiffstats
path: root/src/com/android/bluetooth/a2dp/Avrcp.java
diff options
context:
space:
mode:
authorZhihai Xu <zhihaixu@google.com>2013-03-14 11:51:06 -0700
committerZhihai Xu <zhihaixu@google.com>2013-03-20 18:19:17 -0700
commitc1c259c0ace7195240f1443c805995bfe8692a72 (patch)
tree00a380a2eb1c1b63a496b82a005ea725e03e1883 /src/com/android/bluetooth/a2dp/Avrcp.java
parentb241cda1eec2fbefd6d21e0819532f7a76947635 (diff)
downloadandroid_packages_apps_Bluetooth-c1c259c0ace7195240f1443c805995bfe8692a72.tar.gz
android_packages_apps_Bluetooth-c1c259c0ace7195240f1443c805995bfe8692a72.tar.bz2
android_packages_apps_Bluetooth-c1c259c0ace7195240f1443c805995bfe8692a72.zip
framework support for new Bluetooth profiles AVRCP 1.3:Metadata and play status
send track changed response if the metadata is changed. issue 8383522 Change-Id: Ie55ed368d355484a6b83f4aa24c70aa33b72f799
Diffstat (limited to 'src/com/android/bluetooth/a2dp/Avrcp.java')
-rwxr-xr-xsrc/com/android/bluetooth/a2dp/Avrcp.java444
1 files changed, 444 insertions, 0 deletions
diff --git a/src/com/android/bluetooth/a2dp/Avrcp.java b/src/com/android/bluetooth/a2dp/Avrcp.java
new file mode 100755
index 000000000..d657d57c0
--- /dev/null
+++ b/src/com/android/bluetooth/a2dp/Avrcp.java
@@ -0,0 +1,444 @@
+/*
+ * 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.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.content.Intent;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ParcelUuid;
+import android.util.Log;
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.ProfileService;
+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 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 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 = 0L;
+
+ 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);
+ }
+
+ 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) {
+ Handler handler = mLocalHandler.get();
+ if (handler != null) {
+ handler.obtainMessage(MSG_UPDATE_STATE, generationId, state).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 setTransportControlFlags(int generationId, int flags) {
+ 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);
+ 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), -1, -1);
+ 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;
+
+ }
+ }
+ }
+
+ private void updatePlayPauseState(int state) {
+ if (DEBUG) Log.v(TAG,
+ "updatePlayPauseState(), old=" + mCurrentPlayState + ", state=" + state);
+ if (state == mCurrentPlayState) {
+ return;
+ }
+
+ int oldPlayStatus = convertPlayStateToPlayStatus(mCurrentPlayState);
+ int newPlayStatus = convertPlayStateToPlayStatus(state);
+ mCurrentPlayState = state;
+ 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 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 (DEBUG) Log.v(TAG, "mMetadata=" + mMetadata.toString());
+ }
+
+ 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;
+
+ }
+ }
+
+ private void sendTrackChangedRsp() {
+ byte[] track = new byte[TRACK_ID_SIZE];
+ for (int i = 0; i < TRACK_ID_SIZE; ++i) {
+ track[i] = (byte) (mTrackNumber >> (8 * i));
+ }
+ registerNotificationRspTrackChangeNative(mTrackChangedNT, track);
+ }
+
+ 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;
+
+ }
+ 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:
+ 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:
+ case RemoteControlClient.PLAYSTATE_NONE:
+ 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);
+}