Synchronize get/set of BluetoothMapClient

Change-Id: Ib8386c290c961bb0081c74801838bc2f34586a31
Fix: 135629708
Test: manual
diff --git a/res/values/config.xml b/res/values/config.xml
index 56cb667..0696f07 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -18,4 +18,7 @@
     <!-- Whether existing messages should be loaded. Recommended to turn off if head-unit's and
      BT-paired phone's clocks are not synced.-->
     <bool name="config_loadExistingMessages">false</bool>
+    <!-- Whether app should attempt to reconnect to Bluetooth MAP profile, once MAP is
+    disconnected. -->
+    <bool name="config_reconnectToMap">true</bool>
 </resources>
diff --git a/src/com/android/car/messenger/MessengerDelegate.java b/src/com/android/car/messenger/MessengerDelegate.java
index 6328905..643a58d 100644
--- a/src/com/android/car/messenger/MessengerDelegate.java
+++ b/src/com/android/car/messenger/MessengerDelegate.java
@@ -7,6 +7,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothMapClient;
+import android.bluetooth.BluetoothProfile;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
@@ -34,6 +35,7 @@
 import com.android.car.messenger.bluetooth.BluetoothHelper;
 import com.android.car.messenger.bluetooth.BluetoothMonitor;
 import com.android.car.messenger.log.L;
+import com.android.internal.annotations.GuardedBy;
 
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.request.RequestOptions;
@@ -53,8 +55,10 @@
     private static final String TAG = "CM.MessengerDelegate";
     // Static user name for building a MessagingStyle.
     private static final String STATIC_USER_NAME = "STATIC_USER_NAME";
+    private static final Object mMapClientLock = new Object();
 
     private final Context mContext;
+    @GuardedBy("mMapClientLock")
     private BluetoothMapClient mBluetoothMapClient;
     private NotificationManager mNotificationManager;
     private final SmsDatabaseHandler mSmsDatabaseHandler;
