Merge "A2dp: update correct state to HeadsetA2dpSync while enter Disconnected" into q-keystone-qcom-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index bc10b4b..e435470 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,6 +20,7 @@
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.ACCESS_BLUETOOTH_SHARE" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index fd23784..b232568 100755
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -72,6 +72,7 @@
     private static A2dpService sA2dpService;
     private static A2dpSinkService sA2dpSinkService;
     private static boolean mA2dpSrcSnkConcurrency;
+    private static boolean a2dpMulticast = false;
 
     private AdapterService mAdapterService;
     private HandlerThread mStateMachinesThread;
@@ -285,6 +286,10 @@
         if (DBG) {
             Log.d(TAG, "A2DP concurrency mode set to " + mA2dpSrcSnkConcurrency);
         }
+        a2dpMulticast = SystemProperties.getBoolean("persist.vendor.service.bt.a2dp_multicast_enable", false);
+        if (DBG) {
+                Log.d(TAG, "A2DP Multicast flag set to " + a2dpMulticast);
+        }
         return true;
     }
 
@@ -978,8 +983,11 @@
                 Log.e(TAG,"adapterService is null");
             }
         }
-        if (mAvrcp_ext != null && !tws_switch) {
-            mAvrcp_ext.setAbsVolumeFlag(device);
+        // Don't update the absVolume flags when disconnect one device in multicast mode
+        if (!a2dpMulticast || previousActiveDevice == null) {
+            if (mAvrcp_ext != null && !tws_switch) {
+                mAvrcp_ext.setAbsVolumeFlag(device);
+            }
         }
         tws_switch = false;
         return true;
@@ -1072,6 +1080,18 @@
         }
     }
 
+    public void storeDeviceAudioVolume(BluetoothDevice device) {
+        if (device != null)
+        {
+            if (AvrcpTargetService.get() != null) {
+                AvrcpTargetService.get().storeVolumeForDevice(device);
+            } else if (mAvrcp_ext != null) {
+                //store volume in multi-a2dp for the device doesn't set as active
+                mAvrcp_ext.storeVolumeForDevice(device);
+            }
+        }
+    }
+
     public void resetAvrcpBlacklist(BluetoothDevice device) {
         synchronized(mBtAvrcpLock) {
             if (mAvrcp_ext != null) {
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 ec85592..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,11 +113,15 @@
     protected final Disconnecting mDisconnecting;
     private A2dpSinkService mA2dpSinkService;
 
+    private static CoverArtUtils mCoveArtUtils;
+    private AvrcpControllerBipStateMachine mBipStateMachine;
+
     protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED;
 
     boolean mRemoteControlConnected = false;
     boolean mBrowsingConnected = false;
     final BrowseTree mBrowseTree;
+    private boolean smActive = false;
     private AvrcpPlayer mAddressedPlayer = new AvrcpPlayer();
     private RemoteDevice mRemoteDevice;
     private int mPreviousPercentageVol = -1;
@@ -151,6 +156,7 @@
         addState(mConnected);
         addState(mDisconnecting);
 
+        smActive = true;
         mGetFolderList = new GetFolderList();
         addState(mGetFolderList, mConnected);
 
@@ -161,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);
     }
 
@@ -228,7 +237,7 @@
     }
 
     synchronized void onBrowsingConnected() {
-        if (mBrowsingConnected) return;
+        if (mBrowsingConnected || (!smActive)) return;
         mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode);
         BluetoothMediaBrowserService.notifyChanged(mService
                 .sBrowseTree.mRootNode);
@@ -237,7 +246,7 @@
     }
 
     synchronized void onBrowsingDisconnected() {
-        if (!mBrowsingConnected) return;
+        if (!mBrowsingConnected || (!smActive)) return;
         mAddressedPlayer.setPlayStatus(PlaybackState.STATE_ERROR);
         mAddressedPlayer.updateCurrentTrack(null);
         if (mBrowseTree != null && mBrowseTree.mNowPlayingNode != null) {
@@ -354,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:
@@ -458,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);
             }
@@ -799,6 +825,9 @@
             // If the receiver was never registered unregister will throw an
             // IllegalArgumentException.
         }
