Fixing iPhone SMS handling

iPhones don't return senderContactUri (encoded phone #) via MAP (and don't
support sending messages over MAP either). To address this:
- Updated MapMessageMonitor to use combination of senderName/senderContactUri
  in SenderKey. Auto-reply logic aborts if senderContactUri is not known.
- Also updated MapMessage docs.
- I am independently waiting on b/36368127 which is to provide a MAP API
  indicating if MAP device supports send. Once that's addressed we will
  no longer provide auto-reply as an option for iPhone like devices.

Bug: 33280056
Test: Receiving and send auto-reply with iPhone and Android (Pixel,
      Samsung)

Change-Id: If4887db8cad721b5aeae033253599e1de2b8816c
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5d0391a..d59587d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -19,9 +19,10 @@
 
     <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="25"/>
 
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
     <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
 
     <application android:label="CarMessenger">
         <service android:name=".MessengerService" android:exported="false">
diff --git a/src/com/android/car/messenger/MapMessage.java b/src/com/android/car/messenger/MapMessage.java
index 1369976..2bca390 100644
--- a/src/com/android/car/messenger/MapMessage.java
+++ b/src/com/android/car/messenger/MapMessage.java
@@ -28,11 +28,10 @@
     private BluetoothDevice mDevice;
     private String mHandle;
     private long mReceivedTimeMs;
-    private String mText;
+    private String mSenderName;
     @Nullable
     private String mSenderContactUri;
-    @Nullable
-    private String mSenderName;
+    private String mText;
 
     /**
      * Constructs Message from {@code intent} that was received from MAP service via
@@ -50,18 +49,19 @@
         String senderContactName = intent.getStringExtra(
                 BluetoothMapClient.EXTRA_SENDER_CONTACT_NAME);
         String text = intent.getStringExtra(android.content.Intent.EXTRA_TEXT);
-        return new MapMessage(device, handle, System.currentTimeMillis(), text,
-                senderContactUri, senderContactName);
+        return new MapMessage(device, handle, System.currentTimeMillis(), senderContactName,
+                senderContactUri, text);
     }
 
     private MapMessage(BluetoothDevice device,
             String handle,
             long receivedTimeMs,
-            String text,
+            String senderName,
             @Nullable String senderContactUri,
-            @Nullable String senderName) {
+            String text) {
         boolean missingDevice = (device == null);
         boolean missingHandle = (handle == null);
+        boolean missingSenderName = (senderName == null);
         boolean missingText = (text == null);
         if (missingDevice || missingHandle || missingText) {
             StringBuilder builder = new StringBuilder("Missing required fields:");
@@ -71,6 +71,9 @@
             if (missingHandle) {
                 builder.append(" handle");
             }
+            if (missingSenderName) {
+                builder.append(" senderName");
+            }
             if (missingText) {
                 builder.append(" text");
             }
@@ -88,26 +91,43 @@
         return mDevice;
     }
 
+    /**
+     * @return Unique handle for this message. NOTE: The handle is only required to be unique for
+     *      the lifetime of a single MAP session.
+     */
     public String getHandle() {
         return mHandle;
     }
 
+    /**
+     * @return Milliseconds since epoch at which this message notification was received on the head-
+     *      unit.
+     */
     public long getReceivedTimeMs() {
         return mReceivedTimeMs;
     }
 
-    public String getText() {
-        return mText;
+    /**
+     * @return Contact name as obtained from the device. If contact is in the device's address-book,
+     *       this is typically the contact name. Otherwise it will be the phone number.
+     */
+    public String getSenderName() {
+        return mSenderName;
     }
 
+    /**
+     * @return Sender phone number available as a URI string. iPhone's don't provide these.
+     */
     @Nullable
     public String getSenderContactUri() {
         return mSenderContactUri;
     }
 
-    @Nullable
-    public String getSenderName() {
-        return mSenderName;
+    /**
+     * @return Actual content of the message.
+     */
+    public String getText() {
+        return mText;
     }
 
     @Override
diff --git a/src/com/android/car/messenger/MapMessageMonitor.java b/src/com/android/car/messenger/MapMessageMonitor.java
index 0de9d1e..926579d 100644
--- a/src/com/android/car/messenger/MapMessageMonitor.java
+++ b/src/com/android/car/messenger/MapMessageMonitor.java
@@ -28,6 +28,7 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.annotation.Nullable;
 import android.support.v4.app.NotificationCompat;
 import android.util.Log;
 import android.widget.Toast;
@@ -87,12 +88,12 @@
         }
     }
 
