Merge QQ3A.200605.002 into master
Bug: 158095402
Merged-In: Ia015d92a7c4855b41c068e9c1e8d623dd5665a40
Change-Id: I92439e5e4d5481775b719554e70f65b9e293dfba
diff --git a/Android.bp b/Android.bp
index 12b0f47..671cc6c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -31,8 +31,11 @@
privileged: true,
+ libs: ["android.car"],
+
static_libs: [
"car-apps-common",
+ "car-messenger-common",
"car-telephony-common",
"androidx.annotation_annotation",
"glide-prebuilt",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 71ffe31..3460deb 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -28,6 +28,7 @@
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.WRITE_SMS"/>
+ <uses-permission android:name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"/>
<application android:label="@string/app_name">
<service android:name=".MessengerService"
diff --git a/src/com/android/car/messenger/MapMessage.java b/src/com/android/car/messenger/MapMessage.java
index b4b7aee..95c932d 100644
--- a/src/com/android/car/messenger/MapMessage.java
+++ b/src/com/android/car/messenger/MapMessage.java
@@ -19,6 +19,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothMapClient;
import android.content.Intent;
+import com.android.car.messenger.log.L;
import androidx.annotation.Nullable;
@@ -26,6 +27,7 @@
* Represents a message obtained via MAP service from a connected Bluetooth device.
*/
class MapMessage {
+ private static final String TAG = "CM.MapMessage";
private String mDeviceAddress;
private String mHandle;
private String mSenderName;
@@ -34,18 +36,25 @@
private String mMessageText;
private long mReceiveTime;
private boolean mIsReadOnPhone;
- private boolean mIsReadOnCar;
+ private boolean mShouldInclude;
/**
* Constructs a {@link MapMessage} from {@code intent} that was received from MAP service via
* {@link BluetoothMapClient#ACTION_MESSAGE_RECEIVED} broadcast.
*
* @param intent intent received from MAP service
- * @return message constructed from extras in {@code intent}
+ * @return message constructed from extras in {@code intent}, or null if this is a group
+ * conversation.
* @throws NullPointerException if {@code intent} is missing the device extra
* @throws IllegalArgumentException if {@code intent} is missing any other required extras
*/
+ @Nullable
public static MapMessage parseFrom(Intent intent) {
+ if (intent.getStringArrayExtra(Intent.EXTRA_CC) != null
+ && intent.getStringArrayExtra(Intent.EXTRA_CC).length > 0) {
+ L.i(TAG, "Skipping group conversation message");
+ return null;
+ }
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String handle = intent.getStringExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE);
String senderUri = intent.getStringExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_URI);
@@ -101,6 +110,7 @@
mSenderName = senderName;
mReceiveTime = receiveTime;
mIsReadOnPhone = isRead;
+ mShouldInclude = true;
}
/**
@@ -151,8 +161,13 @@
return mMessageText;
}
- public void markMessageAsRead() {
- mIsReadOnCar = true;
+ /**
+ * Sets the message to be excluded from the notification. Messages that have been read aloud on
+ * the car, or that have been dismissed by the user should be excluded from the notification if/
+ * when the notification gets updated. Note: this state will not be propagated to the phone.
+ */
+ public void excludeFromNotification() {
+ mShouldInclude = false;
}
/**
@@ -163,10 +178,12 @@
}
/**
- * Returns {@code true} if message was read on the car.
+ * Returns {@code true} if message should be included in the notification. Messages that
+ * have been read aloud on the car, or that have been dismissed by the user should be excluded
+ * from the notification if/when the notification gets updated.
*/
- public boolean isReadOnCar() {
- return mIsReadOnCar;
+ public boolean shouldIncludeInNotification() {
+ return mShouldInclude;
}
@Override
@@ -179,7 +196,7 @@
", mSenderName='" + mSenderName + '\'' +
", mReceiveTime=" + mReceiveTime + '\'' +
", mIsReadOnPhone= " + mIsReadOnPhone + '\'' +
- ", mIsReadOnCar= " + mIsReadOnCar +
+ ", mShouldInclude= " + mShouldInclude +
"}";
}
}
diff --git a/src/com/android/car/messenger/MessengerDelegate.java b/src/com/android/car/messenger/MessengerDelegate.java
index 58193d4..7f864b6 100644
--- a/src/com/android/car/messenger/MessengerDelegate.java
+++ b/src/com/android/car/messenger/MessengerDelegate.java
@@ -14,7 +14,6 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
-import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -28,6 +27,7 @@
import com.android.car.apps.common.LetterTileDrawable;
import com.android.car.messenger.bluetooth.BluetoothHelper;
import com.android.car.messenger.bluetooth.BluetoothMonitor;
+import com.android.car.messenger.common.ProjectionStateListener;
import com.android.car.messenger.log.L;
import com.android.car.telephony.common.TelecomUtils;
import com.android.internal.annotations.GuardedBy;
@@ -68,9 +68,15 @@
final Map<String, Long> mBTDeviceAddressToConnectionTimestamp = new HashMap<>();
final Map<SenderKey, Bitmap> mSenderToLargeIconBitmap = new HashMap<>();
+ /** Tracks whether a projection application is active in the foreground. **/
+ private ProjectionStateListener mProjectionStateListener;
+
public MessengerDelegate(Context context) {
mContext = context;
+ mProjectionStateListener = new ProjectionStateListener(context);
+ mProjectionStateListener.start();
+
mNotificationManager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mSmsDatabaseHandler = new SmsDatabaseHandler(mContext);
@@ -94,6 +100,7 @@
public void onMessageReceived(Intent intent) {
try {
MapMessage message = MapMessage.parseFrom(intent);
+ if (message == null) return;
L.d(TAG, "Received message from " + message.getDeviceAddress());
MessageKey messageKey = new MessageKey(message);
@@ -210,12 +217,17 @@
}
}
- protected void markAsRead(SenderKey senderKey) {
+
+ /**
+ * Excludes messages from a notification so that the messages are not shown to the user once
+ * the notification gets updated with newer messages.
+ */
+ protected void excludeFromNotification(SenderKey senderKey) {
NotificationInfo info = mNotificationInfos.get(senderKey);
for (MessageKey key : info.mMessageKeys) {
MapMessage message = mMessages.get(key);
- if (!message.isReadOnCar()) {
- message.markMessageAsRead();
+ if (message.shouldIncludeInNotification()) {
+ message.excludeFromNotification();
mSmsDatabaseHandler.addOrUpdate(message);
}
}
@@ -227,6 +239,7 @@
if (mPhoneNumberInfoFuture != null) {
mPhoneNumberInfoFuture.cancel(true);
}
+ mProjectionStateListener.stop();
}
/**
@@ -239,6 +252,7 @@
if (predicate.test(senderKey)) {
mNotificationManager.cancel(notificationInfo.mNotificationId);
}
+ excludeFromNotification(senderKey);
});
}
@@ -249,11 +263,11 @@
mSmsDatabaseHandler.removeMessagesForDevice(key.getDeviceAddress());
}
}
- mMessages.entrySet().removeIf(
- messageKeyMapMessageEntry -> predicate.test(messageKeyMapMessageEntry.getKey()));
clearNotifications(predicate);
mNotificationInfos.entrySet().removeIf(entry -> predicate.test(entry.getKey()));
mSenderToLargeIconBitmap.entrySet().removeIf(entry -> predicate.test(entry.getKey()));
+ mMessages.entrySet().removeIf(
+ messageKeyMapMessageEntry -> predicate.test(messageKeyMapMessageEntry.getKey()));
}
private void updateNotification(MessageKey messageKey, MapMessage mapMessage) {
@@ -361,17 +375,26 @@
.setUri(notificationInfo.mSenderContactUri)
.build();
notificationInfo.mMessageKeys.stream().map(mMessages::get).forEachOrdered(message -> {
- if (!message.isReadOnCar()) {
+ if (message.shouldIncludeInNotification()) {
messagingStyle.addMessage(
message.getMessageText(),
message.getReceiveTime(),
sender);
+ } else {
+ L.d(TAG, "excluding message received at: " + message.getReceiveTime()
+ + " from notification.");
}
});
- NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext,
- MessengerService.SMS_CHANNEL_ID)
- .setContentTitle(senderName)
+ NotificationCompat.Builder builder;
+ if (mProjectionStateListener.isProjectionInActiveForeground(senderKey.getDeviceAddress())) {
+ builder = new NotificationCompat.Builder(mContext,
+ MessengerService.SILENT_SMS_CHANNEL_ID);
+ } else {
+ builder = new NotificationCompat.Builder(mContext, MessengerService.SMS_CHANNEL_ID);
+ }
+
+ builder.setContentTitle(senderName)
.setContentText(contentText)
.setStyle(messagingStyle)
.setCategory(Notification.CATEGORY_MESSAGE)
diff --git a/src/com/android/car/messenger/MessengerService.java b/src/com/android/car/messenger/MessengerService.java
index 0b0e4e4..2f71de5 100644
--- a/src/com/android/car/messenger/MessengerService.java
+++ b/src/com/android/car/messenger/MessengerService.java
@@ -59,6 +59,7 @@
/* NOTIFICATIONS */
static final String SMS_CHANNEL_ID = "SMS_CHANNEL_ID";
+ static final String SILENT_SMS_CHANNEL_ID = "SILENT_SMS_CHANNEL_ID";
private static final String APP_RUNNING_CHANNEL_ID = "APP_RUNNING_CHANNEL_ID";
private static final int SERVICE_STARTED_NOTIFICATION_ID = Integer.MAX_VALUE;
@@ -111,6 +112,16 @@
notificationManager.createNotificationChannel(appRunningNotificationChannel);
}
+ // Create notification channel for notifications that should be posted silently in the
+ // notification center, without a heads up notification.
+ {
+ NotificationChannel silentNotificationChannel =
+ new NotificationChannel(SILENT_SMS_CHANNEL_ID,
+ getString(R.string.sms_channel_description),
+ NotificationManager.IMPORTANCE_LOW);
+ notificationManager.createNotificationChannel(silentNotificationChannel);
+ }
+
{
AudioAttributes attributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
@@ -243,7 +254,7 @@
public void markAsRead(Intent intent) {
final SenderKey senderKey = intent.getParcelableExtra(EXTRA_SENDER_KEY);
L.d(TAG, "markAsRead");
- mMessengerDelegate.markAsRead(senderKey);
+ mMessengerDelegate.excludeFromNotification(senderKey);
}
/**
diff --git a/src/com/android/car/messenger/SmsDatabaseHandler.java b/src/com/android/car/messenger/SmsDatabaseHandler.java
index 77d8e29..a8fd107 100644
--- a/src/com/android/car/messenger/SmsDatabaseHandler.java
+++ b/src/com/android/car/messenger/SmsDatabaseHandler.java
@@ -168,7 +168,8 @@
newMessage.put(Telephony.Sms.PERSON,
getContactId(mContentResolver,
message.getSenderContactUri()));
- newMessage.put(Telephony.Sms.READ, (message.isReadOnPhone() || message.isReadOnCar()));
+ newMessage.put(Telephony.Sms.READ, (message.isReadOnPhone()
+ || !message.shouldIncludeInNotification()));
return newMessage;
}
diff --git a/tests/robotests/src/com/android/car/messenger/MessengerDelegateTest.java b/tests/robotests/src/com/android/car/messenger/MessengerDelegateTest.java
index 2f9b182..307624b 100644
--- a/tests/robotests/src/com/android/car/messenger/MessengerDelegateTest.java
+++ b/tests/robotests/src/com/android/car/messenger/MessengerDelegateTest.java
@@ -189,12 +189,12 @@
public void testHandleMarkAsRead() {
mMessengerDelegate.onMessageReceived(mMessageOneIntent);
- mMessengerDelegate.markAsRead(mSenderKey);
+ mMessengerDelegate.excludeFromNotification(mSenderKey);
MessengerDelegate.NotificationInfo info = mMessengerDelegate.mNotificationInfos.get(
mSenderKey);
MessengerDelegate.MessageKey key = info.mMessageKeys.get(0);
- assertThat(mMessengerDelegate.mMessages.get(key).isReadOnCar()).isTrue();
+ assertThat(mMessengerDelegate.mMessages.get(key).shouldIncludeInNotification()).isFalse();
}
@Test
@@ -208,7 +208,7 @@
MessengerDelegate.NotificationInfo info = mMessengerDelegate.mNotificationInfos.get(
mSenderKey);
MessengerDelegate.MessageKey key = info.mMessageKeys.get(0);
- assertThat(mMessengerDelegate.mMessages.get(key).isReadOnCar()).isFalse();
+ assertThat(mMessengerDelegate.mMessages.get(key).shouldIncludeInNotification()).isTrue();
assertThat(mMessengerDelegate.mMessages.get(key).isReadOnPhone()).isTrue();
}