+        synchronized(AvrcpControllerStateMachine.this) {
+            smActive = false;
+        }
         // we should disacrd, all currently queuedup messages.
         quitNow();
     }
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;
+    }
+
 }
diff --git a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
old mode 100644
new mode 100755
index 1a356a8..17b9100
--- a/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
+++ b/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -33,6 +33,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.bluetooth.a2dp.A2dpService;
@@ -124,6 +125,8 @@
     private BluetoothDevice mA2dpActiveDevice = null;
     private BluetoothDevice mHfpActiveDevice = null;
     private BluetoothDevice mHearingAidActiveDevice = null;
+    private boolean mTwsPlusSwitch = false;
+    private static boolean a2dpMulticast = false;
 
     // Broadcast receiver for all changes
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -214,8 +217,19 @@
                         mA2dpConnectedDevices.add(device);
                         if (mHearingAidActiveDevice == null) {
                             // New connected device: select it as active
-                            setA2dpActiveDevice(device);
-                            break;
+                            if (!a2dpMulticast) {
+                                setA2dpActiveDevice(device);
+                            }
+                            else {
+                                if (mA2dpActiveDevice == null) {
+                                    setA2dpActiveDevice(device);
+                                }
+                                else {
+                                    // store the volume for the new added device
+                                    final A2dpService a2dpService = mFactory.getA2dpService();
+                                    a2dpService.storeDeviceAudioVolume(device);
+                                }
+                            }
                         }
                         break;
                     }
@@ -231,7 +245,7 @@
                         if (Objects.equals(mA2dpActiveDevice, device)) {
                             final A2dpService mA2dpService = mFactory.getA2dpService();
                             BluetoothDevice mDevice = null;
-                            if (mAdapterService.isTwsPlusDevice(device) &&
+                            if (mAdapterService.isTwsPlusDevice(device) && !mTwsPlusSwitch &&
                                 !mA2dpConnectedDevices.isEmpty()) {
                                 for (BluetoothDevice connected_device: mA2dpConnectedDevices) {
                                     if (mAdapterService.isTwsPlusDevice(connected_device) &&
@@ -241,9 +255,22 @@
                                         break;
                                     }
                                 }
+                            } else if (device.isTwsPlusDevice() && mTwsPlusSwitch) {
+                                Log.d(TAG, "Resetting mTwsPlusSwitch");
+                                mTwsPlusSwitch = false;
+                            }
+                            else if (a2dpMulticast && !mA2dpConnectedDevices.isEmpty()) {
+                                for (BluetoothDevice connected_device: mA2dpConnectedDevices) {
+                                    if (mA2dpService.getConnectionState(connected_device) ==
+                                        BluetoothProfile.STATE_CONNECTED) {
+                                        Log.d(TAG, "a2dp Multicast calling set a2dp Active dev: " + connected_device);
+                                        mDevice = connected_device;
+                                        break;
+                                    }
+                                }
                             }
                             if (!setA2dpActiveDevice(mDevice) && (mDevice != null) &&
-                                mAdapterService.isTwsPlusDevice(mDevice)) {
+                                (mAdapterService.isTwsPlusDevice(mDevice) || a2dpMulticast)) {
                                 Log.w(TAG, "Switch A2dp active device to peer earbud failed");
                                 setA2dpActiveDevice(null);
                             }
@@ -289,7 +316,7 @@
                             break;      // The device is already connected
                         }
                         mHfpConnectedDevices.add(device);
-                        if (mHearingAidActiveDevice == null) {
+                        if ((!a2dpMulticast || mHfpActiveDevice == null) && mHearingAidActiveDevice == null) {
                             // New connected device: select it as active
                             setHfpActiveDevice(device);
                             break;
@@ -329,7 +356,23 @@
                                           "as there is no Connected TWS+ peer");
                                    setHfpActiveDevice(null);
                                 }
-                            } else {
+                            } else if (a2dpMulticast && !mHfpConnectedDevices.isEmpty()) {
+                                if (hfpService == null) {
+                                    Log.e(TAG, "no headsetService, FATAL");
+                                    return;
+                                }
+                                for (BluetoothDevice connected_device: mHfpConnectedDevices) {
+                                    if (hfpService.getConnectionState(connected_device) ==
+                                        BluetoothProfile.STATE_CONNECTED) {
+                                        Log.d(TAG, "a2dp Multicast calling set HFP Active dev: " + connected_device);
+                                        if (!setHfpActiveDevice(connected_device)) {
+                                            setHfpActiveDevice(null);
+                                        }
+                                        break;
+                                    }
+                                }
+                            }
+                            else {
                                setHfpActiveDevice(null);
                             }
                         }
