Added compatibility support for MessagingStyle
Change-Id: Ide2eba4efb261b9f010633500364822ac65fbb34
Fixes: 29574622
diff --git a/compat/java/android/support/v4/app/NotificationCompat.java b/compat/java/android/support/v4/app/NotificationCompat.java
index 009b864..cb02f41 100644
--- a/compat/java/android/support/v4/app/NotificationCompat.java
+++ b/compat/java/android/support/v4/app/NotificationCompat.java
@@ -525,7 +525,7 @@
public Notification build(Builder b, BuilderExtender extender) {
Notification result = b.mNotification;
result = NotificationCompatBase.add(result, b.mContext,
- b.mContentTitle, b.mContentText, b.mContentIntent, b.mFullScreenIntent);
+ b.resolveTitle(), b.resolveText(), b.mContentIntent, b.mFullScreenIntent);
// translate high priority requests into legacy flag
if (b.mPriority > PRIORITY_DEFAULT) {
result.flags |= FLAG_HIGH_PRIORITY;
@@ -604,7 +604,7 @@
@Override
public Notification build(Builder b, BuilderExtender extender) {
Notification notification = NotificationCompatHoneycomb.add(b.mContext, b.mNotification,
- b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView,
+ b.resolveTitle(), b.resolveText(), b.mContentInfo, b.mTickerView,
b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon);
if (b.mContentView != null) {
notification.contentView = b.mContentView;
@@ -617,9 +617,9 @@
@Override
public Notification build(Builder b, BuilderExtender extender) {
NotificationCompatIceCreamSandwich.Builder builder =
- new NotificationCompatIceCreamSandwich.Builder(
- b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
- b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
+ new NotificationCompatIceCreamSandwich.Builder(b.mContext, b.mNotification,
+ b.resolveTitle(), b.resolveText(), b.mContentInfo, b.mTickerView,
+ b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
b.mProgressMax, b.mProgress, b.mProgressIndeterminate);
return extender.build(b, builder);
}
@@ -629,7 +629,7 @@
@Override
public Notification build(Builder b, BuilderExtender extender) {
NotificationCompatJellybean.Builder builder = new NotificationCompatJellybean.Builder(
- b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
+ b.mContext, b.mNotification, b.resolveTitle(), b.resolveText(), b.mContentInfo,
b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
b.mProgressMax, b.mProgress, b.mProgressIndeterminate,
b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mExtras,
@@ -638,7 +638,10 @@
addStyleToBuilderJellybean(builder, b.mStyle);
Notification notification = extender.build(b, builder);
if (b.mStyle != null) {
- b.mStyle.addCompatExtras(getExtras(notification));
+ Bundle extras = getExtras(notification);
+ if (extras != null) {
+ b.mStyle.addCompatExtras(extras);
+ }
}
return notification;
}
@@ -697,7 +700,7 @@
@Override
public Notification build(Builder b, BuilderExtender extender) {
NotificationCompatKitKat.Builder builder = new NotificationCompatKitKat.Builder(
- b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
+ b.mContext, b.mNotification, b.resolveTitle(), b.resolveText(), b.mContentInfo,
b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
b.mProgressMax, b.mProgress, b.mProgressIndeterminate, b.mShowWhen,
b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly,
@@ -749,7 +752,7 @@
@Override
public Notification build(Builder b, BuilderExtender extender) {
NotificationCompatApi20.Builder builder = new NotificationCompatApi20.Builder(
- b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
+ b.mContext, b.mNotification, b.resolveTitle(), b.resolveText(), b.mContentInfo,
b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
b.mProgressMax, b.mProgress, b.mProgressIndeterminate, b.mShowWhen,
b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mPeople, b.mExtras,
@@ -807,7 +810,7 @@
@Override
public Notification build(Builder b, BuilderExtender extender) {
NotificationCompatApi21.Builder builder = new NotificationCompatApi21.Builder(
- b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
+ b.mContext, b.mNotification, b.resolveTitle(), b.resolveText(), b.mContentInfo,
b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
b.mProgressMax, b.mProgress, b.mProgressIndeterminate, b.mShowWhen,
b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mCategory,
@@ -897,8 +900,6 @@
bigPictureStyle.mPicture,
bigPictureStyle.mBigLargeIcon,
bigPictureStyle.mBigLargeIconSet);
- } else if (style instanceof MessagingStyle) {
- // TODO implement BigText fallback
}
}
}
@@ -1788,6 +1789,25 @@
public int getColor() {
return mColor;
}
+
+
+ /**
+ * @return the text of the notification
+ *
+ * @hide
+ */
+ protected CharSequence resolveText() {
+ return mContentText;
+ }
+
+ /**
+ * @return the title of the notification
+ *
+ * @hide
+ */
+ protected CharSequence resolveTitle() {
+ return mContentTitle;
+ }
}
/**
@@ -1967,8 +1987,9 @@
* messages of varying types between any number of people.
*
* <br>
- * If the platform does not provide large-format notifications, this method has no effect. The
- * user will always see the normal notification view.
+ * In order to get a backwards compatible behavior, the app needs to use the v7 version of the
+ * notification builder together with this style, otherwise the user will see the normal
+ * notification view.
* <br>
* This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like
* so:
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompat.java b/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
index 6215d46..278493e 100644
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
+++ b/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
@@ -19,12 +19,21 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
import android.os.Build;
import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.text.BidiFormatter;
import android.support.v7.appcompat.R;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
import android.widget.RemoteViews;
+import java.util.List;
+
/**
* An extension of {@link android.support.v4.app.NotificationCompat} which supports
* {@link android.support.v7.app.NotificationCompat.MediaStyle},
@@ -40,7 +49,7 @@
NotificationCompatImpl24.addDecoratedCustomViewStyle(builder);
} else if (b.mStyle instanceof DecoratedMediaCustomViewStyle) {
NotificationCompatImpl24.addDecoratedMediaCustomViewStyle(builder);
- } else {
+ } else if (!(b.mStyle instanceof MessagingStyle)) {
addStyleGetContentViewLollipop(builder, b);
}
}
@@ -76,12 +85,107 @@
setBackgroundColor(b.mContext, contentViewMedia, b.getColor());
return contentViewMedia;
}
+ return null;
} else if (b.mStyle instanceof DecoratedCustomViewStyle) {
return getDecoratedContentView(b);
}
+ return addStyleGetContentViewJellybean(builder, b);
+ }
+
+ private static RemoteViews addStyleGetContentViewJellybean(
+ NotificationBuilderWithBuilderAccessor builder,
+ android.support.v4.app.NotificationCompat.Builder b) {
+ if (b.mStyle instanceof MessagingStyle) {
+ addMessagingFallBackStyle((MessagingStyle) b.mStyle, builder, b);
+ }
+ return addStyleGetContentViewIcs(builder, b);
+ }
+
+ private static MessagingStyle.Message findLatestIncomingMessage(MessagingStyle style) {
+ List<MessagingStyle.Message> messages = style.getMessages();
+ for (int i = messages.size() - 1; i >= 0; i--) {
+ MessagingStyle.Message m = messages.get(i);
+ // Incoming messages have a non-empty sender.
+ if (!TextUtils.isEmpty(m.getSender())) {
+ return m;
+ }
+ }
+ if (!messages.isEmpty()) {
+ // No incoming messages, fall back to outgoing message
+ return messages.get(messages.size() - 1);
+ }
return null;
}
+ private static CharSequence makeMessageLine(android.support.v4.app.NotificationCompat.Builder b,
+ MessagingStyle style,
+ MessagingStyle.Message m) {
+ BidiFormatter bidi = BidiFormatter.getInstance();
+ SpannableStringBuilder sb = new SpannableStringBuilder();
+ boolean afterLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+ int color = afterLollipop || Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1
+ ? Color.BLACK : Color.WHITE;
+ CharSequence replyName = m.getSender();
+ if (TextUtils.isEmpty(m.getSender())) {
+ replyName = style.getUserDisplayName() == null
+ ? "" : style.getUserDisplayName();
+ color = afterLollipop && b.getColor() != NotificationCompat.COLOR_DEFAULT
+ ? b.getColor()
+ : color;
+ }
+ CharSequence senderText = bidiWrapIfNotSpanned(bidi, replyName);
+ sb.append(senderText);
+ sb.setSpan(makeFontColorSpan(color),
+ sb.length() - senderText.length(),
+ sb.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* flags */);
+ CharSequence text = m.getText() == null ? "" : m.getText();
+ sb.append(" ").append(bidiWrapIfNotSpanned(bidi, text));
+ return sb;
+ }
+
+ private static CharSequence bidiWrapIfNotSpanned(BidiFormatter bidi, CharSequence replyName) {
+ // Unfortunately bidiFormatter doesn't support CharSequences in support
+ if (replyName instanceof Spanned) {
+ return replyName;
+ }
+ return bidi.unicodeWrap(replyName.toString());
+ }
+
+ private static TextAppearanceSpan makeFontColorSpan(int color) {
+ return new TextAppearanceSpan(null, 0, 0, ColorStateList.valueOf(color), null);
+ }
+
+ private static void addMessagingFallBackStyle(MessagingStyle style,
+ NotificationBuilderWithBuilderAccessor builder,
+ android.support.v4.app.NotificationCompat.Builder b) {
+ SpannableStringBuilder completeMessage = new SpannableStringBuilder();
+ List<MessagingStyle.Message> messages = style.getMessages();
+ boolean showNames = style.getConversationTitle() != null
+ || hasMessagesWithoutSender(style.getMessages());
+ for (int i = messages.size() - 1; i >= 0; i--) {
+ MessagingStyle.Message m = messages.get(i);
+ CharSequence line;
+ line = showNames ? makeMessageLine(b, style, m) : m.getText();
+ if (i != messages.size() - 1) {
+ completeMessage.insert(0, "\n");
+ }
+ completeMessage.insert(0, line);
+ }
+ NotificationCompatImplJellybean.addBigTextStyle(builder, completeMessage);
+ }
+
+ private static boolean hasMessagesWithoutSender(
+ List<MessagingStyle.Message> messages) {
+ for (int i = messages.size() - 1; i >= 0; i--) {
+ MessagingStyle.Message m = messages.get(i);
+ if (m.getSender() == null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static RemoteViews addStyleGetContentViewIcs(
NotificationBuilderWithBuilderAccessor builder,
android.support.v4.app.NotificationCompat.Builder b) {
@@ -238,6 +342,43 @@
}
/**
+ * @return the text of the notification
+ *
+ * @hide
+ */
+ @Override
+ protected CharSequence resolveText() {
+ if (mStyle instanceof MessagingStyle) {
+ MessagingStyle style = (MessagingStyle) mStyle;
+ MessagingStyle.Message m = findLatestIncomingMessage(style);
+ CharSequence conversationTitle = style.getConversationTitle();
+ if (m != null) {
+ return conversationTitle != null ? makeMessageLine(this, style, m)
+ : m.getText();
+ }
+ }
+ return super.resolveText();
+ }
+
+ /**
+ * @return the title of the notification
+ *
+ * @hide
+ */
+ @Override
+ protected CharSequence resolveTitle() {
+ if (mStyle instanceof MessagingStyle) {
+ MessagingStyle style = (MessagingStyle) mStyle;
+ MessagingStyle.Message m = findLatestIncomingMessage(style);
+ CharSequence conversationTitle = style.getConversationTitle();
+ if (conversationTitle != null || m != null) {
+ return conversationTitle != null ? conversationTitle : m.getSender();
+ }
+ }
+ return super.resolveTitle();
+ }
+
+ /**
* @hide
*/
@Override
@@ -279,7 +420,7 @@
@Override
public Notification build(android.support.v4.app.NotificationCompat.Builder b,
NotificationBuilderWithBuilderAccessor builder) {
- RemoteViews contentView = addStyleGetContentViewIcs(builder, b);
+ RemoteViews contentView = addStyleGetContentViewJellybean(builder, b);
Notification n = builder.build();
// The above call might override decorated content views again, let's make sure it
// sticks.
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompatImplJellybean.java b/v7/appcompat/src/android/support/v7/app/NotificationCompatImplJellybean.java
new file mode 100644
index 0000000..cf8d128
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/app/NotificationCompatImplJellybean.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.v7.app;
+
+import android.app.Notification;
+import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
+
+class NotificationCompatImplJellybean {
+
+ public static void addBigTextStyle(NotificationBuilderWithBuilderAccessor b,
+ CharSequence bigText) {
+ Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle(b.getBuilder());
+ bigTextStyle.bigText(bigText);
+ }
+}