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 android.app.Notification.CarExtender.UnreadConversation.KEY_ON_READ;
20 import static android.app.Notification.CarExtender.UnreadConversation.KEY_ON_REPLY;
21 import static android.app.Notification.CarExtender.UnreadConversation.KEY_REMOTE_INPUT;
22 import static android.app.Notification.EXTRA_ANSWER_INTENT;
23 import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
24 import static android.app.Notification.EXTRA_CALL_PERSON;
25 import static android.app.Notification.EXTRA_CONVERSATION_ICON;
26 import static android.app.Notification.EXTRA_DECLINE_INTENT;
27 import static android.app.Notification.EXTRA_HANG_UP_INTENT;
28 import static android.app.Notification.EXTRA_LARGE_ICON;
29 import static android.app.Notification.EXTRA_LARGE_ICON_BIG;
30 import static android.app.Notification.EXTRA_MEDIA_REMOTE_INTENT;
31 import static android.app.Notification.EXTRA_MEDIA_SESSION;
32 import static android.app.Notification.EXTRA_MESSAGING_PERSON;
33 import static android.app.Notification.EXTRA_PEOPLE_LIST;
34 import static android.app.Notification.EXTRA_PICTURE;
35 import static android.app.Notification.EXTRA_PICTURE_ICON;
36 import static android.app.Notification.EXTRA_SUMMARY_TEXT;
37 import static android.app.Notification.EXTRA_TITLE;
38 import static android.app.Notification.MessagingStyle.Message.KEY_DATA_URI;
39 import static android.app.Notification.MessagingStyle.Message.KEY_SENDER_PERSON;
40 import static android.app.Notification.MessagingStyle.Message.KEY_TEXT;
41 import static android.app.Notification.MessagingStyle.Message.KEY_TIMESTAMP;
42 import static android.app.Notification.TvExtender.EXTRA_CONTENT_INTENT;
43 import static android.app.Notification.TvExtender.EXTRA_DELETE_INTENT;
44 import static android.app.Notification.WearableExtender.KEY_BACKGROUND;
45 import static android.app.Notification.WearableExtender.KEY_DISPLAY_INTENT;
46 
47 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
48 import static com.android.internal.util.ContrastColorUtilTest.assertContrastIsAtLeast;
49 import static com.android.internal.util.ContrastColorUtilTest.assertContrastIsWithinRange;
50 
51 import static com.google.common.truth.Truth.assertThat;
52 
53 import static junit.framework.Assert.assertNotNull;
54 import static junit.framework.Assert.fail;
55 
56 import static org.junit.Assert.assertEquals;
57 import static org.junit.Assert.assertFalse;
58 import static org.junit.Assert.assertNotSame;
59 import static org.junit.Assert.assertNull;
60 import static org.junit.Assert.assertSame;
61 import static org.junit.Assert.assertTrue;
62 import static org.mockito.Mockito.mock;
63 import static org.mockito.Mockito.spy;
64 import static org.mockito.Mockito.when;
65 
66 import android.annotation.Nullable;
67 import android.content.Context;
68 import android.content.Intent;
69 import android.content.LocusId;
70 import android.content.res.Configuration;
71 import android.graphics.Bitmap;
72 import android.graphics.BitmapFactory;
73 import android.graphics.Color;
74 import android.graphics.Typeface;
75 import android.graphics.drawable.Icon;
76 import android.net.Uri;
77 import android.os.Build;
78 import android.os.Bundle;
79 import android.os.Parcel;
80 import android.os.Parcelable;
81 import android.os.SystemProperties;
82 import android.text.Spannable;
83 import android.text.SpannableString;
84 import android.text.SpannableStringBuilder;
85 import android.text.Spanned;
86 import android.text.style.ForegroundColorSpan;
87 import android.text.style.StyleSpan;
88 import android.text.style.TextAppearanceSpan;
89 import android.util.Pair;
90 import android.widget.RemoteViews;
91 
92 import androidx.test.InstrumentationRegistry;
93 import androidx.test.filters.SmallTest;
94 import androidx.test.runner.AndroidJUnit4;
95 
96 import com.android.internal.R;
97 import com.android.internal.util.ContrastColorUtil;
98 
99 import junit.framework.Assert;
100 
101 import org.junit.Before;
102 import org.junit.Test;
103 import org.junit.runner.RunWith;
104 
105 import java.util.List;
106 import java.util.function.Consumer;
107 
108 @RunWith(AndroidJUnit4.class)
109 @SmallTest
110 public class NotificationTest {
111 
112     private Context mContext;
113 
114     @Before
setUp()115     public void setUp() {
116         mContext = InstrumentationRegistry.getContext();
117         // TODO(b/169435530): remove this flag set once resolved.
118         SystemProperties.set("persist.sysui.notification.builder_extras_override",
119                 Boolean.toString(false));
120     }
121 
122     @Test
testColorizedByPermission()123     public void testColorizedByPermission() {
124         Notification n = new Notification.Builder(mContext, "test")
125                 .setFlag(Notification.FLAG_CAN_COLORIZE, true)
126                 .setColorized(true).setColor(Color.WHITE)
127                 .build();
128         assertTrue(n.isColorized());
129 
130         n = new Notification.Builder(mContext, "test")
131                 .setFlag(Notification.FLAG_CAN_COLORIZE, true)
132                 .build();
133         assertFalse(n.isColorized());
134 
135         n = new Notification.Builder(mContext, "test")
136                 .setFlag(Notification.FLAG_CAN_COLORIZE, false)
137                 .setColorized(true).setColor(Color.WHITE)
138                 .build();
139         assertFalse(n.isColorized());
140     }
141 
142     @Test
testColorizedByForeground()143     public void testColorizedByForeground() {
144         Notification n = new Notification.Builder(mContext, "test")
145                 .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
146                 .setColorized(true).setColor(Color.WHITE)
147                 .build();
148         assertTrue(n.isColorized());
149 
150         n = new Notification.Builder(mContext, "test")
151                 .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
152                 .build();
153         assertFalse(n.isColorized());
154 
155         n = new Notification.Builder(mContext, "test")
156                 .setFlag(Notification.FLAG_FOREGROUND_SERVICE, false)
157                 .setColorized(true).setColor(Color.WHITE)
158                 .build();
159         assertFalse(n.isColorized());
160     }
161 
162     @Test
testHasCompletedProgress_noProgress()163     public void testHasCompletedProgress_noProgress() {
164         Notification n = new Notification.Builder(mContext).build();
165 
166         assertFalse(n.hasCompletedProgress());
167     }
168 
169     @Test
testHasCompletedProgress_complete()170     public void testHasCompletedProgress_complete() {
171         Notification n = new Notification.Builder(mContext)
172                 .setProgress(100, 100, true)
173                 .build();
174         Notification n2 = new Notification.Builder(mContext)
175                 .setProgress(10, 10, false)
176                 .build();
177         assertTrue(n.hasCompletedProgress());
178         assertTrue(n2.hasCompletedProgress());
179     }
180 
181     @Test
testHasCompletedProgress_notComplete()182     public void testHasCompletedProgress_notComplete() {
183         Notification n = new Notification.Builder(mContext)
184                 .setProgress(100, 99, true)
185                 .build();
186         Notification n2 = new Notification.Builder(mContext)
187                 .setProgress(10, 4, false)
188                 .build();
189         assertFalse(n.hasCompletedProgress());
190         assertFalse(n2.hasCompletedProgress());
191     }
192 
193     @Test
testHasCompletedProgress_zeroMax()194     public void testHasCompletedProgress_zeroMax() {
195         Notification n = new Notification.Builder(mContext)
196                 .setProgress(0, 0, true)
197                 .build();
198         assertFalse(n.hasCompletedProgress());
199     }
200 
201     @Test
largeIconMultipleReferences_keptAfterParcelling()202     public void largeIconMultipleReferences_keptAfterParcelling() {
203         Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource(
204                 mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
205 
206         Notification n = new Notification.Builder(mContext).setLargeIcon(originalIcon).build();
207         assertSame(n.getLargeIcon(), originalIcon);
208 
209         Notification q = writeAndReadParcelable(n);
210         assertNotSame(q.getLargeIcon(), n.getLargeIcon());
211 
212         assertTrue(q.getLargeIcon().getBitmap().sameAs(n.getLargeIcon().getBitmap()));
213         assertSame(q.getLargeIcon(), q.extras.getParcelable(EXTRA_LARGE_ICON));
214     }
215 
216     @Test
largeIconReferenceInExtrasOnly_keptAfterParcelling()217     public void largeIconReferenceInExtrasOnly_keptAfterParcelling() {
218         Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource(
219                 mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
220 
221         Notification n = new Notification.Builder(mContext).build();
222         n.extras.putParcelable(EXTRA_LARGE_ICON, originalIcon);
223         assertSame(n.getLargeIcon(), null);
224 
225         Notification q = writeAndReadParcelable(n);
226         assertSame(q.getLargeIcon(), null);
227         assertTrue(((Icon) q.extras.getParcelable(EXTRA_LARGE_ICON)).getBitmap()
228                 .sameAs(originalIcon.getBitmap()));
229     }
230 
231     @Test
allPendingIntents_recollectedAfterReusingBuilder()232     public void allPendingIntents_recollectedAfterReusingBuilder() {
233         PendingIntent intent1 = PendingIntent.getActivity(
234                 mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
235         PendingIntent intent2 = PendingIntent.getActivity(
236                 mContext, 0, new Intent("test2"), PendingIntent.FLAG_IMMUTABLE);
237 
238         Notification.Builder builder = new Notification.Builder(mContext, "channel");
239         builder.setContentIntent(intent1);
240 
241         Parcel p = Parcel.obtain();
242 
243         Notification n1 = builder.build();
244         n1.writeToParcel(p, 0);
245 
246         builder.setContentIntent(intent2);
247         Notification n2 = builder.build();
248         n2.writeToParcel(p, 0);
249 
250         assertTrue(n2.allPendingIntents.contains(intent2));
251     }
252 
253     @Test
allPendingIntents_containsCustomRemoteViews()254     public void allPendingIntents_containsCustomRemoteViews() {
255         PendingIntent intent = PendingIntent.getActivity(mContext, 0,
256                 new Intent("test").setPackage(mContext.getPackageName()),
257                 PendingIntent.FLAG_MUTABLE);
258 
259         RemoteViews contentView = new RemoteViews(mContext.getPackageName(), 0 /* layoutId */);
260         contentView.setOnClickPendingIntent(1 /* id */, intent);
261 
262         Notification.Builder builder = new Notification.Builder(mContext, "channel");
263         builder.setCustomContentView(contentView);
264 
265         Parcel p = Parcel.obtain();
266 
267         Notification n = builder.build();
268         n.writeToParcel(p, 0);
269 
270         assertTrue(n.allPendingIntents.contains(intent));
271     }
272 
273     @Test
allPendingIntents_resilientToAnotherNotificationInExtras()274     public void allPendingIntents_resilientToAnotherNotificationInExtras() {
275         PendingIntent contentIntent = createPendingIntent("content");
276         PendingIntent actionIntent = createPendingIntent("action");
277         Notification another = new Notification.Builder(mContext, "channel").build();
278         Bundle bundleContainingAnotherNotification = new Bundle();
279         bundleContainingAnotherNotification.putParcelable(null, another);
280         Notification source = new Notification.Builder(mContext, "channel")
281                 .setContentIntent(contentIntent)
282                 .addAction(new Notification.Action.Builder(null, "action", actionIntent).build())
283                 .setExtras(bundleContainingAnotherNotification)
284                 .build();
285 
286         Parcel p = Parcel.obtain();
287         source.writeToParcel(p, 0);
288         p.setDataPosition(0);
289         Notification unparceled = new Notification(p);
290 
291         assertThat(unparceled.allPendingIntents).containsExactly(contentIntent, actionIntent);
292     }
293 
294     @Test
allPendingIntents_alsoInPublicVersion()295     public void allPendingIntents_alsoInPublicVersion() {
296         PendingIntent contentIntent = createPendingIntent("content");
297         PendingIntent actionIntent = createPendingIntent("action");
298         PendingIntent publicContentIntent = createPendingIntent("publicContent");
299         PendingIntent publicActionIntent = createPendingIntent("publicAction");
300         Notification source = new Notification.Builder(mContext, "channel")
301                 .setContentIntent(contentIntent)
302                 .addAction(new Notification.Action.Builder(null, "action", actionIntent).build())
303                 .setPublicVersion(new Notification.Builder(mContext, "channel")
304                         .setContentIntent(publicContentIntent)
305                         .addAction(new Notification.Action.Builder(
306                                 null, "publicAction", publicActionIntent).build())
307                         .build())
308                 .build();
309 
310         Parcel p = Parcel.obtain();
311         source.writeToParcel(p, 0);
312         p.setDataPosition(0);
313         Notification unparceled = new Notification(p);
314 
315         assertThat(unparceled.allPendingIntents).containsExactly(contentIntent, actionIntent,
316                 publicContentIntent, publicActionIntent);
317         assertThat(unparceled.publicVersion.allPendingIntents).containsExactly(publicContentIntent,
318                 publicActionIntent);
319     }
320 
321     @Test
messagingStyle_isGroupConversation()322     public void messagingStyle_isGroupConversation() {
323         mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
324         Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
325                 .setGroupConversation(true)
326                 .setConversationTitle("test conversation title");
327         Notification notification = new Notification.Builder(mContext, "test id")
328                 .setSmallIcon(1)
329                 .setContentTitle("test title")
330                 .setStyle(messagingStyle)
331                 .build();
332 
333         assertTrue(messagingStyle.isGroupConversation());
334         assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
335     }
336 
337     @Test
messagingStyle_isGroupConversation_noConversationTitle()338     public void messagingStyle_isGroupConversation_noConversationTitle() {
339         mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
340         Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
341                 .setGroupConversation(true)
342                 .setConversationTitle(null);
343         Notification notification = new Notification.Builder(mContext, "test id")
344                 .setSmallIcon(1)
345                 .setContentTitle("test title")
346                 .setStyle(messagingStyle)
347                 .build();
348 
349         assertTrue(messagingStyle.isGroupConversation());
350         assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
351     }
352 
353     @Test
messagingStyle_isGroupConversation_withConversationTitle_legacy()354     public void messagingStyle_isGroupConversation_withConversationTitle_legacy() {
355         // In legacy (version < P), isGroupConversation is controlled by conversationTitle.
356         mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.O;
357         Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
358                 .setGroupConversation(false)
359                 .setConversationTitle("test conversation title");
360         Notification notification = new Notification.Builder(mContext, "test id")
361                 .setSmallIcon(1)
362                 .setContentTitle("test title")
363                 .setStyle(messagingStyle)
364                 .build();
365 
366         assertTrue(messagingStyle.isGroupConversation());
367         assertFalse(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
368     }
369 
370     @Test
messagingStyle_isGroupConversation_withoutConversationTitle_legacy()371     public void messagingStyle_isGroupConversation_withoutConversationTitle_legacy() {
372         // In legacy (version < P), isGroupConversation is controlled by conversationTitle.
373         mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.O;
374         Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
375                 .setGroupConversation(true)
376                 .setConversationTitle(null);
377         Notification notification = new Notification.Builder(mContext, "test id")
378                 .setSmallIcon(1)
379                 .setContentTitle("test title")
380                 .setStyle(messagingStyle)
381                 .build();
382 
383         assertFalse(messagingStyle.isGroupConversation());
384         assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
385     }
386 
387     @Test
action_builder_hasDefault()388     public void action_builder_hasDefault() {
389         Notification.Action action = makeNotificationAction(null);
390         assertEquals(Notification.Action.SEMANTIC_ACTION_NONE, action.getSemanticAction());
391     }
392 
393     @Test
action_builder_setSemanticAction()394     public void action_builder_setSemanticAction() {
395         Notification.Action action = makeNotificationAction(
396                 builder -> builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_REPLY));
397         assertEquals(Notification.Action.SEMANTIC_ACTION_REPLY, action.getSemanticAction());
398     }
399 
400     @Test
action_parcel()401     public void action_parcel() {
402         Notification.Action action = writeAndReadParcelable(
403                 makeNotificationAction(builder -> {
404                     builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_ARCHIVE);
405                     builder.setAllowGeneratedReplies(true);
406                 }));
407 
408         assertEquals(Notification.Action.SEMANTIC_ACTION_ARCHIVE, action.getSemanticAction());
409         assertTrue(action.getAllowGeneratedReplies());
410     }
411 
412     @Test
action_clone()413     public void action_clone() {
414         Notification.Action action = makeNotificationAction(
415                 builder -> builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_DELETE));
416         assertEquals(
417                 Notification.Action.SEMANTIC_ACTION_DELETE,
418                 action.clone().getSemanticAction());
419     }
420 
421     @Test
testBuilder_setLocusId()422     public void testBuilder_setLocusId() {
423         LocusId locusId = new LocusId("4815162342");
424         Notification notification = new Notification.Builder(mContext, "whatever")
425                 .setLocusId(locusId).build();
426         assertEquals(locusId, notification.getLocusId());
427 
428         Notification clone = writeAndReadParcelable(notification);
429         assertEquals(locusId, clone.getLocusId());
430     }
431 
432     @Test
testBuilder_setLocusId_null()433     public void testBuilder_setLocusId_null() {
434         Notification notification = new Notification.Builder(mContext, "whatever")
435                 .setLocusId(null).build();
436         assertNull(notification.getLocusId());
437 
438         Notification clone = writeAndReadParcelable(notification);
439         assertNull(clone.getLocusId());
440     }
441 
442     @Test
testBuilder_getFullLengthSpanColor_returnsNullForString()443     public void testBuilder_getFullLengthSpanColor_returnsNullForString() {
444         assertThat(Notification.Builder.getFullLengthSpanColor("String")).isNull();
445     }
446 
447     @Test
testBuilder_getFullLengthSpanColor_returnsNullWithPartialSpan()448     public void testBuilder_getFullLengthSpanColor_returnsNullWithPartialSpan() {
449         CharSequence text = new SpannableStringBuilder()
450                 .append("text with ")
451                 .append("some red", new ForegroundColorSpan(Color.RED),
452                         Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
453         assertThat(Notification.Builder.getFullLengthSpanColor(text)).isNull();
454     }
455 
456     @Test
testBuilder_getFullLengthSpanColor_worksWithSingleSpan()457     public void testBuilder_getFullLengthSpanColor_worksWithSingleSpan() {
458         CharSequence text = new SpannableStringBuilder()
459                 .append("text that is all red", new ForegroundColorSpan(Color.RED),
460                         Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
461         assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(Color.RED);
462     }
463 
464     @Test
testBuilder_getFullLengthSpanColor_worksWithFullAndPartialSpans()465     public void testBuilder_getFullLengthSpanColor_worksWithFullAndPartialSpans() {
466         Spannable text = new SpannableString("blue text with yellow and green");
467         text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
468                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
469         text.setSpan(new ForegroundColorSpan(Color.BLUE), 0, text.length(),
470                 Spanned.SPAN_INCLUSIVE_INCLUSIVE);
471         text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
472                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
473         assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(Color.BLUE);
474     }
475 
476     @Test
testBuilder_getFullLengthSpanColor_worksWithTextAppearance()477     public void testBuilder_getFullLengthSpanColor_worksWithTextAppearance() {
478         Spannable text = new SpannableString("title text with yellow and green");
479         text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
480                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
481         TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(mContext,
482                 R.style.TextAppearance_DeviceDefault_Notification_Title);
483         int expectedTextColor = textAppearanceSpan.getTextColor().getDefaultColor();
484         text.setSpan(textAppearanceSpan, 0, text.length(),
485                 Spanned.SPAN_INCLUSIVE_INCLUSIVE);
486         text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
487                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
488         assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(expectedTextColor);
489     }
490 
491 
492 
493     @Test
testBuilder_ensureButtonFillContrast_adjustsDarker()494     public void testBuilder_ensureButtonFillContrast_adjustsDarker() {
495         int background = Color.LTGRAY;
496         int foreground = Color.LTGRAY;
497         int result = Notification.Builder.ensureButtonFillContrast(foreground, background);
498         assertContrastIsWithinRange(result, background, 1.3, 1.5);
499         assertThat(ContrastColorUtil.calculateLuminance(result))
500                 .isLessThan(ContrastColorUtil.calculateLuminance(background));
501     }
502 
503     @Test
testBuilder_ensureButtonFillContrast_adjustsLighter()504     public void testBuilder_ensureButtonFillContrast_adjustsLighter() {
505         int background = Color.DKGRAY;
506         int foreground = Color.DKGRAY;
507         int result = Notification.Builder.ensureButtonFillContrast(foreground, background);
508         assertContrastIsWithinRange(result, background, 1.3, 1.5);
509         assertThat(ContrastColorUtil.calculateLuminance(result))
510                 .isGreaterThan(ContrastColorUtil.calculateLuminance(background));
511     }
512 
513     @Test
testCallStyle_getSystemActions_forIncomingCall()514     public void testCallStyle_getSystemActions_forIncomingCall() {
515         PendingIntent answerIntent = createPendingIntent("answer");
516         PendingIntent declineIntent = createPendingIntent("decline");
517         Notification.CallStyle style = Notification.CallStyle.forIncomingCall(
518                 new Person.Builder().setName("A Caller").build(),
519                 declineIntent,
520                 answerIntent
521         );
522         style.setBuilder(new Notification.Builder(mContext, "Channel"));
523 
524         List<Notification.Action> actions = style.getActionsListWithSystemActions();
525 
526         assertEquals(2, actions.size());
527         assertEquals(declineIntent, actions.get(0).actionIntent);
528         assertEquals(answerIntent, actions.get(1).actionIntent);
529     }
530 
531     @Test
testCallStyle_getSystemActions_forOngoingCall()532     public void testCallStyle_getSystemActions_forOngoingCall() {
533         PendingIntent hangUpIntent = createPendingIntent("hangUp");
534         Notification.CallStyle style = Notification.CallStyle.forOngoingCall(
535                 new Person.Builder().setName("A Caller").build(),
536                 hangUpIntent
537         );
538         style.setBuilder(new Notification.Builder(mContext, "Channel"));
539 
540         List<Notification.Action> actions = style.getActionsListWithSystemActions();
541 
542         assertEquals(1, actions.size());
543         assertEquals(hangUpIntent, actions.get(0).actionIntent);
544     }
545 
546     @Test
testCallStyle_getSystemActions_forIncomingCallWithOtherActions()547     public void testCallStyle_getSystemActions_forIncomingCallWithOtherActions() {
548         PendingIntent answerIntent = createPendingIntent("answer");
549         PendingIntent declineIntent = createPendingIntent("decline");
550         Notification.CallStyle style = Notification.CallStyle.forIncomingCall(
551                 new Person.Builder().setName("A Caller").build(),
552                 declineIntent,
553                 answerIntent
554         );
555         Notification.Action actionToKeep = makeNotificationAction(null);
556         Notification.Action actionToDrop = makeNotificationAction(null);
557         Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel")
558                 .addAction(actionToKeep)
559                 .addAction(actionToDrop); //expect to move this action to the end
560         style.setBuilder(notifBuilder); //add a builder with actions
561 
562         List<Notification.Action> actions = style.getActionsListWithSystemActions();
563 
564         assertEquals(4, actions.size());
565         assertEquals(declineIntent, actions.get(0).actionIntent);
566         assertEquals(actionToKeep, actions.get(1));
567         assertEquals(answerIntent, actions.get(2).actionIntent);
568         assertEquals(actionToDrop, actions.get(3));
569     }
570 
571     @Test
testCallStyle_getSystemActions_forOngoingCallWithOtherActions()572     public void testCallStyle_getSystemActions_forOngoingCallWithOtherActions() {
573         PendingIntent hangUpIntent = createPendingIntent("hangUp");
574         Notification.CallStyle style = Notification.CallStyle.forOngoingCall(
575                 new Person.Builder().setName("A Caller").build(),
576                 hangUpIntent
577         );
578         Notification.Action firstAction = makeNotificationAction(null);
579         Notification.Action secondAction = makeNotificationAction(null);
580         Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel")
581                 .addAction(firstAction)
582                 .addAction(secondAction);
583         style.setBuilder(notifBuilder); //add a builder with actions
584 
585         List<Notification.Action> actions = style.getActionsListWithSystemActions();
586 
587         assertEquals(3, actions.size());
588         assertEquals(hangUpIntent, actions.get(0).actionIntent);
589         assertEquals(firstAction, actions.get(1));
590         assertEquals(secondAction, actions.get(2));
591     }
592 
593     @Test
testCallStyle_getSystemActions_dropsOldSystemActions()594     public void testCallStyle_getSystemActions_dropsOldSystemActions() {
595         PendingIntent hangUpIntent = createPendingIntent("decline");
596         Notification.CallStyle style = Notification.CallStyle.forOngoingCall(
597                 new Person.Builder().setName("A Caller").build(),
598                 hangUpIntent
599         );
600         Bundle actionExtras = new Bundle();
601         actionExtras.putBoolean("key_action_priority", true);
602         Notification.Action oldSystemAction = makeNotificationAction(
603                 builder -> builder.addExtras(actionExtras)
604         );
605         Notification.Builder notifBuilder = new Notification.Builder(mContext, "Channel")
606                 .addAction(oldSystemAction);
607         style.setBuilder(notifBuilder); //add a builder with actions
608 
609         List<Notification.Action> actions = style.getActionsListWithSystemActions();
610 
611         assertFalse("Old versions of system actions should be dropped.",
612                 actions.contains(oldSystemAction));
613     }
614 
615     @Test
testBuild_ensureSmallIconIsNotTooBig_resizesIcon()616     public void testBuild_ensureSmallIconIsNotTooBig_resizesIcon() {
617         Icon hugeIcon = Icon.createWithBitmap(
618                 Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
619         Notification notification = new Notification.Builder(mContext, "Channel").setSmallIcon(
620                 hugeIcon).build();
621 
622         Bitmap smallNotificationIcon = notification.getSmallIcon().getBitmap();
623         assertThat(smallNotificationIcon.getWidth()).isEqualTo(
624                 mContext.getResources().getDimensionPixelSize(
625                         R.dimen.notification_small_icon_size));
626         assertThat(smallNotificationIcon.getHeight()).isEqualTo(
627                 mContext.getResources().getDimensionPixelSize(
628                         R.dimen.notification_small_icon_size));
629     }
630 
631     @Test
testBuild_ensureMessagingUserIsNotTooBig_resizesIcon()632     public void testBuild_ensureMessagingUserIsNotTooBig_resizesIcon() {
633         Icon hugeIcon = Icon.createWithBitmap(
634                 Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
635         Icon hugeMessageAvatar = Icon.createWithBitmap(
636                 Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
637         Icon hugeHistoricMessageAvatar = Icon.createWithBitmap(
638                 Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
639 
640         Notification.MessagingStyle style = new Notification.MessagingStyle(
641                 new Person.Builder().setIcon(hugeIcon).setName("A User").build());
642         style.addMessage(new Notification.MessagingStyle.Message("A message", 123456,
643                 new Person.Builder().setIcon(hugeMessageAvatar).setName("A Sender").build()));
644         style.addHistoricMessage(new Notification.MessagingStyle.Message("A message", 123456,
645                 new Person.Builder().setIcon(hugeHistoricMessageAvatar).setName(
646                         "A Historic Sender").build()));
647         Notification notification = new Notification.Builder(mContext, "Channel").setStyle(
648                 style).build();
649 
650         int targetSize = mContext.getResources().getDimensionPixelSize(
651                 ActivityManager.isLowRamDeviceStatic()
652                         ? R.dimen.notification_person_icon_max_size_low_ram
653                         : R.dimen.notification_person_icon_max_size);
654 
655         Bitmap personIcon = style.getUser().getIcon().getBitmap();
656         assertThat(personIcon.getWidth()).isEqualTo(targetSize);
657         assertThat(personIcon.getHeight()).isEqualTo(targetSize);
658 
659         Bitmap avatarIcon = style.getMessages().get(0).getSenderPerson().getIcon().getBitmap();
660         assertThat(avatarIcon.getWidth()).isEqualTo(targetSize);
661         assertThat(avatarIcon.getHeight()).isEqualTo(targetSize);
662 
663         Bitmap historicAvatarIcon = style.getHistoricMessages().get(
664                 0).getSenderPerson().getIcon().getBitmap();
665         assertThat(historicAvatarIcon.getWidth()).isEqualTo(targetSize);
666         assertThat(historicAvatarIcon.getHeight()).isEqualTo(targetSize);
667     }
668 
669     @Test
testBuild_ensureMessagingShortcutIconIsNotTooBig_resizesIcon()670     public void testBuild_ensureMessagingShortcutIconIsNotTooBig_resizesIcon() {
671         Icon hugeIcon = Icon.createWithBitmap(
672                 Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
673         Notification.MessagingStyle style = new Notification.MessagingStyle(
674                 new Person.Builder().setName("A User").build()).setShortcutIcon(hugeIcon);
675 
676         Notification notification = new Notification.Builder(mContext, "Channel").setStyle(
677                 style).build();
678         Bitmap shortcutIcon = style.getShortcutIcon().getBitmap();
679 
680         assertThat(shortcutIcon.getWidth()).isEqualTo(
681                 mContext.getResources().getDimensionPixelSize(
682                         R.dimen.notification_small_icon_size));
683         assertThat(shortcutIcon.getHeight()).isEqualTo(
684                 mContext.getResources().getDimensionPixelSize(
685                         R.dimen.notification_small_icon_size));
686     }
687 
688     @Test
testColors_ensureColors_dayMode_producesValidPalette()689     public void testColors_ensureColors_dayMode_producesValidPalette() {
690         Notification.Colors c = new Notification.Colors();
691         boolean colorized = false;
692         boolean nightMode = false;
693         resolveColorsInNightMode(nightMode, c, Color.BLUE, colorized);
694         assertValid(c);
695     }
696 
697     @Test
testColors_ensureColors_nightMode_producesValidPalette()698     public void testColors_ensureColors_nightMode_producesValidPalette() {
699         Notification.Colors c = new Notification.Colors();
700         boolean colorized = false;
701         boolean nightMode = true;
702         resolveColorsInNightMode(nightMode, c, Color.BLUE, colorized);
703         assertValid(c);
704     }
705 
706     @Test
testColors_ensureColors_colorized_producesValidPalette_default()707     public void testColors_ensureColors_colorized_producesValidPalette_default() {
708         validateColorizedPaletteForColor(Notification.COLOR_DEFAULT);
709     }
710 
711     @Test
testColors_ensureColors_colorized_producesValidPalette_blue()712     public void testColors_ensureColors_colorized_producesValidPalette_blue() {
713         validateColorizedPaletteForColor(Color.BLUE);
714     }
715 
716     @Test
testColors_ensureColors_colorized_producesValidPalette_red()717     public void testColors_ensureColors_colorized_producesValidPalette_red() {
718         validateColorizedPaletteForColor(Color.RED);
719     }
720 
721     @Test
testColors_ensureColors_colorized_producesValidPalette_white()722     public void testColors_ensureColors_colorized_producesValidPalette_white() {
723         validateColorizedPaletteForColor(Color.WHITE);
724     }
725 
726     @Test
testColors_ensureColors_colorized_producesValidPalette_black()727     public void testColors_ensureColors_colorized_producesValidPalette_black() {
728         validateColorizedPaletteForColor(Color.BLACK);
729     }
730 
731     @Test
testIsMediaNotification_nullSession_returnsFalse()732     public void testIsMediaNotification_nullSession_returnsFalse() {
733         // Null media session
734         Notification.MediaStyle mediaStyle = new Notification.MediaStyle();
735         Notification notification = new Notification.Builder(mContext, "test id")
736                 .setStyle(mediaStyle)
737                 .build();
738         assertFalse(notification.isMediaNotification());
739     }
740 
741     @Test
testIsMediaNotification_invalidSession_returnsFalse()742     public void testIsMediaNotification_invalidSession_returnsFalse() {
743         // Extra was set manually to an invalid type
744         Bundle extras = new Bundle();
745         extras.putParcelable(EXTRA_MEDIA_SESSION, new Intent());
746         Notification.MediaStyle mediaStyle = new Notification.MediaStyle();
747         Notification notification = new Notification.Builder(mContext, "test id")
748                 .setStyle(mediaStyle)
749                 .addExtras(extras)
750                 .build();
751         assertFalse(notification.isMediaNotification());
752     }
753 
validateColorizedPaletteForColor(int rawColor)754     public void validateColorizedPaletteForColor(int rawColor) {
755         Notification.Colors cDay = new Notification.Colors();
756         Notification.Colors cNight = new Notification.Colors();
757         boolean colorized = true;
758 
759         resolveColorsInNightMode(false, cDay, rawColor, colorized);
760         resolveColorsInNightMode(true, cNight, rawColor, colorized);
761 
762         if (rawColor != Notification.COLOR_DEFAULT) {
763             assertEquals(rawColor, cDay.getBackgroundColor());
764             assertEquals(rawColor, cNight.getBackgroundColor());
765         }
766 
767         assertValid(cDay);
768         assertValid(cNight);
769 
770         if (rawColor != Notification.COLOR_DEFAULT) {
771             // When a color is provided, night mode should have no effect on the notification
772             assertEquals(cDay.getBackgroundColor(), cNight.getBackgroundColor());
773             assertEquals(cDay.getPrimaryTextColor(), cNight.getPrimaryTextColor());
774             assertEquals(cDay.getSecondaryTextColor(), cNight.getSecondaryTextColor());
775             assertEquals(cDay.getPrimaryAccentColor(), cNight.getPrimaryAccentColor());
776             assertEquals(cDay.getSecondaryAccentColor(), cNight.getSecondaryAccentColor());
777             assertEquals(cDay.getTertiaryAccentColor(), cNight.getTertiaryAccentColor());
778             assertEquals(cDay.getOnAccentTextColor(), cNight.getOnAccentTextColor());
779             assertEquals(cDay.getProtectionColor(), cNight.getProtectionColor());
780             assertEquals(cDay.getContrastColor(), cNight.getContrastColor());
781             assertEquals(cDay.getRippleAlpha(), cNight.getRippleAlpha());
782         }
783     }
784 
785     @Test
testRecoverBuilder_nullExtraPeopleList_noCrash()786     public void testRecoverBuilder_nullExtraPeopleList_noCrash() {
787         Bundle extras = new Bundle();
788         extras.putParcelable(EXTRA_PEOPLE_LIST, null);
789 
790         Notification n = new Notification.Builder(mContext, "test")
791                 .setSmallIcon(0)
792                 .addExtras(extras)
793                 .build();
794         Bundle fakeTypes = new Bundle();
795         fakeTypes.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, new Bundle());
796 
797         Notification.Builder.recoverBuilder(mContext, n);
798 
799         // no crash, good
800     }
801 
802     @Test
testVisitUris_invalidExtra_noCrash()803     public void testVisitUris_invalidExtra_noCrash() {
804         Notification n = new Notification.Builder(mContext, "test")
805                 .setSmallIcon(0)
806                 .build();
807         Bundle fakeTypes = new Bundle();
808         fakeTypes.putParcelable(EXTRA_LARGE_ICON_BIG, new Bundle());
809         fakeTypes.putParcelable(EXTRA_PICTURE_ICON, new Bundle());
810         fakeTypes.putParcelable(EXTRA_MESSAGING_PERSON, new Bundle());
811 
812         Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
813         n.visitUris(visitor);
814 
815         // no crash, good
816     }
817 
818     @Test
testRecoverBuilder_invalidExtra_noCrash()819     public void testRecoverBuilder_invalidExtra_noCrash() {
820         Notification n = new Notification.Builder(mContext, "test")
821                 .setSmallIcon(0)
822                 .build();
823         Bundle fakeTypes = new Bundle();
824         fakeTypes.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, new Bundle());
825 
826         Notification.Builder.recoverBuilder(mContext, n);
827 
828         // no crash, good
829     }
830 
831     @Test
testIsMediaNotification_invalidExtra_noCrash()832     public void testIsMediaNotification_invalidExtra_noCrash() {
833         Notification n = new Notification.Builder(mContext, "test")
834                 .setSmallIcon(0)
835                 .setStyle(new Notification.MediaStyle())
836                 .build();
837         Bundle fakeTypes = new Bundle();
838         fakeTypes.putParcelable(EXTRA_MEDIA_SESSION, new Bundle());
839 
840         n.isMediaNotification();
841 
842         // no crash, good
843     }
844 
845     @Test
testRestoreFromExtras_BigText_invalidExtra_noCrash()846     public void testRestoreFromExtras_BigText_invalidExtra_noCrash() {
847         Notification.Style style = new Notification.BigTextStyle();
848         Bundle fakeTypes = new Bundle();
849         fakeTypes.putParcelable(EXTRA_LARGE_ICON_BIG, new Bundle());
850 
851 
852         // no crash, good
853     }
854 
855     @Test
testRestoreFromExtras_Messaging_invalidExtra_noCrash()856     public void testRestoreFromExtras_Messaging_invalidExtra_noCrash() {
857         Notification.Style style = new Notification.MessagingStyle("test");
858         Bundle fakeTypes = new Bundle();
859         fakeTypes.putParcelable(EXTRA_MESSAGING_PERSON, new Bundle());
860         fakeTypes.putParcelable(EXTRA_CONVERSATION_ICON, new Bundle());
861 
862         Notification n = new Notification.Builder(mContext, "test")
863                 .setStyle(style)
864                 .setExtras(fakeTypes)
865                 .build();
866         Notification.Builder.recoverBuilder(mContext, n);
867 
868         // no crash, good
869     }
870 
871     @Test
testRestoreFromExtras_Media_invalidExtra_noCrash()872     public void testRestoreFromExtras_Media_invalidExtra_noCrash() {
873         Notification.Style style = new Notification.MediaStyle();
874         Bundle fakeTypes = new Bundle();
875         fakeTypes.putParcelable(EXTRA_MEDIA_SESSION, new Bundle());
876         fakeTypes.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, new Bundle());
877 
878         Notification n = new Notification.Builder(mContext, "test")
879                 .setStyle(style)
880                 .setExtras(fakeTypes)
881                 .build();
882         Notification.Builder.recoverBuilder(mContext, n);
883 
884         // no crash, good
885     }
886 
887     @Test
testRestoreFromExtras_Call_invalidExtra_noCrash()888     public void testRestoreFromExtras_Call_invalidExtra_noCrash() {
889         PendingIntent intent1 = PendingIntent.getActivity(
890                 mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
891         Notification.Style style = Notification.CallStyle.forIncomingCall(
892                 new Person.Builder().setName("hi").build(), intent1, intent1);
893 
894         Bundle fakeTypes = new Bundle();
895         fakeTypes.putParcelable(EXTRA_CALL_PERSON, new Bundle());
896         fakeTypes.putParcelable(EXTRA_ANSWER_INTENT, new Bundle());
897         fakeTypes.putParcelable(EXTRA_DECLINE_INTENT, new Bundle());
898         fakeTypes.putParcelable(EXTRA_HANG_UP_INTENT, new Bundle());
899 
900         Notification n = new Notification.Builder(mContext, "test")
901                 .setStyle(style)
902                 .setExtras(fakeTypes)
903                 .build();
904         Notification.Builder.recoverBuilder(mContext, n);
905         // no crash, good
906     }
907 
908     @Test
testGetPictureIcon_invalidExtra_noCrash()909     public void testGetPictureIcon_invalidExtra_noCrash() {
910         Bundle fakeTypes = new Bundle();
911         fakeTypes.putParcelable(EXTRA_PICTURE, new Bundle());
912         fakeTypes.putParcelable(EXTRA_PICTURE_ICON, new Bundle());
913 
914         Notification.BigPictureStyle.getPictureIcon(fakeTypes);
915 
916         // no crash, good
917     }
918 
919     @Test
testWearableExtender_invalidExtra_noCrash()920     public void testWearableExtender_invalidExtra_noCrash() {
921         Notification n = new Notification.Builder(mContext, "test")
922                 .setSmallIcon(0)
923                 .setStyle(new Notification.MediaStyle())
924                 .build();
925         Bundle fakeTypes = new Bundle();
926         fakeTypes.putParcelable(KEY_DISPLAY_INTENT, new Bundle());
927         fakeTypes.putParcelable(KEY_BACKGROUND, new Bundle());
928         Notification.WearableExtender extender = new Notification.WearableExtender(n);
929 
930         // no crash, good
931     }
932 
933     @Test
testCarExtender_invalidExtra_noCrash()934     public void testCarExtender_invalidExtra_noCrash() {
935         Notification n = new Notification.Builder(mContext, "test")
936                 .setSmallIcon(0)
937                 .setStyle(new Notification.MediaStyle())
938                 .build();
939         Bundle fakeTypes = new Bundle();
940         fakeTypes.putParcelable(EXTRA_LARGE_ICON, new Bundle());
941         Notification.CarExtender extender = new Notification.CarExtender(n);
942 
943         // no crash, good
944     }
945 
946     @Test
testTvExtender_invalidExtra_noCrash()947     public void testTvExtender_invalidExtra_noCrash() {
948         Notification n = new Notification.Builder(mContext, "test")
949                 .setSmallIcon(0)
950                 .setStyle(new Notification.MediaStyle())
951                 .build();
952         Bundle fakeTypes = new Bundle();
953         fakeTypes.putParcelable(EXTRA_CONTENT_INTENT, new Bundle());
954         fakeTypes.putParcelable(EXTRA_DELETE_INTENT, new Bundle());
955         Notification.TvExtender extender = new Notification.TvExtender(n);
956 
957         // no crash, good
958     }
959 
960     @Test
testGetUnreadConversationFromBundle_invalidExtra_noCrash()961     public void testGetUnreadConversationFromBundle_invalidExtra_noCrash() {
962         Bundle fakeTypes = new Bundle();
963         fakeTypes.putParcelable(KEY_ON_READ, new Bundle());
964         fakeTypes.putParcelable(KEY_ON_REPLY, new Bundle());
965         fakeTypes.putParcelable(KEY_REMOTE_INPUT, new Bundle());
966 
967         Notification n = new Notification.Builder(mContext, "test")
968                 .setExtras(fakeTypes)
969                 .build();
970         Notification.CarExtender extender = new Notification.CarExtender(n);
971 
972         // no crash, good
973     }
974 
975     @Test
testGetMessageFromBundle_invalidExtra_noCrash()976     public void testGetMessageFromBundle_invalidExtra_noCrash() {
977         Bundle fakeTypes = new Bundle();
978         fakeTypes.putParcelable(KEY_SENDER_PERSON, new Bundle());
979         fakeTypes.putString(KEY_TEXT, "text");
980         fakeTypes.putLong(KEY_TIMESTAMP, 0);
981         fakeTypes.putParcelable(KEY_REMOTE_INPUT, new Bundle());
982         fakeTypes.putParcelable(KEY_DATA_URI, new Bundle());
983         Notification.MessagingStyle.Message.getMessageFromBundle(fakeTypes);
984 
985         // no crash, good
986     }
987 
988 
989     @Test
testDoesNotStripsExtenders()990     public void testDoesNotStripsExtenders() {
991         Notification.Builder nb = new Notification.Builder(mContext, "channel");
992         nb.extend(new Notification.CarExtender().setColor(Color.RED));
993         nb.extend(new Notification.TvExtender().setChannelId("different channel"));
994         nb.extend(new Notification.WearableExtender().setDismissalId("dismiss"));
995         Notification before = nb.build();
996         Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before);
997 
998         assertTrue(before == after);
999 
1000         Assert.assertEquals("different channel",
1001                 new Notification.TvExtender(before).getChannelId());
1002         Assert.assertEquals(Color.RED, new Notification.CarExtender(before).getColor());
1003         Assert.assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId());
1004     }
1005 
1006     @Test
areIconsDifferent_sameSmallIcon_false()1007     public void areIconsDifferent_sameSmallIcon_false() {
1008         Notification n1 = new Notification.Builder(mContext, "test").setSmallIcon(1).build();
1009         Notification n2 = new Notification.Builder(mContext, "test").setSmallIcon(1).build();
1010 
1011         assertThat(Notification.areIconsDifferent(n1, n2)).isFalse();
1012     }
1013 
1014     @Test
areIconsDifferent_differentSmallIcon_true()1015     public void areIconsDifferent_differentSmallIcon_true() {
1016         Notification n1 = new Notification.Builder(mContext, "test").setSmallIcon(1).build();
1017         Notification n2 = new Notification.Builder(mContext, "test").setSmallIcon(2).build();
1018 
1019         assertThat(Notification.areIconsDifferent(n1, n2)).isTrue();
1020     }
1021 
1022     @Test
areIconsDifferent_sameLargeIcon_false()1023     public void areIconsDifferent_sameLargeIcon_false() {
1024         Icon icon1 = Icon.createWithContentUri("uri");
1025         Icon icon2 = Icon.createWithContentUri("uri");
1026         Notification n1 = new Notification.Builder(mContext, "test")
1027                 .setSmallIcon(1).setLargeIcon(icon1).build();
1028         Notification n2 = new Notification.Builder(mContext, "test")
1029                 .setSmallIcon(1).setLargeIcon(icon2).build();
1030 
1031         // Note that this will almost certainly not happen for Icons created from Bitmaps, since
1032         // their serialization/deserialization of Bitmaps (app -> system_process) results in a
1033         // different getGenerationId() value. :(
1034         assertThat(Notification.areIconsDifferent(n1, n2)).isFalse();
1035     }
1036 
1037     @Test
areIconsDifferent_differentLargeIcon_true()1038     public void areIconsDifferent_differentLargeIcon_true() {
1039         Icon icon1 = Icon.createWithContentUri("uri1");
1040         Icon icon2 = Icon.createWithContentUri("uri2");
1041         Notification n1 = new Notification.Builder(mContext, "test")
1042                 .setSmallIcon(1).setLargeIcon(icon1).build();
1043         Notification n2 = new Notification.Builder(mContext, "test")
1044                 .setSmallIcon(2).setLargeIcon(icon2).build();
1045 
1046         assertThat(Notification.areIconsDifferent(n1, n2)).isTrue();
1047     }
1048 
1049     @Test
areIconsDifferent_addedLargeIcon_true()1050     public void areIconsDifferent_addedLargeIcon_true() {
1051         Icon icon = Icon.createWithContentUri("uri");
1052         Notification n1 = new Notification.Builder(mContext, "test").setSmallIcon(1).build();
1053         Notification n2 = new Notification.Builder(mContext, "test")
1054                 .setSmallIcon(2).setLargeIcon(icon).build();
1055 
1056         assertThat(Notification.areIconsDifferent(n1, n2)).isTrue();
1057     }
1058 
1059     @Test
areIconsDifferent_removedLargeIcon_true()1060     public void areIconsDifferent_removedLargeIcon_true() {
1061         Icon icon = Icon.createWithContentUri("uri");
1062         Notification n1 = new Notification.Builder(mContext, "test")
1063                 .setSmallIcon(1).setLargeIcon(icon).build();
1064         Notification n2 = new Notification.Builder(mContext, "test").setSmallIcon(2).build();
1065 
1066         assertThat(Notification.areIconsDifferent(n1, n2)).isTrue();
1067     }
1068 
1069     @Test
testStyleChangeVisiblyDifferent_noStyles()1070     public void testStyleChangeVisiblyDifferent_noStyles() {
1071         Notification.Builder n1 = new Notification.Builder(mContext, "test");
1072         Notification.Builder n2 = new Notification.Builder(mContext, "test");
1073 
1074         assertFalse(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
1075     }
1076 
1077     @Test
testStyleChangeVisiblyDifferent_noStyleToStyle()1078     public void testStyleChangeVisiblyDifferent_noStyleToStyle() {
1079         Notification.Builder n1 = new Notification.Builder(mContext, "test");
1080         Notification.Builder n2 = new Notification.Builder(mContext, "test")
1081                 .setStyle(new Notification.BigTextStyle());
1082 
1083         assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
1084     }
1085 
1086     @Test
testStyleChangeVisiblyDifferent_styleToNoStyle()1087     public void testStyleChangeVisiblyDifferent_styleToNoStyle() {
1088         Notification.Builder n2 = new Notification.Builder(mContext, "test");
1089         Notification.Builder n1 = new Notification.Builder(mContext, "test")
1090                 .setStyle(new Notification.BigTextStyle());
1091 
1092         assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
1093     }
1094 
1095     @Test
testStyleChangeVisiblyDifferent_changeStyle()1096     public void testStyleChangeVisiblyDifferent_changeStyle() {
1097         Notification.Builder n1 = new Notification.Builder(mContext, "test")
1098                 .setStyle(new Notification.InboxStyle());
1099         Notification.Builder n2 = new Notification.Builder(mContext, "test")
1100                 .setStyle(new Notification.BigTextStyle());
1101 
1102         assertTrue(Notification.areStyledNotificationsVisiblyDifferent(n1, n2));
1103     }
1104 
1105     @Test
testInboxTextChange()1106     public void testInboxTextChange() {
1107         Notification.Builder nInbox1 = new Notification.Builder(mContext, "test")
1108                 .setStyle(new Notification.InboxStyle().addLine("a").addLine("b"));
1109         Notification.Builder nInbox2 = new Notification.Builder(mContext, "test")
1110                 .setStyle(new Notification.InboxStyle().addLine("b").addLine("c"));
1111 
1112         assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nInbox1, nInbox2));
1113     }
1114 
1115     @Test
testBigTextTextChange()1116     public void testBigTextTextChange() {
1117         Notification.Builder nBigText1 = new Notification.Builder(mContext, "test")
1118                 .setStyle(new Notification.BigTextStyle().bigText("something"));
1119         Notification.Builder nBigText2 = new Notification.Builder(mContext, "test")
1120                 .setStyle(new Notification.BigTextStyle().bigText("else"));
1121 
1122         assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigText1, nBigText2));
1123     }
1124 
1125     @Test
testBigPictureChange()1126     public void testBigPictureChange() {
1127         Bitmap bitA = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
1128         Bitmap bitB = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
1129 
1130         Notification.Builder nBigPic1 = new Notification.Builder(mContext, "test")
1131                 .setStyle(new Notification.BigPictureStyle().bigPicture(bitA));
1132         Notification.Builder nBigPic2 = new Notification.Builder(mContext, "test")
1133                 .setStyle(new Notification.BigPictureStyle().bigPicture(bitB));
1134 
1135         assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigPic1, nBigPic2));
1136     }
1137 
1138     @Test
testMessagingChange_text()1139     public void testMessagingChange_text() {
1140         Notification.Builder nM1 = new Notification.Builder(mContext, "test")
1141                 .setStyle(new Notification.MessagingStyle("")
1142                         .addMessage(new Notification.MessagingStyle.Message(
1143                                 "a", 100, new Person.Builder().setName("hi").build())));
1144         Notification.Builder nM2 = new Notification.Builder(mContext, "test")
1145                 .setStyle(new Notification.MessagingStyle("")
1146                         .addMessage(new Notification.MessagingStyle.Message(
1147                                 "a", 100, new Person.Builder().setName("hi").build()))
1148                         .addMessage(new Notification.MessagingStyle.Message(
1149                                 "b", 100, new Person.Builder().setName("hi").build()))
1150                 );
1151 
1152         assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
1153     }
1154 
1155     @Test
testMessagingChange_data()1156     public void testMessagingChange_data() {
1157         Notification.Builder nM1 = new Notification.Builder(mContext, "test")
1158                 .setStyle(new Notification.MessagingStyle("")
1159                         .addMessage(new Notification.MessagingStyle.Message(
1160                                 "a", 100, new Person.Builder().setName("hi").build())
1161                                 .setData("text", mock(Uri.class))));
1162         Notification.Builder nM2 = new Notification.Builder(mContext, "test")
1163                 .setStyle(new Notification.MessagingStyle("")
1164                         .addMessage(new Notification.MessagingStyle.Message(
1165                                 "a", 100, new Person.Builder().setName("hi").build())));
1166 
1167         assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
1168     }
1169 
1170     @Test
testMessagingChange_sender()1171     public void testMessagingChange_sender() {
1172         Person a = new Person.Builder().setName("A").build();
1173         Person b = new Person.Builder().setName("b").build();
1174         Notification.Builder nM1 = new Notification.Builder(mContext, "test")
1175                 .setStyle(new Notification.MessagingStyle("")
1176                         .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
1177         Notification.Builder nM2 = new Notification.Builder(mContext, "test")
1178                 .setStyle(new Notification.MessagingStyle("")
1179                         .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
1180 
1181         assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
1182     }
1183 
1184     @Test
testMessagingChange_key()1185     public void testMessagingChange_key() {
1186         Person a = new Person.Builder().setName("hi").setKey("A").build();
1187         Person b = new Person.Builder().setName("hi").setKey("b").build();
1188         Notification.Builder nM1 = new Notification.Builder(mContext, "test")
1189                 .setStyle(new Notification.MessagingStyle("")
1190                         .addMessage(new Notification.MessagingStyle.Message("a", 100, a)));
1191         Notification.Builder nM2 = new Notification.Builder(mContext, "test")
1192                 .setStyle(new Notification.MessagingStyle("")
1193                         .addMessage(new Notification.MessagingStyle.Message("a", 100, b)));
1194 
1195         assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
1196     }
1197 
1198     @Test
testMessagingChange_ignoreTimeChange()1199     public void testMessagingChange_ignoreTimeChange() {
1200         Notification.Builder nM1 = new Notification.Builder(mContext, "test")
1201                 .setStyle(new Notification.MessagingStyle("")
1202                         .addMessage(new Notification.MessagingStyle.Message(
1203                                 "a", 100, new Person.Builder().setName("hi").build())));
1204         Notification.Builder nM2 = new Notification.Builder(mContext, "test")
1205                 .setStyle(new Notification.MessagingStyle("")
1206                         .addMessage(new Notification.MessagingStyle.Message(
1207                                 "a", 1000, new Person.Builder().setName("hi").build()))
1208                 );
1209 
1210         assertFalse(Notification.areStyledNotificationsVisiblyDifferent(nM1, nM2));
1211     }
1212 
1213     @Test
testRemoteViews_nullChange()1214     public void testRemoteViews_nullChange() {
1215         Notification.Builder n1 = new Notification.Builder(mContext, "test")
1216                 .setContent(mock(RemoteViews.class));
1217         Notification.Builder n2 = new Notification.Builder(mContext, "test");
1218         assertTrue(Notification.areRemoteViewsChanged(n1, n2));
1219 
1220         n1 = new Notification.Builder(mContext, "test");
1221         n2 = new Notification.Builder(mContext, "test")
1222                 .setContent(mock(RemoteViews.class));
1223         assertTrue(Notification.areRemoteViewsChanged(n1, n2));
1224 
1225         n1 = new Notification.Builder(mContext, "test")
1226                 .setCustomBigContentView(mock(RemoteViews.class));
1227         n2 = new Notification.Builder(mContext, "test");
1228         assertTrue(Notification.areRemoteViewsChanged(n1, n2));
1229 
1230         n1 = new Notification.Builder(mContext, "test");
1231         n2 = new Notification.Builder(mContext, "test")
1232                 .setCustomBigContentView(mock(RemoteViews.class));
1233         assertTrue(Notification.areRemoteViewsChanged(n1, n2));
1234 
1235         n1 = new Notification.Builder(mContext, "test");
1236         n2 = new Notification.Builder(mContext, "test");
1237         assertFalse(Notification.areRemoteViewsChanged(n1, n2));
1238     }
1239 
1240     @Test
testRemoteViews_layoutChange()1241     public void testRemoteViews_layoutChange() {
1242         RemoteViews a = mock(RemoteViews.class);
1243         when(a.getLayoutId()).thenReturn(234);
1244         RemoteViews b = mock(RemoteViews.class);
1245         when(b.getLayoutId()).thenReturn(189);
1246 
1247         Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
1248         Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
1249         assertTrue(Notification.areRemoteViewsChanged(n1, n2));
1250 
1251         n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
1252         n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
1253         assertTrue(Notification.areRemoteViewsChanged(n1, n2));
1254 
1255         n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
1256         n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
1257         assertTrue(Notification.areRemoteViewsChanged(n1, n2));
1258     }
1259 
1260     @Test
testRemoteViews_layoutSame()1261     public void testRemoteViews_layoutSame() {
1262         RemoteViews a = mock(RemoteViews.class);
1263         when(a.getLayoutId()).thenReturn(234);
1264         RemoteViews b = mock(RemoteViews.class);
1265         when(b.getLayoutId()).thenReturn(234);
1266 
1267         Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
1268         Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
1269         assertFalse(Notification.areRemoteViewsChanged(n1, n2));
1270 
1271         n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
1272         n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
1273         assertFalse(Notification.areRemoteViewsChanged(n1, n2));
1274 
1275         n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
1276         n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
1277         assertFalse(Notification.areRemoteViewsChanged(n1, n2));
1278     }
1279 
1280     @Test
testRemoteViews_sequenceChange()1281     public void testRemoteViews_sequenceChange() {
1282         RemoteViews a = mock(RemoteViews.class);
1283         when(a.getLayoutId()).thenReturn(234);
1284         when(a.getSequenceNumber()).thenReturn(1);
1285         RemoteViews b = mock(RemoteViews.class);
1286         when(b.getLayoutId()).thenReturn(234);
1287         when(b.getSequenceNumber()).thenReturn(2);
1288 
1289         Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
1290         Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
1291         assertTrue(Notification.areRemoteViewsChanged(n1, n2));
1292 
1293         n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
1294         n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
1295         assertTrue(Notification.areRemoteViewsChanged(n1, n2));
1296 
1297         n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
1298         n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
1299         assertTrue(Notification.areRemoteViewsChanged(n1, n2));
1300     }
1301 
1302     @Test
testRemoteViews_sequenceSame()1303     public void testRemoteViews_sequenceSame() {
1304         RemoteViews a = mock(RemoteViews.class);
1305         when(a.getLayoutId()).thenReturn(234);
1306         when(a.getSequenceNumber()).thenReturn(1);
1307         RemoteViews b = mock(RemoteViews.class);
1308         when(b.getLayoutId()).thenReturn(234);
1309         when(b.getSequenceNumber()).thenReturn(1);
1310 
1311         Notification.Builder n1 = new Notification.Builder(mContext, "test").setContent(a);
1312         Notification.Builder n2 = new Notification.Builder(mContext, "test").setContent(b);
1313         assertFalse(Notification.areRemoteViewsChanged(n1, n2));
1314 
1315         n1 = new Notification.Builder(mContext, "test").setCustomBigContentView(a);
1316         n2 = new Notification.Builder(mContext, "test").setCustomBigContentView(b);
1317         assertFalse(Notification.areRemoteViewsChanged(n1, n2));
1318 
1319         n1 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(a);
1320         n2 = new Notification.Builder(mContext, "test").setCustomHeadsUpContentView(b);
1321         assertFalse(Notification.areRemoteViewsChanged(n1, n2));
1322     }
1323 
1324     @Test
testActionsDifferent_null()1325     public void testActionsDifferent_null() {
1326         Notification n1 = new Notification.Builder(mContext, "test")
1327                 .build();
1328         Notification n2 = new Notification.Builder(mContext, "test")
1329                 .build();
1330 
1331         assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
1332     }
1333 
1334     @Test
testActionsDifferentSame()1335     public void testActionsDifferentSame() {
1336         PendingIntent intent = PendingIntent.getActivity(
1337                 mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
1338         Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
1339 
1340         Notification n1 = new Notification.Builder(mContext, "test")
1341                 .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
1342                 .build();
1343         Notification n2 = new Notification.Builder(mContext, "test")
1344                 .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
1345                 .build();
1346 
1347         assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
1348     }
1349 
1350     @Test
testActionsDifferentText()1351     public void testActionsDifferentText() {
1352         PendingIntent intent = PendingIntent.getActivity(
1353                 mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
1354         Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
1355 
1356         Notification n1 = new Notification.Builder(mContext, "test")
1357                 .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
1358                 .build();
1359         Notification n2 = new Notification.Builder(mContext, "test")
1360                 .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
1361                 .build();
1362 
1363         assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
1364     }
1365 
1366     @Test
testActionsDifferentSpannables()1367     public void testActionsDifferentSpannables() {
1368         PendingIntent intent = PendingIntent.getActivity(
1369                 mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
1370         Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
1371 
1372         Notification n1 = new Notification.Builder(mContext, "test")
1373                 .addAction(new Notification.Action.Builder(icon,
1374                         new SpannableStringBuilder().append("test1",
1375                                 new StyleSpan(Typeface.BOLD),
1376                                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE),
1377                         intent).build())
1378                 .build();
1379         Notification n2 = new Notification.Builder(mContext, "test")
1380                 .addAction(new Notification.Action.Builder(icon, "test1", intent).build())
1381                 .build();
1382 
1383         assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
1384     }
1385 
1386     @Test
testActionsDifferentNumber()1387     public void testActionsDifferentNumber() {
1388         PendingIntent intent = PendingIntent.getActivity(
1389                 mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
1390         Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
1391 
1392         Notification n1 = new Notification.Builder(mContext, "test")
1393                 .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
1394                 .build();
1395         Notification n2 = new Notification.Builder(mContext, "test")
1396                 .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent).build())
1397                 .addAction(new Notification.Action.Builder(icon, "TEXT 2", intent).build())
1398                 .build();
1399 
1400         assertTrue(Notification.areActionsVisiblyDifferent(n1, n2));
1401     }
1402 
1403     @Test
testActionsDifferentIntent()1404     public void testActionsDifferentIntent() {
1405         PendingIntent intent1 = PendingIntent.getActivity(
1406                 mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
1407         PendingIntent intent2 = PendingIntent.getActivity(
1408                 mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);
1409         Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
1410 
1411         Notification n1 = new Notification.Builder(mContext, "test")
1412                 .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent1).build())
1413                 .build();
1414         Notification n2 = new Notification.Builder(mContext, "test")
1415                 .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent2).build())
1416                 .build();
1417 
1418         assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
1419     }
1420 
1421     @Test
testActionsIgnoresRemoteInputs()1422     public void testActionsIgnoresRemoteInputs() {
1423         PendingIntent intent = PendingIntent.getActivity(
1424                 mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
1425         Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
1426 
1427         Notification n1 = new Notification.Builder(mContext, "test")
1428                 .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
1429                         .addRemoteInput(new RemoteInput.Builder("a")
1430                                 .setChoices(new CharSequence[] {"i", "m"})
1431                                 .build())
1432                         .build())
1433                 .build();
1434         Notification n2 = new Notification.Builder(mContext, "test")
1435                 .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
1436                         .addRemoteInput(new RemoteInput.Builder("a")
1437                                 .setChoices(new CharSequence[] {"t", "m"})
1438                                 .build())
1439                         .build())
1440                 .build();
1441 
1442         assertFalse(Notification.areActionsVisiblyDifferent(n1, n2));
1443     }
1444 
1445     @Test
testFreeformRemoteInputActionPair_noRemoteInput()1446     public void testFreeformRemoteInputActionPair_noRemoteInput() {
1447         PendingIntent intent = PendingIntent.getActivity(
1448                 mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
1449         Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
1450         Notification notification = new Notification.Builder(mContext, "test")
1451                 .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
1452                         .build())
1453                 .build();
1454         Assert.assertNull(notification.findRemoteInputActionPair(false));
1455     }
1456 
1457     @Test
testFreeformRemoteInputActionPair_hasRemoteInput()1458     public void testFreeformRemoteInputActionPair_hasRemoteInput() {
1459         PendingIntent intent = PendingIntent.getActivity(
1460                 mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
1461         Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
1462 
1463         RemoteInput remoteInput = new RemoteInput.Builder("a").build();
1464 
1465         Notification.Action actionWithRemoteInput =
1466                 new Notification.Action.Builder(icon, "TEXT 1", intent)
1467                         .addRemoteInput(remoteInput)
1468                         .addRemoteInput(remoteInput)
1469                         .build();
1470 
1471         Notification.Action actionWithoutRemoteInput =
1472                 new Notification.Action.Builder(icon, "TEXT 2", intent)
1473                         .build();
1474 
1475         Notification notification = new Notification.Builder(mContext, "test")
1476                 .addAction(actionWithoutRemoteInput)
1477                 .addAction(actionWithRemoteInput)
1478                 .build();
1479 
1480         Pair<RemoteInput, Notification.Action> remoteInputActionPair =
1481                 notification.findRemoteInputActionPair(false);
1482 
1483         assertNotNull(remoteInputActionPair);
1484         Assert.assertEquals(remoteInput, remoteInputActionPair.first);
1485         Assert.assertEquals(actionWithRemoteInput, remoteInputActionPair.second);
1486     }
1487 
1488     @Test
testFreeformRemoteInputActionPair_requestFreeform_noFreeformRemoteInput()1489     public void testFreeformRemoteInputActionPair_requestFreeform_noFreeformRemoteInput() {
1490         PendingIntent intent = PendingIntent.getActivity(
1491                 mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
1492         Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
1493         Notification notification = new Notification.Builder(mContext, "test")
1494                 .addAction(new Notification.Action.Builder(icon, "TEXT 1", intent)
1495                         .addRemoteInput(
1496                                 new RemoteInput.Builder("a")
1497                                         .setAllowFreeFormInput(false).build())
1498                         .build())
1499                 .build();
1500         Assert.assertNull(notification.findRemoteInputActionPair(true));
1501     }
1502 
1503     @Test
testFreeformRemoteInputActionPair_requestFreeform_hasFreeformRemoteInput()1504     public void testFreeformRemoteInputActionPair_requestFreeform_hasFreeformRemoteInput() {
1505         PendingIntent intent = PendingIntent.getActivity(
1506                 mContext, 0, new Intent("test1"), PendingIntent.FLAG_IMMUTABLE);;
1507         Icon icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
1508 
1509         RemoteInput remoteInput =
1510                 new RemoteInput.Builder("a").setAllowFreeFormInput(false).build();
1511         RemoteInput freeformRemoteInput =
1512                 new RemoteInput.Builder("b").setAllowFreeFormInput(true).build();
1513 
1514         Notification.Action actionWithFreeformRemoteInput =
1515                 new Notification.Action.Builder(icon, "TEXT 1", intent)
1516                         .addRemoteInput(remoteInput)
1517                         .addRemoteInput(freeformRemoteInput)
1518                         .build();
1519 
1520         Notification.Action actionWithoutFreeformRemoteInput =
1521                 new Notification.Action.Builder(icon, "TEXT 2", intent)
1522                         .addRemoteInput(remoteInput)
1523                         .build();
1524 
1525         Notification notification = new Notification.Builder(mContext, "test")
1526                 .addAction(actionWithoutFreeformRemoteInput)
1527                 .addAction(actionWithFreeformRemoteInput)
1528                 .build();
1529 
1530         Pair<RemoteInput, Notification.Action> remoteInputActionPair =
1531                 notification.findRemoteInputActionPair(true);
1532 
1533         assertNotNull(remoteInputActionPair);
1534         Assert.assertEquals(freeformRemoteInput, remoteInputActionPair.first);
1535         Assert.assertEquals(actionWithFreeformRemoteInput, remoteInputActionPair.second);
1536     }
1537 
1538     // Ensures that extras in a Notification Builder can be updated.
1539     @Test
testExtras_cachedExtrasOverwrittenByUserProvided()1540     public void testExtras_cachedExtrasOverwrittenByUserProvided() {
1541         // Sets the flag to new state.
1542         // TODO(b/169435530): remove this set value once resolved.
1543         SystemProperties.set("persist.sysui.notification.builder_extras_override",
1544                 Boolean.toString(true));
1545         Bundle extras = new Bundle();
1546         extras.putCharSequence(EXTRA_TITLE, "test title");
1547         extras.putCharSequence(EXTRA_SUMMARY_TEXT, "summary text");
1548 
1549         Notification.Builder builder = new Notification.Builder(mContext, "test id")
1550                 .addExtras(extras);
1551 
1552         Notification notification = builder.build();
1553         assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo(
1554                 "test title");
1555         assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo(
1556                 "summary text");
1557 
1558         extras.putCharSequence(EXTRA_TITLE, "new title");
1559         builder.addExtras(extras);
1560         notification = builder.build();
1561         assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo(
1562                 "new title");
1563         assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo(
1564                 "summary text");
1565     }
1566 
1567     // Ensures that extras in a Notification Builder can be updated by an extender.
1568     @Test
testExtras_cachedExtrasOverwrittenByExtender()1569     public void testExtras_cachedExtrasOverwrittenByExtender() {
1570         // Sets the flag to new state.
1571         // TODO(b/169435530): remove this set value once resolved.
1572         SystemProperties.set("persist.sysui.notification.builder_extras_override",
1573                 Boolean.toString(true));
1574         Notification.CarExtender extender = new Notification.CarExtender().setColor(1234);
1575 
1576         Notification notification = new Notification.Builder(mContext, "test id")
1577                 .extend(extender).build();
1578 
1579         extender.setColor(5678);
1580 
1581         Notification.Builder.recoverBuilder(mContext, notification).extend(extender).build();
1582 
1583         Notification.CarExtender recoveredExtender = new Notification.CarExtender(notification);
1584         assertThat(recoveredExtender.getColor()).isEqualTo(5678);
1585     }
1586 
1587     // Validates pre-flag flip behavior, that extras in a Notification Builder cannot be updated.
1588     // TODO(b/169435530): remove this test once resolved.
1589     @Test
testExtras_cachedExtrasOverwrittenByUserProvidedOld()1590     public void testExtras_cachedExtrasOverwrittenByUserProvidedOld() {
1591         // Sets the flag to old state.
1592         SystemProperties.set("persist.sysui.notification.builder_extras_override",
1593                 Boolean.toString(false));
1594 
1595         Bundle extras = new Bundle();
1596         extras.putCharSequence(EXTRA_TITLE, "test title");
1597         extras.putCharSequence(EXTRA_SUMMARY_TEXT, "summary text");
1598 
1599         Notification.Builder builder = new Notification.Builder(mContext, "test id")
1600                 .addExtras(extras);
1601 
1602         Notification notification = builder.build();
1603         assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo(
1604                 "test title");
1605         assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo(
1606                 "summary text");
1607 
1608         extras.putCharSequence(EXTRA_TITLE, "new title");
1609         builder.addExtras(extras);
1610         notification = builder.build();
1611         assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo(
1612                 "test title");
1613         assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo(
1614                 "summary text");
1615     }
1616 
1617     // Validates pre-flag flip behavior, that extras in a Notification Builder cannot be updated
1618     // by an extender.
1619     // TODO(b/169435530): remove this test once resolved.
1620     @Test
testExtras_cachedExtrasOverwrittenByExtenderOld()1621     public void testExtras_cachedExtrasOverwrittenByExtenderOld() {
1622         // Sets the flag to old state.
1623         SystemProperties.set("persist.sysui.notification.builder_extras_override",
1624                 Boolean.toString(false));
1625 
1626         Notification.CarExtender extender = new Notification.CarExtender().setColor(1234);
1627 
1628         Notification notification = new Notification.Builder(mContext, "test id")
1629                 .extend(extender).build();
1630 
1631         extender.setColor(5678);
1632 
1633         Notification.Builder.recoverBuilder(mContext, notification).extend(extender).build();
1634 
1635         Notification.CarExtender recoveredExtender = new Notification.CarExtender(notification);
1636         assertThat(recoveredExtender.getColor()).isEqualTo(1234);
1637     }
1638 
assertValid(Notification.Colors c)1639     private void assertValid(Notification.Colors c) {
1640         // Assert that all colors are populated
1641         assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
1642         assertThat(c.getProtectionColor()).isNotEqualTo(Notification.COLOR_INVALID);
1643         assertThat(c.getPrimaryTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
1644         assertThat(c.getSecondaryTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
1645         assertThat(c.getPrimaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
1646         assertThat(c.getSecondaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
1647         assertThat(c.getTertiaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
1648         assertThat(c.getOnAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
1649         assertThat(c.getErrorColor()).isNotEqualTo(Notification.COLOR_INVALID);
1650         assertThat(c.getContrastColor()).isNotEqualTo(Notification.COLOR_INVALID);
1651         assertThat(c.getRippleAlpha()).isAtLeast(0x00);
1652         assertThat(c.getRippleAlpha()).isAtMost(0xff);
1653 
1654         // Assert that various colors have sufficient contrast with the background
1655         assertContrastIsAtLeast(c.getPrimaryTextColor(), c.getBackgroundColor(), 4.5);
1656         assertContrastIsAtLeast(c.getSecondaryTextColor(), c.getBackgroundColor(), 4.5);
1657         assertContrastIsAtLeast(c.getPrimaryAccentColor(), c.getBackgroundColor(), 4.5);
1658         assertContrastIsAtLeast(c.getErrorColor(), c.getBackgroundColor(), 4.5);
1659         assertContrastIsAtLeast(c.getContrastColor(), c.getBackgroundColor(), 4.5);
1660 
1661         // These colors are only used for emphasized buttons; they do not need contrast
1662         assertContrastIsAtLeast(c.getSecondaryAccentColor(), c.getBackgroundColor(), 1);
1663         assertContrastIsAtLeast(c.getTertiaryAccentColor(), c.getBackgroundColor(), 1);
1664 
1665         // The text that is used within the accent color DOES need to have contrast
1666         assertContrastIsAtLeast(c.getOnAccentTextColor(), c.getTertiaryAccentColor(), 4.5);
1667     }
1668 
resolveColorsInNightMode(boolean nightMode, Notification.Colors c, int rawColor, boolean colorized)1669     private void resolveColorsInNightMode(boolean nightMode, Notification.Colors c, int rawColor,
1670             boolean colorized) {
1671         runInNightMode(nightMode,
1672                 () -> c.resolvePalette(mContext, rawColor, colorized, nightMode));
1673     }
1674 
runInNightMode(boolean nightMode, Runnable task)1675     private void runInNightMode(boolean nightMode, Runnable task) {
1676         final String initialNightMode = changeNightMode(nightMode);
1677         try {
1678             Configuration currentConfig = mContext.getResources().getConfiguration();
1679             boolean isNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
1680                     == Configuration.UI_MODE_NIGHT_YES;
1681             assertEquals(nightMode, isNightMode);
1682             task.run();
1683         } finally {
1684             runShellCommand("cmd uimode night " + initialNightMode);
1685         }
1686     }
1687 
1688 
1689     // Change the night mode and return the previous mode
changeNightMode(boolean nightMode)1690     private String changeNightMode(boolean nightMode) {
1691         final String nightModeText = runShellCommand("cmd uimode night");
1692         final String[] nightModeSplit = nightModeText.split(":");
1693         if (nightModeSplit.length != 2) {
1694             fail("Failed to get initial night mode value from " + nightModeText);
1695         }
1696         String previousMode = nightModeSplit[1].trim();
1697         runShellCommand("cmd uimode night " + (nightMode ? "yes" : "no"));
1698         return previousMode;
1699     }
1700 
1701     /**
1702       * Writes an arbitrary {@link Parcelable} into a {@link Parcel} using its writeToParcel
1703       * method before reading it out again to check that it was sent properly.
1704       */
writeAndReadParcelable(T original)1705     private static <T extends Parcelable> T writeAndReadParcelable(T original) {
1706         Parcel p = Parcel.obtain();
1707         p.writeParcelable(original, /* flags */ 0);
1708         p.setDataPosition(0);
1709         return p.readParcelable(/* classLoader */ null);
1710     }
1711 
1712     /**
1713      * Creates a Notification.Action by mocking initial dependencies and then applying
1714      * transformations if they're defined.
1715      */
makeNotificationAction( @ullable Consumer<Notification.Action.Builder> transformation)1716     private Notification.Action makeNotificationAction(
1717             @Nullable Consumer<Notification.Action.Builder> transformation) {
1718         Notification.Action.Builder actionBuilder =
1719                 new Notification.Action.Builder(null, "Test Title", null);
1720         if (transformation != null) {
1721             transformation.accept(actionBuilder);
1722         }
1723         return actionBuilder.build();
1724     }
1725 
1726     /**
1727      * Creates a PendingIntent with the given action.
1728      */
createPendingIntent(String action)1729     private PendingIntent createPendingIntent(String action) {
1730         return PendingIntent.getActivity(mContext, 0,
1731                 new Intent(action).setPackage(mContext.getPackageName()),
1732                 PendingIntent.FLAG_MUTABLE);
1733     }
1734 }
1735