Avrcp: Enable Cover Art feature
Port Cover Art feature from P.
CRs-Fixed:2596286
Change-Id: Idc50f228a5aa390b2a5e1f77dbfe820e76a98369
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
index 3b9056e..d3219e2 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -25,6 +25,7 @@
import android.media.MediaDescription;
import android.media.browse.MediaBrowser.MediaItem;
import android.media.session.PlaybackState;
+import android.os.Message;
import android.os.Bundle;
import android.util.Log;
@@ -96,11 +97,36 @@
"android.bluetooth.avrcp-controller.profile.extra.PLAYBACK";
+ /**
+ * Intent used to broadcast the change of folder list.
+ *
+ * <p>This intent will have the one extra:
+ * <ul>
+ * <li> {@link #EXTRA_FOLDER_LIST} - array of {@link MediaBrowser#MediaItem}
+ * containing the folder listing of currently selected folder.
+ * </ul>
+ */
+ public static final String ACTION_FOLDER_LIST =
+ "android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST";
+
+ public static final String EXTRA_FOLDER_LIST =
+ "android.bluetooth.avrcp-controller.profile.extra.FOLDER_LIST";
+
+ public static final String EXTRA_FOLDER_ID = "com.android.bluetooth.avrcp.EXTRA_FOLDER_ID";
+ public static final String EXTRA_FOLDER_BT_ID =
+ "com.android.bluetooth.avrcp-controller.EXTRA_FOLDER_BT_ID";
+
+ public static final String EXTRA_METADATA =
+ "android.bluetooth.avrcp-controller.profile.extra.METADATA";
+
static BrowseTree sBrowseTree;
private static AvrcpControllerService sService;
private final BluetoothAdapter mAdapter;
+ private int mFeatures;
+ private int mCaPsm;
+
protected Map<BluetoothDevice, AvrcpControllerStateMachine> mDeviceStateMap =
new ConcurrentHashMap<>(1);
@@ -312,11 +338,15 @@
// Called by JNI to notify Avrcp of features supported by the Remote device.
private void getRcFeatures(byte[] address, int features, int caPsm) {
- /*Log.i(TAG, " getRcFeatures caPsm :" + caPsm);
+ Log.i(TAG, " getRcFeatures caPsm :" + caPsm);
+ mFeatures = features;
+ mCaPsm = caPsm;
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
- Message msg = mAvrcpCtSm.obtainMessage(
- AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES, features, caPsm, device);
- mAvrcpCtSm.sendMessage(msg); */
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(
+ AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES, features, caPsm, device);
+ }
}
// Called by JNI
@@ -361,15 +391,29 @@
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
AvrcpControllerStateMachine stateMachine = getStateMachine(device);
if (stateMachine != null) {
- stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED,
- TrackInfo.getMetadata(attributes, attribVals));
+ List<Integer> attrList = new ArrayList<>();
+ for (int attr : attributes) {
+ attrList.add(attr);
+ }
+ List<String> attrValList = Arrays.asList(attribVals);
+ TrackInfo trackInfo = new TrackInfo(attrList, attrValList);
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED, trackInfo);
}
}
private void onElementAttributeUpdate(byte[] address, byte numAttributes, int[] attributes,
String[] attribVals) {
-
+ if (DBG) {
+ Log.d(TAG, "onElementAttributeUpdate");
+ }
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine == null)
+ return;
+ CoverArtUtils coverArtUtils = new CoverArtUtils();
+ coverArtUtils.onElementAttributeUpdate(address, numAttributes, attributes, attribVals,
+ device, stateMachine);
}
// Called by JNI periodically based upon timer to update play position
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
index e0317f6..03d8f48 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -80,6 +80,7 @@
static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 214;
static final int MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED = 215;
static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 216;
+ static final int MESSAGE_PROCESS_RC_FEATURES = 217;
//300->399 Events for Browsing
static final int MESSAGE_GET_FOLDER_ITEMS = 300;
@@ -112,6 +113,9 @@
protected final Disconnecting mDisconnecting;
private A2dpSinkService mA2dpSinkService;
+ private static CoverArtUtils mCoveArtUtils;
+ private AvrcpControllerBipStateMachine mBipStateMachine;
+
protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED;
boolean mRemoteControlConnected = false;
@@ -163,6 +167,9 @@
IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
mService.registerReceiver(mBroadcastReceiver, filter);
+ mCoveArtUtils = new CoverArtUtils();
+ mBipStateMachine = AvrcpControllerBipStateMachine.make(this, getHandler(), service);
+
setInitialState(mDisconnected);
}
@@ -356,8 +363,11 @@
return true;
case MESSAGE_PROCESS_TRACK_CHANGED:
- mAddressedPlayer.updateCurrentTrack((MediaMetadata) msg.obj);
- BluetoothMediaBrowserService.trackChanged((MediaMetadata) msg.obj);
+ TrackInfo trackInfo = (TrackInfo)msg.obj;
+ mAddressedPlayer.updateCurrentTrack((MediaMetadata) trackInfo.getMediaMetaData());
+ BluetoothMediaBrowserService.trackChanged((MediaMetadata) trackInfo.getMediaMetaData());
+ mAddressedPlayer.updateCurrentTrackInfo(trackInfo);
+ mCoveArtUtils.msgTrackChanged(mService, mBipStateMachine,mAddressedPlayer,mRemoteDevice);
return true;
case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
@@ -460,6 +470,20 @@
mVolumeChangedNotificationsToIgnore = 0;
return true;
+ case MESSAGE_PROCESS_RC_FEATURES:
+ mRemoteDevice.setRemoteFeatures(msg.arg1);
+ if (msg.arg2 > 0) {
+ mCoveArtUtils.msgProcessRcFeatures(mBipStateMachine, mRemoteDevice,msg.arg2);
+ }
+ return true;
+
+ case CoverArtUtils.MESSAGE_BIP_CONNECTED:
+ case CoverArtUtils.MESSAGE_BIP_DISCONNECTED:
+ case CoverArtUtils.MESSAGE_BIP_IMAGE_FETCHED:
+ case CoverArtUtils.MESSAGE_BIP_THUMB_NAIL_FETCHED:
+ mCoveArtUtils.processBipAction(mService, mAddressedPlayer,
+ mRemoteDevice, msg.what, msg);
+ return true;
default:
return super.processMessage(msg);
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
index ab0e987..7bd7bcb 100644
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpPlayer.java
@@ -53,6 +53,8 @@
private MediaMetadata mCurrentTrack;
private PlaybackState mPlaybackState;
+ private TrackInfo mCurrentTrackInfo = new TrackInfo();
+
AvrcpPlayer() {
mId = INVALID_ID;
//Set Default Actions in case Player data isn't available.
@@ -175,4 +177,12 @@
}
if (DBG) Log.d(TAG, "Supported Actions = " + mAvailableActions);
}
+
+ public synchronized void updateCurrentTrackInfo(TrackInfo update) {
+ mCurrentTrackInfo = update;
+ }
+
+ public synchronized TrackInfo getCurrentTrackInfo() {
+ return mCurrentTrackInfo;
+ }
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
index 0240610..961e92e 100644
--- a/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
+++ b/src/com/android/bluetooth/avrcpcontroller/TrackInfo.java
@@ -17,9 +17,29 @@
package com.android.bluetooth.avrcpcontroller;
import android.media.MediaMetadata;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.MediaMetadata;
+import android.net.Uri;
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
final class TrackInfo {
+
+ private static final String TAG = "AvrcpTrackInfo";
+ private static final boolean VDBG = AvrcpControllerService.VDBG;
+ /*
+ * Default values for each of the items from JNI
+ */
+ private static final int TRACK_NUM_INVALID = -1;
+ private static final int TOTAL_TRACKS_INVALID = -1;
+ private static final int TOTAL_TRACK_TIME_INVALID = -1;
+ private static final String UNPOPULATED_ATTRIBUTE = "";
+
/*
*Element Id Values for GetMetaData from JNI
*/
@@ -30,6 +50,108 @@
private static final int MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER = 0x05;
private static final int MEDIA_ATTRIBUTE_GENRE = 0x06;
private static final int MEDIA_ATTRIBUTE_PLAYING_TIME = 0x07;
+ private static final int MEDIA_ATTRIBUTE_COVER_ART_HANDLE = 0x08;
+
+ private final String mArtistName;
+ private final String mTrackTitle;
+ private final String mAlbumTitle;
+ private final String mGenre;
+ private final long mTrackNum; // number of audio file on original recording.
+ private final long mTotalTracks; // total number of tracks on original recording
+ private final long mTrackLen; // full length of AudioFile.
+ private String mCoverArtHandle;
+ private String mImageLocation;
+ private String mThumbNailLocation;
+
+
+ TrackInfo() {
+ this(new ArrayList<Integer>(), new ArrayList<String>());
+ }
+
+ TrackInfo(List<Integer> attrIds, List<String> attrMap) {
+ Map<Integer, String> attributeMap = new HashMap<>();
+ for (int i = 0; i < attrIds.size(); i++) {
+ attributeMap.put(attrIds.get(i), attrMap.get(i));
+ }
+
+ String attribute;
+ mTrackTitle = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_TITLE, UNPOPULATED_ATTRIBUTE);
+
+ mArtistName = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_ARTIST_NAME, UNPOPULATED_ATTRIBUTE);
+
+ mAlbumTitle = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_ALBUM_NAME, UNPOPULATED_ATTRIBUTE);
+
+ attribute = attributeMap.get(MEDIA_ATTRIBUTE_TRACK_NUMBER);
+ mTrackNum = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute)
+ : TRACK_NUM_INVALID;
+
+ attribute = attributeMap.get(MEDIA_ATTRIBUTE_TOTAL_TRACK_NUMBER);
+ mTotalTracks = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute)
+ : TOTAL_TRACKS_INVALID;
+
+ mGenre = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_GENRE, UNPOPULATED_ATTRIBUTE);
+
+ attribute = attributeMap.get(MEDIA_ATTRIBUTE_PLAYING_TIME);
+ mTrackLen = (attribute != null && !attribute.isEmpty()) ? Long.valueOf(attribute)
+ : TOTAL_TRACK_TIME_INVALID;
+ mCoverArtHandle = attributeMap.getOrDefault(MEDIA_ATTRIBUTE_COVER_ART_HANDLE,
+ UNPOPULATED_ATTRIBUTE);
+ mImageLocation = UNPOPULATED_ATTRIBUTE;
+ mThumbNailLocation = UNPOPULATED_ATTRIBUTE;
+ }
+
+ boolean updateImageLocation(String mCAHandle, String mLocation) {
+ if (VDBG) Log.d(TAG, " updateImageLocation hndl " + mCAHandle + " location " + mLocation);
+ if (!mCAHandle.equals(mCoverArtHandle) || (mLocation == null)) {
+ return false;
+ }
+ mImageLocation = mLocation;
+ return true;
+ }
+
+ boolean updateThumbNailLocation(String mCAHandle, String mLocation) {
+ if (VDBG) Log.d(TAG, " mCAHandle " + mCAHandle + " location " + mLocation);
+ if (!mCAHandle.equals(mCoverArtHandle) || (mLocation == null)) {
+ return false;
+ }
+ mThumbNailLocation = mLocation;
+ return true;
+ }
+
+ public String toString() {
+ return "Metadata [artist=" + mArtistName + " trackTitle= " + mTrackTitle + " albumTitle= "
+ + mAlbumTitle + " genre= " + mGenre + " trackNum= " + Long.toString(mTrackNum)
+ + " track_len : " + Long.toString(mTrackLen) + " TotalTracks " + Long.toString(
+ mTotalTracks) + " mCoverArtHandle=" + mCoverArtHandle +
+ " mImageLocation :"+mImageLocation+"]";
+ }
+
+
+ public MediaMetadata getMediaMetaData() {
+ if (VDBG) {
+ Log.d(TAG, " TrackInfo " + toString());
+ }
+ MediaMetadata.Builder mMetaDataBuilder = new MediaMetadata.Builder();
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, mArtistName);
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, mTrackTitle);
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ALBUM, mAlbumTitle);
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_GENRE, mGenre);
+ mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, mTrackNum);
+ mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, mTotalTracks);
+ mMetaDataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION, mTrackLen);
+ if (mImageLocation != UNPOPULATED_ATTRIBUTE) {
+ Uri imageUri = Uri.parse(mImageLocation);
+ if (VDBG) Log.d(TAG," updating image uri = " + imageUri.toString());
+ mMetaDataBuilder.putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+ imageUri.toString());
+ }
+ if (mThumbNailLocation != UNPOPULATED_ATTRIBUTE) {
+ Bitmap mThumbNailBitmap = BitmapFactory.decodeFile(mThumbNailLocation);
+ mMetaDataBuilder.putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, mThumbNailBitmap);
+ }
+ return mMetaDataBuilder.build();
+ }
+
static MediaMetadata getMetadata(int[] attrIds, String[] attrMap) {
MediaMetadata.Builder metaDataBuilder = new MediaMetadata.Builder();
@@ -74,7 +196,45 @@
break;
}
}
-
+
return metaDataBuilder.build();
}
+
+ public String displayMetaData() {
+ MediaMetadata metaData = getMediaMetaData();
+ StringBuffer sb = new StringBuffer();
+ // getDescription only contains artist, title and album
+ sb.append(metaData.getDescription().toString() + " ");
+ if (metaData.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
+ sb.append(metaData.getString(MediaMetadata.METADATA_KEY_GENRE) + " ");
+ }
+ if (metaData.containsKey(MediaMetadata.METADATA_KEY_MEDIA_ID)) {
+ sb.append(metaData.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) + " ");
+ }
+ if (metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
+ sb.append(
+ Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) + " ");
+ }
+ if (metaData.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
+ sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS)) + " ");
+ }
+ if (metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
+ sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
+ }
+ if (metaData.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
+ sb.append(Long.toString(metaData.getLong(MediaMetadata.METADATA_KEY_DURATION)) + " ");
+ }
+ return sb.toString();
+ }
+
+ String getCoverArtHandle() {
+ return mCoverArtHandle;
+ }
+
+ void clearCoverArtData() {
+ mCoverArtHandle = UNPOPULATED_ATTRIBUTE;
+ mImageLocation = UNPOPULATED_ATTRIBUTE;
+ mThumbNailLocation = UNPOPULATED_ATTRIBUTE;
+ }
+
}