Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package android.app;
     18 
     19 import static com.android.internal.util.NotificationColorUtil.satisfiesTextContrast;
     20 
     21 import static org.junit.Assert.assertEquals;
     22 import static org.junit.Assert.assertFalse;
     23 import static org.junit.Assert.assertNotSame;
     24 import static org.junit.Assert.assertSame;
     25 import static org.junit.Assert.assertTrue;
     26 
     27 import android.annotation.Nullable;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.graphics.BitmapFactory;
     31 import android.graphics.drawable.Icon;
     32 import android.media.session.MediaSession;
     33 import android.os.Build;
     34 import android.os.Parcel;
     35 import android.os.Parcelable;
     36 import android.support.test.InstrumentationRegistry;
     37 import android.support.test.filters.SmallTest;
     38 import android.support.test.runner.AndroidJUnit4;
     39 import android.widget.RemoteViews;
     40 
     41 import org.junit.Before;
     42 import org.junit.Test;
     43 import org.junit.runner.RunWith;
     44 
     45 import java.util.function.Consumer;
     46 
     47 @RunWith(AndroidJUnit4.class)
     48 @SmallTest
     49 public class NotificationTest {
     50 
     51     private Context mContext;
     52 
     53     @Before
     54     public void setUp() {
     55         mContext = InstrumentationRegistry.getContext();
     56     }
     57 
     58     @Test
     59     public void testColorizedByPermission() {
     60         Notification n = new Notification.Builder(mContext, "test")
     61                 .setFlag(Notification.FLAG_CAN_COLORIZE, true)
     62                 .setColorized(true)
     63                 .build();
     64         assertTrue(n.isColorized());
     65 
     66         n = new Notification.Builder(mContext, "test")
     67                 .setFlag(Notification.FLAG_CAN_COLORIZE, true)
     68                 .build();
     69         assertFalse(n.isColorized());
     70 
     71         n = new Notification.Builder(mContext, "test")
     72                 .setFlag(Notification.FLAG_CAN_COLORIZE, false)
     73                 .setColorized(true)
     74                 .build();
     75         assertFalse(n.isColorized());
     76     }
     77 
     78     @Test
     79     public void testColorizedByForeground() {
     80         Notification n = new Notification.Builder(mContext, "test")
     81                 .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
     82                 .setColorized(true)
     83                 .build();
     84         assertTrue(n.isColorized());
     85 
     86         n = new Notification.Builder(mContext, "test")
     87                 .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
     88                 .build();
     89         assertFalse(n.isColorized());
     90 
     91         n = new Notification.Builder(mContext, "test")
     92                 .setFlag(Notification.FLAG_FOREGROUND_SERVICE, false)
     93                 .setColorized(true)
     94                 .build();
     95         assertFalse(n.isColorized());
     96     }
     97 
     98     @Test
     99     public void testColorSatisfiedWhenBgDarkTextDarker() {
    100         Notification.Builder builder = getMediaNotification();
    101         Notification n = builder.build();
    102 
    103         assertTrue(n.isColorized());
    104 
    105         // An initial guess where the foreground color is actually darker than an already dark bg
    106         int backgroundColor = 0xff585868;
    107         int initialForegroundColor = 0xff505868;
    108         builder.setColorPalette(backgroundColor, initialForegroundColor);
    109         int primaryTextColor = builder.getPrimaryTextColor();
    110         assertTrue(satisfiesTextContrast(primaryTextColor, backgroundColor));
    111         int secondaryTextColor = builder.getSecondaryTextColor();
    112         assertTrue(satisfiesTextContrast(secondaryTextColor, backgroundColor));
    113     }
    114 
    115     @Test
    116     public void testHasCompletedProgress_noProgress() {
    117         Notification n = new Notification.Builder(mContext).build();
    118 
    119         assertFalse(n.hasCompletedProgress());
    120     }
    121 
    122     @Test
    123     public void testHasCompletedProgress_complete() {
    124         Notification n = new Notification.Builder(mContext)
    125                 .setProgress(100, 100, true)
    126                 .build();
    127         Notification n2 = new Notification.Builder(mContext)
    128                 .setProgress(10, 10, false)
    129                 .build();
    130         assertTrue(n.hasCompletedProgress());
    131         assertTrue(n2.hasCompletedProgress());
    132     }
    133 
    134     @Test
    135     public void testHasCompletedProgress_notComplete() {
    136         Notification n = new Notification.Builder(mContext)
    137                 .setProgress(100, 99, true)
    138                 .build();
    139         Notification n2 = new Notification.Builder(mContext)
    140                 .setProgress(10, 4, false)
    141                 .build();
    142         assertFalse(n.hasCompletedProgress());
    143         assertFalse(n2.hasCompletedProgress());
    144     }
    145 
    146     @Test
    147     public void testHasCompletedProgress_zeroMax() {
    148         Notification n = new Notification.Builder(mContext)
    149                 .setProgress(0, 0, true)
    150                 .build();
    151         assertFalse(n.hasCompletedProgress());
    152     }
    153 
    154     @Test
    155     public void largeIconMultipleReferences_keptAfterParcelling() {
    156         Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource(
    157                 mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
    158 
    159         Notification n = new Notification.Builder(mContext).setLargeIcon(originalIcon).build();
    160         assertSame(n.getLargeIcon(), originalIcon);
    161 
    162         Notification q = writeAndReadParcelable(n);
    163         assertNotSame(q.getLargeIcon(), n.getLargeIcon());
    164 
    165         assertTrue(q.getLargeIcon().getBitmap().sameAs(n.getLargeIcon().getBitmap()));
    166         assertSame(q.getLargeIcon(), q.extras.getParcelable(Notification.EXTRA_LARGE_ICON));
    167     }
    168 
    169     @Test
    170     public void largeIconReferenceInExtrasOnly_keptAfterParcelling() {
    171         Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource(
    172                 mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
    173 
    174         Notification n = new Notification.Builder(mContext).build();
    175         n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, originalIcon);
    176         assertSame(n.getLargeIcon(), null);
    177 
    178         Notification q = writeAndReadParcelable(n);
    179         assertSame(q.getLargeIcon(), null);
    180         assertTrue(((Icon) q.extras.getParcelable(Notification.EXTRA_LARGE_ICON)).getBitmap()
    181                 .sameAs(originalIcon.getBitmap()));
    182     }
    183 
    184     @Test
    185     public void allPendingIntents_recollectedAfterReusingBuilder() {
    186         PendingIntent intent1 = PendingIntent.getActivity(mContext, 0, new Intent("test1"), 0);
    187         PendingIntent intent2 = PendingIntent.getActivity(mContext, 0, new Intent("test2"), 0);
    188 
    189         Notification.Builder builder = new Notification.Builder(mContext, "channel");
    190         builder.setContentIntent(intent1);
    191 
    192         Parcel p = Parcel.obtain();
    193 
    194         Notification n1 = builder.build();
    195         n1.writeToParcel(p, 0);
    196 
    197         builder.setContentIntent(intent2);
    198         Notification n2 = builder.build();
    199         n2.writeToParcel(p, 0);
    200 
    201         assertTrue(n2.allPendingIntents.contains(intent2));
    202     }
    203 
    204     @Test
    205     public void allPendingIntents_containsCustomRemoteViews() {
    206         PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent("test"), 0);
    207 
    208         RemoteViews contentView = new RemoteViews(mContext.getPackageName(), 0 /* layoutId */);
    209         contentView.setOnClickPendingIntent(1 /* id */, intent);
    210 
    211         Notification.Builder builder = new Notification.Builder(mContext, "channel");
    212         builder.setCustomContentView(contentView);
    213 
    214         Parcel p = Parcel.obtain();
    215 
    216         Notification n = builder.build();
    217         n.writeToParcel(p, 0);
    218 
    219         assertTrue(n.allPendingIntents.contains(intent));
    220     }
    221 
    222     @Test
    223     public void messagingStyle_isGroupConversation() {
    224         mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
    225         Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
    226                 .setGroupConversation(true)
    227                 .setConversationTitle("test conversation title");
    228         Notification notification = new Notification.Builder(mContext, "test id")
    229                 .setSmallIcon(1)
    230                 .setContentTitle("test title")
    231                 .setStyle(messagingStyle)
    232                 .build();
    233 
    234         assertTrue(messagingStyle.isGroupConversation());
    235         assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
    236     }
    237 
    238     @Test
    239     public void messagingStyle_isGroupConversation_noConversationTitle() {
    240         mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
    241         Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
    242                 .setGroupConversation(true)
    243                 .setConversationTitle(null);
    244         Notification notification = new Notification.Builder(mContext, "test id")
    245                 .setSmallIcon(1)
    246                 .setContentTitle("test title")
    247                 .setStyle(messagingStyle)
    248                 .build();
    249 
    250         assertTrue(messagingStyle.isGroupConversation());
    251         assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
    252     }
    253 
    254     @Test
    255     public void messagingStyle_isGroupConversation_withConversationTitle_legacy() {
    256         // In legacy (version < P), isGroupConversation is controlled by conversationTitle.
    257         mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.O;
    258         Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
    259                 .setGroupConversation(false)
    260                 .setConversationTitle("test conversation title");
    261         Notification notification = new Notification.Builder(mContext, "test id")
    262                 .setSmallIcon(1)
    263                 .setContentTitle("test title")
    264                 .setStyle(messagingStyle)
    265                 .build();
    266 
    267         assertTrue(messagingStyle.isGroupConversation());
    268         assertFalse(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
    269     }
    270 
    271     @Test
    272     public void messagingStyle_isGroupConversation_withoutConversationTitle_legacy() {
    273         // In legacy (version < P), isGroupConversation is controlled by conversationTitle.
    274         mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.O;
    275         Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
    276                 .setGroupConversation(true)
    277                 .setConversationTitle(null);
    278         Notification notification = new Notification.Builder(mContext, "test id")
    279                 .setSmallIcon(1)
    280                 .setContentTitle("test title")
    281                 .setStyle(messagingStyle)
    282                 .build();
    283 
    284         assertFalse(messagingStyle.isGroupConversation());
    285         assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
    286     }
    287 
    288     @Test
    289     public void action_builder_hasDefault() {
    290         Notification.Action action = makeNotificationAction(null);
    291         assertEquals(Notification.Action.SEMANTIC_ACTION_NONE, action.getSemanticAction());
    292     }
    293 
    294     @Test
    295     public void action_builder_setSemanticAction() {
    296         Notification.Action action = makeNotificationAction(
    297                 builder -> builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_REPLY));
    298         assertEquals(Notification.Action.SEMANTIC_ACTION_REPLY, action.getSemanticAction());
    299     }
    300 
    301     @Test
    302     public void action_parcel() {
    303         Notification.Action action = writeAndReadParcelable(
    304                 makeNotificationAction(builder -> {
    305                     builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_ARCHIVE);
    306                     builder.setAllowGeneratedReplies(true);
    307                 }));
    308 
    309         assertEquals(Notification.Action.SEMANTIC_ACTION_ARCHIVE, action.getSemanticAction());
    310         assertTrue(action.getAllowGeneratedReplies());
    311     }
    312 
    313     @Test
    314     public void action_clone() {
    315         Notification.Action action = makeNotificationAction(
    316                 builder -> builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_DELETE));
    317         assertEquals(
    318                 Notification.Action.SEMANTIC_ACTION_DELETE,
    319                 action.clone().getSemanticAction());
    320     }
    321 
    322     private Notification.Builder getMediaNotification() {
    323         MediaSession session = new MediaSession(mContext, "test");
    324         return new Notification.Builder(mContext, "color")
    325                 .setSmallIcon(com.android.internal.R.drawable.emergency_icon)
    326                 .setContentTitle("Title")
    327                 .setContentText("Text")
    328                 .setStyle(new Notification.MediaStyle().setMediaSession(session.getSessionToken()));
    329     }
    330 
    331     /**
    332       * Writes an arbitrary {@link Parcelable} into a {@link Parcel} using its writeToParcel
    333       * method before reading it out again to check that it was sent properly.
    334       */
    335     private static <T extends Parcelable> T writeAndReadParcelable(T original) {
    336         Parcel p = Parcel.obtain();
    337         p.writeParcelable(original, /* flags */ 0);
    338         p.setDataPosition(0);
    339         return p.readParcelable(/* classLoader */ null);
    340     }
    341 
    342     /**
    343      * Creates a Notification.Action by mocking initial dependencies and then applying
    344      * transformations if they're defined.
    345      */
    346     private Notification.Action makeNotificationAction(
    347             @Nullable Consumer<Notification.Action.Builder> transformation) {
    348         Notification.Action.Builder actionBuilder =
    349                 new Notification.Action.Builder(null, "Test Title", null);
    350         if (transformation != null) {
    351             transformation.accept(actionBuilder);
    352         }
    353         return actionBuilder.build();
    354     }
    355 }
    356