@@ -439,6 +482,7 @@
         mAdapterService.registerReceiver(mReceiver, filter);
 
         mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler);
+        a2dpMulticast = SystemProperties.getBoolean("persist.vendor.service.bt.a2dp_multicast_enable", false);
     }
 
     void cleanup() {
@@ -454,7 +498,12 @@
         }
         resetState();
     }
-
+    public void notify_active_device_unbonding(BluetoothDevice device) {
+        if (device.isTwsPlusDevice() && Objects.equals(mA2dpActiveDevice, device)) {
+            Log.d(TAG,"TWS+ active device is getting unpaired, avoid switch to pair");
+            mTwsPlusSwitch = true;
+        }
+    }
     /**
      * Get the {@link Looper} for the handler thread. This is used in testing and helper
      * objects
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index d9e0eb4..d7c6469 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -2472,7 +2472,9 @@
             return false;
         }
         deviceProp.setBondingInitiatedLocally(false);
-
+        if (device.isTwsPlusDevice()) {
+            mActiveDeviceManager.notify_active_device_unbonding(device);
+        }
         Message msg = mBondStateMachine.obtainMessage(BondStateMachine.REMOVE_BOND);
         msg.obj = device;
         mBondStateMachine.sendMessage(msg);
@@ -3598,15 +3600,7 @@
             debugLog(action);
             if (action == null) return;
             if (isEnabled() && (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION))) {
-                 WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);
-                 if ((wifiMgr != null) && (wifiMgr.isWifiEnabled())) {
-                     WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
-                     if ((wifiInfo != null) && (wifiInfo.getNetworkId() != -1)) {
-                         mVendor.setWifiState(true);
-                     } else {
-                         mVendor.setWifiState(false);
-                     }
-                 }
+                fetchWifiState();
              } else if (isEnabled() &&
                         (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION) ||
                         (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)))){
diff --git a/src/com/android/bluetooth/hfp/HeadsetA2dpSync.java b/src/com/android/bluetooth/hfp/HeadsetA2dpSync.java
index 4c28e8b..7fb2e2d 100644
--- a/src/com/android/bluetooth/hfp/HeadsetA2dpSync.java
+++ b/src/com/android/bluetooth/hfp/HeadsetA2dpSync.java
@@ -38,6 +38,8 @@
 import android.util.Log;
 import java.util.HashMap;
 import java.util.concurrent.ConcurrentHashMap;
+import com.android.bluetooth.hearingaid.HearingAidService;
+import java.util.List;
 
 /**
  * Defines methods used for synchronization between HFP and A2DP
@@ -113,6 +115,18 @@
      */
     public boolean suspendA2DP(int reason, BluetoothDevice device){
         int a2dpState = isA2dpPlaying();
+
+        List<BluetoothDevice> HAActiveDevices = null;
+        HearingAidService mHaService = HearingAidService.getHearingAidService();
+        if (mHaService != null) {
+            HAActiveDevices = mHaService.getActiveDevices();
+        }
+        if (HAActiveDevices != null && (HAActiveDevices.get(0) != null
+                || HAActiveDevices.get(1) != null)) {
+            Log.d(TAG,"Ignore suspendA2DP if active device is HearingAid");
+            return false;
+        }
+
         Log.d(TAG," suspendA2DP currPlayingState = "+ a2dpState + " for reason " + reason
               + "mA2dpSuspendTriggered = " + mA2dpSuspendTriggered + " for device " + device);
         if (mA2dpSuspendTriggered != A2DP_SUSPENDED_NOT_TRIGGERED) {