Fix type error with BigPictureStyle.bigLargeIcon()
From API 16-22, EXTRA_LARGE_ICON_BIG held a Bitmap. From API 23+,
it held an Icon. When I added compat builder recovery, my logic to
populate extras did not account for this, and effectively
corrupted the otherwise valid extras.
Fixes: 171955422
Test: NotificationCompatTest
Relnote: """Fix a bug where setting BigPictureStyle.bigLargeIcon
would break the BigPictureStyle for that notification on newer
OS versions."""
Change-Id: Ic623db52b536e3ad6839be5073431e1afc1f7fd4
diff --git a/core/core/proguard-rules.pro b/core/core/proguard-rules.pro
index 12588e6..4efb0d5 100644
--- a/core/core/proguard-rules.pro
+++ b/core/core/proguard-rules.pro
@@ -5,3 +5,6 @@
-keepclassmembernames,allowobfuscation,allowshrinking class androidx.core.view.WindowInsetsCompat$*Impl* {
<methods>;
}
+-keepclassmembernames,allowobfuscation,allowshrinking class androidx.core.app.NotificationCompat$*$Api*Impl {
+ <methods>;
+}
diff --git a/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java b/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
index 7a652ed..2c40e7c 100644
--- a/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
@@ -42,13 +42,16 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
+import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.Parcelable;
import android.support.v4.BaseInstrumentationTestCase;
import android.widget.RemoteViews;
@@ -1343,6 +1346,90 @@
assertEquals(100, n.ledOffMS);
}
+ @SdkSuppress(minSdkVersion = 24)
+ @Test
+ public void testBigPictureStyle_isRecovered() {
+ Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),
+ R.drawable.notification_bg_low_pressed);
+ Notification n = new NotificationCompat.Builder(mContext, "channelId")
+ .setSmallIcon(1)
+ .setStyle(new NotificationCompat.BigPictureStyle()
+ .bigPicture(bitmap)
+ .bigLargeIcon(bitmap)
+ .setBigContentTitle("Big Content Title")
+ .setSummaryText("Summary Text"))
+ .build();
+ Notification.Builder builder = Notification.Builder.recoverBuilder(mContext, n);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ Notification.Style style = builder.getStyle();
+ assertNotNull(style);
+ assertSame(Notification.BigPictureStyle.class, style.getClass());
+ }
+ builder.getExtras().remove(Notification.EXTRA_LARGE_ICON_BIG);
+ Icon icon = builder.build().extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG);
+ assertNotNull(icon);
+ }
+
+ @SdkSuppress(minSdkVersion = 19)
+ @Test
+ public void testBigPictureStyle_recoverStyleWithBitmap() {
+ Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),
+ R.drawable.notification_bg_low_pressed);
+ Notification n = new Notification.Builder(mContext)
+ .setSmallIcon(1)
+ .setStyle(new Notification.BigPictureStyle()
+ .bigPicture(bitmap)
+ .bigLargeIcon(bitmap)
+ .setBigContentTitle("Big Content Title")
+ .setSummaryText("Summary Text"))
+ .build();
+ Parcelable firstBuiltIcon = n.extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ assertSame(Icon.class, firstBuiltIcon.getClass());
+ assertEquals(Icon.TYPE_BITMAP, ((Icon) firstBuiltIcon).getType());
+ } else {
+ assertSame(Bitmap.class, firstBuiltIcon.getClass());
+ }
+
+ Style style = Style.extractStyleFromNotification(n);
+ assertNotNull(style);
+ assertSame(NotificationCompat.BigPictureStyle.class, style.getClass());
+ n = new NotificationCompat.Builder(mContext, "channelId")
+ .setSmallIcon(1)
+ .setStyle(style)
+ .build();
+ Parcelable rebuiltIcon = n.extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ assertSame(Icon.class, rebuiltIcon.getClass());
+ assertEquals(Icon.TYPE_BITMAP, ((Icon) rebuiltIcon).getType());
+ } else {
+ assertSame(Bitmap.class, rebuiltIcon.getClass());
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = 23)
+ @Test
+ public void testBigPictureStyle_recoverStyleWithResIcon() {
+ Notification n = new Notification.Builder(mContext)
+ .setSmallIcon(1)
+ .setStyle(new Notification.BigPictureStyle()
+ .bigLargeIcon(Icon.createWithResource(mContext,
+ R.drawable.notification_template_icon_bg)))
+ .build();
+ Icon firstBuiltIcon = n.extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG);
+ assertEquals(Icon.TYPE_RESOURCE, firstBuiltIcon.getType());
+
+ Style style = Style.extractStyleFromNotification(n);
+ assertNotNull(style);
+ assertSame(NotificationCompat.BigPictureStyle.class, style.getClass());
+ n = new NotificationCompat.Builder(mContext, "channelId")
+ .setSmallIcon(1)
+ .setStyle(style)
+ .build();
+ Icon rebuiltIcon = n.extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG);
+ assertEquals(Icon.TYPE_RESOURCE, rebuiltIcon.getType());
+ }
+
@SdkSuppress(minSdkVersion = 16)
@Test
public void testMessagingStyle_nullPerson() {
diff --git a/core/core/src/main/java/androidx/core/app/NotificationCompat.java b/core/core/src/main/java/androidx/core/app/NotificationCompat.java
index 2df35b0..2c47026 100644
--- a/core/core/src/main/java/androidx/core/app/NotificationCompat.java
+++ b/core/core/src/main/java/androidx/core/app/NotificationCompat.java
@@ -2510,6 +2510,14 @@
}
/**
+ * This is called with the extras of the framework {@link Notification} during the
+ * {@link Builder#build()} process, after <code>apply()</code> has been called. This means
+ * that you only need to add data which won't be populated by the framework Notification
+ * which was built so far.
+ *
+ * Moreover, recovering builders and styles is only supported at API 19 and above, no
+ * implementation is required for current BigTextStyle, BigPictureStyle, or InboxStyle.
+ *
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@@ -2923,7 +2931,7 @@
"androidx.core.app.NotificationCompat$BigPictureStyle";
private Bitmap mPicture;
- private Bitmap mBigLargeIcon;
+ private IconCompat mBigLargeIcon;
private boolean mBigLargeIconSet;
public BigPictureStyle() {
@@ -2963,7 +2971,7 @@
* Override the large icon when the big notification is shown.
*/
public @NonNull BigPictureStyle bigLargeIcon(@Nullable Bitmap b) {
- mBigLargeIcon = b;
+ mBigLargeIcon = IconCompat.createWithBitmap(b);
mBigLargeIconSet = true;
return this;
}
@@ -2990,10 +2998,25 @@
.setBigContentTitle(mBigContentTitle)
.bigPicture(mPicture);
if (mBigLargeIconSet) {
- style.bigLargeIcon(mBigLargeIcon);
+ if (mBigLargeIcon == null) {
+ Api16Impl.setBigLargeIcon(style, null);
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ Context context = null;
+ if (builder instanceof NotificationCompatBuilder) {
+ context = ((NotificationCompatBuilder) builder).getContext();
+ }
+ Api23Impl.setBigLargeIcon(style, mBigLargeIcon.toIcon(context));
+ } else if (mBigLargeIcon.getType() == IconCompat.TYPE_BITMAP) {
+ // Before M, only the Bitmap setter existed
+ Api16Impl.setBigLargeIcon(style, mBigLargeIcon.getBitmap());
+ } else {
+ // TODO(b/172282791): When we add #bigLargeIcon(Icon) we'll need to support
+ // other icon types here by rendering them into a new Bitmap.
+ Api16Impl.setBigLargeIcon(style, null);
+ }
}
if (mSummaryTextSet) {
- style.setSummaryText(mSummaryText);
+ Api16Impl.setSummaryText(style, mSummaryText);
}
}
}
@@ -3003,30 +3026,31 @@
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@Override
- public void addCompatExtras(@NonNull Bundle extras) {
- super.addCompatExtras(extras);
-
- if (mBigLargeIconSet) {
- extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
- }
- extras.putParcelable(EXTRA_PICTURE, mPicture);
- }
-
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
- @Override
protected void restoreFromCompatExtras(@NonNull Bundle extras) {
super.restoreFromCompatExtras(extras);
if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) {
- mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG);
+ mBigLargeIcon = asIconCompat(extras.getParcelable(EXTRA_LARGE_ICON_BIG));
mBigLargeIconSet = true;
}
mPicture = extras.getParcelable(EXTRA_PICTURE);
}
+ @Nullable
+ private static IconCompat asIconCompat(@Nullable Parcelable bitmapOrIcon) {
+ if (bitmapOrIcon != null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (bitmapOrIcon instanceof Icon) {
+ return IconCompat.createFromIcon((Icon) bitmapOrIcon);
+ }
+ }
+ if (bitmapOrIcon instanceof Bitmap) {
+ return IconCompat.createWithBitmap((Bitmap) bitmapOrIcon);
+ }
+ }
+ return null;
+ }
+
/**
* @hide
*/
@@ -3037,6 +3061,52 @@
extras.remove(EXTRA_LARGE_ICON_BIG);
extras.remove(EXTRA_PICTURE);
}
+
+ /**
+ * A class for wrapping calls to {@link Notification.BigPictureStyle} methods which
+ * were added in API 16; these calls must be wrapped to avoid performance issues.
+ * See the UnsafeNewApiCall lint rule for more details.
+ */
+ @RequiresApi(16)
+ private static class Api16Impl {
+ private Api16Impl() {
+ }
+
+ /**
+ * Calls {@link Notification.BigPictureStyle#bigLargeIcon(Bitmap)}
+ */
+ @RequiresApi(16)
+ static void setBigLargeIcon(Notification.BigPictureStyle style, Bitmap icon) {
+ style.bigLargeIcon(icon);
+ }
+
+ /**
+ * Calls {@link Notification.BigPictureStyle#setSummaryText(CharSequence)}
+ */
+ @RequiresApi(16)
+ static void setSummaryText(Notification.BigPictureStyle style, CharSequence text) {
+ style.setSummaryText(text);
+ }
+ }
+
+ /**
+ * A class for wrapping calls to {@link Notification.BigPictureStyle} methods which
+ * were added in API 23; these calls must be wrapped to avoid performance issues.
+ * See the UnsafeNewApiCall lint rule for more details.
+ */
+ @RequiresApi(23)
+ private static class Api23Impl {
+ private Api23Impl() {
+ }
+
+ /**
+ * Calls {@link Notification.BigPictureStyle#bigLargeIcon(Icon)}
+ */
+ @RequiresApi(23)
+ static void setBigLargeIcon(Notification.BigPictureStyle style, Icon icon) {
+ style.bigLargeIcon(icon);
+ }
+ }
}
/**
@@ -3133,17 +3203,6 @@
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@Override
- public void addCompatExtras(@NonNull Bundle extras) {
- super.addCompatExtras(extras);
-
- extras.putCharSequence(EXTRA_BIG_TEXT, mBigText);
- }
-
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
- @Override
protected void restoreFromCompatExtras(@NonNull Bundle extras) {
super.restoreFromCompatExtras(extras);
@@ -4034,18 +4093,6 @@
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@Override
- public void addCompatExtras(@NonNull Bundle extras) {
- super.addCompatExtras(extras);
-
- CharSequence[] arr = new CharSequence[mTexts.size()];
- extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(arr));
- }
-
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
- @Override
protected void restoreFromCompatExtras(@NonNull Bundle extras) {
super.restoreFromCompatExtras(extras);
mTexts.clear();
diff --git a/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java b/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java
index 0992927..00b1660 100644
--- a/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java
+++ b/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java
@@ -25,6 +25,7 @@
import static androidx.core.app.NotificationCompat.GROUP_ALERT_SUMMARY;
import android.app.Notification;
+import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
@@ -46,6 +47,7 @@
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
class NotificationCompatBuilder implements NotificationBuilderWithBuilderAccessor {
+ private final Context mContext;
private final Notification.Builder mBuilder;
private final NotificationCompat.Builder mBuilderCompat;
@@ -65,6 +67,7 @@
@SuppressWarnings("deprecation")
NotificationCompatBuilder(NotificationCompat.Builder b) {
mBuilderCompat = b;
+ mContext = b.mContext;
if (Build.VERSION.SDK_INT >= 26) {
mBuilder = new Notification.Builder(b.mContext, b.mChannelId);
} else {
@@ -299,6 +302,10 @@
return mBuilder;
}
+ Context getContext() {
+ return mContext;
+ }
+
public Notification build() {
final NotificationCompat.Style style = mBuilderCompat.mStyle;
if (style != null) {