@@ -112,12 +116,14 @@
     public void onDeviceConnected(BluetoothDevice device) {
         L.d(TAG, "Device connected: \t%s", device.getAddress());
         mBTDeviceAddressToConnectionTimestamp.put(device.getAddress(), System.currentTimeMillis());
-        if (mBluetoothMapClient != null && mShouldLoadExistingMessages) {
-            mBluetoothMapClient.getUnreadMessages(device);
-        } else {
-            // onDeviceConnected should be sent by BluetoothMapClient, so log if we run into this
-            // strange case.
-            L.e(TAG, "BluetoothMapClient is null after connecting to device.");
+        synchronized (mMapClientLock) {
+            if (mBluetoothMapClient != null && mShouldLoadExistingMessages) {
+                mBluetoothMapClient.getUnreadMessages(device);
+            } else {
+                // onDeviceConnected should be sent by BluetoothMapClient, so log if we run into
+                // this strange case.
+                L.e(TAG, "BluetoothMapClient is null after connecting to device.");
+            }
         }
     }
 
@@ -131,24 +137,36 @@
 
     @Override
     public void onMapConnected(BluetoothMapClient client) {
-        if (mBluetoothMapClient == client) {
-            return;
-        }
+        List<BluetoothDevice> connectedDevices;
+        synchronized (mMapClientLock) {
+            if (mBluetoothMapClient == client) {
+                return;
+            }
 
-        if (mBluetoothMapClient != null) {
-            mBluetoothMapClient.close();
-        }
+            if (mBluetoothMapClient != null) {
+                mBluetoothMapClient.close();
+            }
 
-        mBluetoothMapClient = client;
-        for (BluetoothDevice device : client.getConnectedDevices()) {
-            onDeviceConnected(device);
+            mBluetoothMapClient = client;
+            connectedDevices = mBluetoothMapClient.getConnectedDevices();
+        }
+        if (connectedDevices != null) {
+            for (BluetoothDevice device : connectedDevices) {
+                onDeviceConnected(device);
+            }
         }
     }
 
     @Override
     public void onMapDisconnected(int profile) {
-        mBluetoothMapClient = null;
         cleanupMessagesAndNotifications(key -> true);
+        synchronized (mMapClientLock) {
+            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+            if (adapter != null) {
+                adapter.closeProfileProxy(BluetoothProfile.MAP_CLIENT, mBluetoothMapClient);
+            }
+            mBluetoothMapClient = null;
+        }
     }
 
     @Override
@@ -159,24 +177,27 @@
     protected void sendMessage(SenderKey senderKey, String messageText) {
         boolean success = false;
         // Even if the device is not connected, try anyway so that the reply in enqueued.
-        if (mBluetoothMapClient != null) {
-            NotificationInfo notificationInfo = mNotificationInfos.get(senderKey);
-            if (notificationInfo == null) {
-                L.w(TAG, "No notificationInfo found for senderKey: %s", senderKey);
-            } else if (notificationInfo.mSenderContactUri == null) {
-                L.w(TAG, "Do not have contact URI for sender!");
-            } else {
-                Uri recipientUris[] = {Uri.parse(notificationInfo.mSenderContactUri)};
+        synchronized (mMapClientLock) {
+            if (mBluetoothMapClient != null) {
+                NotificationInfo notificationInfo = mNotificationInfos.get(senderKey);
+                if (notificationInfo == null) {
+                    L.w(TAG, "No notificationInfo found for senderKey: %s", senderKey);
+                } else if (notificationInfo.mSenderContactUri == null) {
+                    L.w(TAG, "Do not have contact URI for sender!");
+                } else {
+                    Uri[] recipientUris = {Uri.parse(notificationInfo.mSenderContactUri)};
 
-                final int requestCode = senderKey.hashCode();
+                    final int requestCode = senderKey.hashCode();
 
-                Intent intent = new Intent(BluetoothMapClient.ACTION_MESSAGE_SENT_SUCCESSFULLY);
-                PendingIntent sentIntent = PendingIntent.getBroadcast(mContext, requestCode, intent,
-                        PendingIntent.FLAG_ONE_SHOT);
+                    Intent intent = new Intent(BluetoothMapClient.ACTION_MESSAGE_SENT_SUCCESSFULLY);
+                    PendingIntent sentIntent = PendingIntent.getBroadcast(mContext, requestCode,
+                            intent,
+                            PendingIntent.FLAG_ONE_SHOT);
 
-                success = BluetoothHelper.sendMessage(mBluetoothMapClient,
-                        senderKey.getDeviceAddress(), recipientUris, messageText,
-                        sentIntent, null);
+                    success = BluetoothHelper.sendMessage(mBluetoothMapClient,
+                            senderKey.getDeviceAddress(), recipientUris, messageText,
+                            sentIntent, null);
+                }
             }
         }
 
@@ -299,8 +320,10 @@
 
     protected void cleanup() {
         cleanupMessagesAndNotifications(key -> true);
-        if (mBluetoothMapClient != null) {
-            mBluetoothMapClient.close();
+        synchronized (mMapClientLock) {
+            if (mBluetoothMapClient != null) {
+                mBluetoothMapClient.close();
+            }
         }
     }
 
@@ -426,7 +449,10 @@
         }
         BluetoothDevice device = adapter.getRemoteDevice(deviceAddress);
 
-        return mBluetoothMapClient.isUploadingSupported(device);
+        synchronized (mMapClientLock) {
+            return (mBluetoothMapClient != null) && mBluetoothMapClient.isUploadingSupported(
+                    device);
+        }
     }
 
     /**
diff --git a/src/com/android/car/messenger/bluetooth/BluetoothMonitor.java b/src/com/android/car/messenger/bluetooth/BluetoothMonitor.java
index 7640c1c..0015ebc 100644
--- a/src/com/android/car/messenger/bluetooth/BluetoothMonitor.java
+++ b/src/com/android/car/messenger/bluetooth/BluetoothMonitor.java
@@ -9,11 +9,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources.NotFoundException;
 import android.os.Parcelable;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.car.messenger.R;
 import com.android.car.messenger.log.L;
 
 import java.util.HashSet;
@@ -153,6 +155,17 @@
 
     private void onMapDisconnected(int profile) {
         mListeners.forEach(listener -> listener.onMapDisconnected(profile));
+        boolean shouldReconnectToMap = false;
+        try {
+            shouldReconnectToMap = mContext.getResources().getBoolean(
+                    R.bool.config_loadExistingMessages);
+        } catch (NotFoundException e) {
+            // Should only happen for robolectric unit tests
+            L.e(TAG, e, "Could not find loadExistingMessages config");
+        }
+        if (shouldReconnectToMap) {
+            connectToMap();
+        }
     }
 
     private void onSdpRecord(BluetoothDevice device, boolean supportsReply) {