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;
+    }
+
 }