-    // TODO(sriniv): Handle unknown senders. b/33280056
     private void updateNotificationInfo(MapMessage message, MessageKey messageKey) {
         SenderKey senderKey = new SenderKey(message);
         NotificationInfo notificationInfo = mNotificationInfos.get(senderKey);
         if (notificationInfo == null) {
-            notificationInfo = new NotificationInfo(message.getSenderName());
+            notificationInfo =
+                    new NotificationInfo(message.getSenderName(), message.getSenderContactUri());
             mNotificationInfos.put(senderKey, notificationInfo);
         }
         notificationInfo.mMessageKeys.add(messageKey);
@@ -192,7 +193,16 @@
         }
         BluetoothDevice device =
                 BluetoothAdapter.getDefaultAdapter().getRemoteDevice(senderKey.mDeviceAddress);
-        Uri recipientUris[] = { Uri.parse(senderKey.mSubKey) };
+        NotificationInfo notificationInfo = mNotificationInfos.get(senderKey);
+        if (notificationInfo == null) {
+            Log.w(TAG, "No notificationInfo found for senderKey: " + senderKey);
+            return false;
+        }
+        if (notificationInfo.mSenderContactUri == null) {
+            Log.w(TAG, "Do not have contact URI for sender!");
+            return false;
+        }
+        Uri recipientUris[] = { Uri.parse(notificationInfo.mSenderContactUri) };
 
         final int requestCode = senderKey.hashCode();
         PendingIntent sentIntent =
@@ -264,7 +274,9 @@
         }
     }
 
-    // Key used in HashMap that is composed from a BT device-address and device-specific "sub key"
+    /**
+     * Key used in HashMap that is composed from a BT device-address and device-specific "sub key"
+     */
     private abstract static class CompositeKey {
         final String mDeviceAddress;
         final String mSubKey;
@@ -304,22 +316,32 @@
         }
     }
 
-    // CompositeKey used to identify specific messages; it uses message-handle as the secondary key.
+    /**
+     * {@link CompositeKey} subclass used to identify specific messages; it uses message-handle as
+     * the secondary key.
+     */
     private static class MessageKey extends CompositeKey {
         MessageKey(MapMessage message) {
             super(message.getDevice().getAddress(), message.getHandle());
         }
     }
 
-    // CompositeKey used to identify Notification info for a sender. It uses senderContactUri as
-    // the secondary key.
+    /**
+     * CompositeKey used to identify Notification info for a sender; it uses a combination of
+     * senderContactUri and senderContactName as the secondary key.
+     */
     static class SenderKey extends CompositeKey implements Parcelable {
         private SenderKey(String deviceAddress, String key) {
             super(deviceAddress, key);
         }
 
         SenderKey(MapMessage message) {
-            this(message.getDevice().getAddress(), message.getSenderContactUri());
+            // Use a combination of senderName and senderContactUri for key. Ideally we would use
+            // only senderContactUri (which is encoded phone no.). However since some phones don't
+            // provide these, we fall back to senderName. Since senderName may not be unique, we
+            // include senderContactUri also to provide uniqueness in cases it is available.
+            this(message.getDevice().getAddress(),
+                    message.getSenderName() + "/" + message.getSenderContactUri());
         }
 
         @Override
@@ -347,16 +369,21 @@
         };
     }
 
-    // Information about a single notification displayed.
+    /**
+     * Information about a single notification that is displayed.
+     */
     private static class NotificationInfo {
         private static int NEXT_NOTIFICATION_ID = 0;
 
         final int mNotificationId = NEXT_NOTIFICATION_ID++;
         final String mSenderName;
+        @Nullable
+        final String mSenderContactUri;
         final List<MessageKey> mMessageKeys = new LinkedList<>();
 
-        NotificationInfo(String senderName) {
+        NotificationInfo(String senderName, @Nullable String senderContactUri) {
             mSenderName = senderName;
+            mSenderContactUri = senderContactUri;
         }
     }
 }
diff --git a/src/com/android/car/messenger/MessengerService.java b/src/com/android/car/messenger/MessengerService.java
index 963d17d..c2de6aa 100644
--- a/src/com/android/car/messenger/MessengerService.java
+++ b/src/com/android/car/messenger/MessengerService.java
@@ -111,14 +111,15 @@
         }
         switch (intent.getAction()) {
             case ACTION_AUTO_REPLY:
-                boolean failed = true;
+                boolean success;
                 if (mMapClient != null) {
-                    failed = mMessageMonitor.sendAutoReply(
+                    success = mMessageMonitor.sendAutoReply(
                             intent.getParcelableExtra(EXTRA_SENDER_KEY), mMapClient);
                 } else {
                     Log.e(TAG, "Unable to send reply; MAP profile disconnected!");
+                    success = false;
                 }
-                if (failed) {
+                if (!success) {
                     Toast.makeText(this, R.string.auto_reply_failed_message, Toast.LENGTH_SHORT)
                             .show();
                 }