[go: nahoru, domu]

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) {