blob: ccadcc35f37a6a7ec329451dcd2402f467b08cc6 [file] [log] [blame]
Selim Cinek2630dc72017-04-20 15:16:10 -07001/*
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
Rohan Shah20790b82018-07-02 17:21:04 -070017package com.android.systemui.statusbar.notification.row;
Selim Cinek2630dc72017-04-20 15:16:10 -070018
Ned Burns1a5e22f2019-02-14 15:11:52 -050019import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_ALL;
Ned Burns1a5e22f2019-02-14 15:11:52 -050020import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_EXPANDED;
21import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_HEADS_UP;
22import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_PUBLIC;
Selim Cinek2630dc72017-04-20 15:16:10 -070023
Kevind4660b22018-09-27 10:57:35 -070024import static org.junit.Assert.assertEquals;
25import static org.junit.Assert.assertNotNull;
Jason Monk6dceace2018-05-15 20:24:07 -040026import static org.junit.Assert.assertTrue;
Selim Cinek2630dc72017-04-20 15:16:10 -070027import static org.mockito.Mockito.spy;
28import static org.mockito.Mockito.times;
29import static org.mockito.Mockito.verify;
30
31import android.app.Notification;
32import android.content.Context;
Selim Cinekd246bed2017-06-19 16:58:35 -070033import android.os.CancellationSignal;
34import android.os.Handler;
35import android.os.Looper;
Selim Cinek2630dc72017-04-20 15:16:10 -070036import android.service.notification.StatusBarNotification;
Jason Monk6dceace2018-05-15 20:24:07 -040037import android.testing.AndroidTestingRunner;
38import android.testing.TestableLooper.RunWithLooper;
Kevind4660b22018-09-27 10:57:35 -070039import android.util.ArrayMap;
Selim Cinekd246bed2017-06-19 16:58:35 -070040import android.view.View;
41import android.view.ViewGroup;
Selim Cinek2630dc72017-04-20 15:16:10 -070042import android.widget.RemoteViews;
43
Brett Chabot84151d92019-02-27 15:37:59 -080044import androidx.test.filters.SmallTest;
45
Jason Monkfba8faf2017-05-23 10:42:59 -040046import com.android.systemui.SysuiTestCase;
Jason Monkb05395f2017-07-11 10:05:03 -040047import com.android.systemui.statusbar.InflationTask;
Selim Cinek2630dc72017-04-20 15:16:10 -070048import com.android.systemui.statusbar.NotificationTestHelper;
Ned Burnsf81c4c42019-01-07 14:10:43 -050049import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Ned Burns1a5e22f2019-02-14 15:11:52 -050050import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationCallback;
Sunny Goyal87fccf02019-08-13 17:39:10 -070051import com.android.systemui.tests.R;
Selim Cinek2630dc72017-04-20 15:16:10 -070052
53import org.junit.Assert;
54import org.junit.Before;
Geoffrey Pitsch351a3212017-05-22 15:20:20 -040055import org.junit.Ignore;
Selim Cinek2630dc72017-04-20 15:16:10 -070056import org.junit.Test;
57import org.junit.runner.RunWith;
58
Selim Cinekd246bed2017-06-19 16:58:35 -070059import java.util.HashMap;
Selim Cinek2630dc72017-04-20 15:16:10 -070060import java.util.concurrent.CountDownLatch;
Selim Cinekd246bed2017-06-19 16:58:35 -070061import java.util.concurrent.Executor;
Jason Monk6dceace2018-05-15 20:24:07 -040062import java.util.concurrent.TimeUnit;
Selim Cinek2630dc72017-04-20 15:16:10 -070063
64@SmallTest
Jason Monk6dceace2018-05-15 20:24:07 -040065@RunWith(AndroidTestingRunner.class)
66@RunWithLooper(setAsMainLooper = true)
Ned Burns1a5e22f2019-02-14 15:11:52 -050067public class NotificationContentInflaterTest extends SysuiTestCase {
Selim Cinek2630dc72017-04-20 15:16:10 -070068
Ned Burns1a5e22f2019-02-14 15:11:52 -050069 private NotificationContentInflater mNotificationInflater;
Selim Cinek2630dc72017-04-20 15:16:10 -070070 private Notification.Builder mBuilder;
71 private ExpandableNotificationRow mRow;
72
73 @Before
74 public void setUp() throws Exception {
Selim Cinek2630dc72017-04-20 15:16:10 -070075 mBuilder = new Notification.Builder(mContext).setSmallIcon(
76 R.drawable.ic_person)
77 .setContentTitle("Title")
78 .setContentText("Text")
79 .setStyle(new Notification.BigTextStyle().bigText("big text"));
80 ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow(
81 mBuilder.build());
82 mRow = spy(row);
Ned Burns1a5e22f2019-02-14 15:11:52 -050083 mNotificationInflater = new NotificationContentInflater(mRow);
84 mNotificationInflater.setInflationCallback(new InflationCallback() {
Selim Cinek2630dc72017-04-20 15:16:10 -070085 @Override
86 public void handleInflationException(StatusBarNotification notification,
Selim Cinek01d3da62017-04-28 15:03:48 -070087 Exception e) {
Selim Cinek2630dc72017-04-20 15:16:10 -070088 }
89
90 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -050091 public void onAsyncInflationFinished(NotificationEntry entry,
Ned Burns1a5e22f2019-02-14 15:11:52 -050092 @NotificationContentInflater.InflationFlag int inflatedFlags) {
Selim Cinek2630dc72017-04-20 15:16:10 -070093 }
94 });
95 }
96
97 @Test
98 public void testIncreasedHeadsUpBeingUsed() {
99 mNotificationInflater.setUsesIncreasedHeadsUpHeight(true);
100 Notification.Builder builder = spy(mBuilder);
Ned Burns342c3a02019-02-15 18:08:39 -0500101 mNotificationInflater.inflateNotificationViews(
Selim Cinek69d11c22019-06-10 23:52:32 -0700102 true /* inflateSynchronously */,
Ned Burns342c3a02019-02-15 18:08:39 -0500103 FLAG_CONTENT_VIEW_ALL,
104 builder,
105 mContext);
Selim Cinek2630dc72017-04-20 15:16:10 -0700106 verify(builder).createHeadsUpContentView(true);
107 }
108
109 @Test
110 public void testIncreasedHeightBeingUsed() {
111 mNotificationInflater.setUsesIncreasedHeight(true);
112 Notification.Builder builder = spy(mBuilder);
Ned Burns342c3a02019-02-15 18:08:39 -0500113 mNotificationInflater.inflateNotificationViews(
Selim Cinek69d11c22019-06-10 23:52:32 -0700114 true /* inflateSynchronously */,
Ned Burns342c3a02019-02-15 18:08:39 -0500115 FLAG_CONTENT_VIEW_ALL,
116 builder,
117 mContext);
Selim Cinek2630dc72017-04-20 15:16:10 -0700118 verify(builder).createContentView(true);
119 }
120
121 @Test
122 public void testInflationCallsUpdated() throws Exception {
123 runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
124 mNotificationInflater);
125 verify(mRow).onNotificationUpdated();
126 }
127
128 @Test
Kevind4660b22018-09-27 10:57:35 -0700129 public void testInflationOnlyInflatesSetFlags() throws Exception {
Kevind5022f92018-10-08 18:30:26 -0700130 mNotificationInflater.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP,
Kevind4660b22018-09-27 10:57:35 -0700131 true /* shouldInflate */);
132 runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
133 mNotificationInflater);
134
135 assertNotNull(mRow.getPrivateLayout().getHeadsUpChild());
Selim Cinek2630dc72017-04-20 15:16:10 -0700136 verify(mRow).onNotificationUpdated();
137 }
138
139 @Test
140 public void testInflationThrowsErrorDoesntCallUpdated() throws Exception {
141 mRow.getPrivateLayout().removeAllViews();
142 mRow.getStatusBarNotification().getNotification().contentView
Selim Cinekdc1231c2017-04-27 17:30:50 -0700143 = new RemoteViews(mContext.getPackageName(), R.layout.status_bar);
Selim Cinek2630dc72017-04-20 15:16:10 -0700144 runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
145 true /* expectingException */, mNotificationInflater);
Jason Monk6dceace2018-05-15 20:24:07 -0400146 assertTrue(mRow.getPrivateLayout().getChildCount() == 0);
Selim Cinek2630dc72017-04-20 15:16:10 -0700147 verify(mRow, times(0)).onNotificationUpdated();
148 }
149
Selim Cinekdc1231c2017-04-27 17:30:50 -0700150 @Test
151 public void testAsyncTaskRemoved() throws Exception {
Selim Cinek0f66a4c2017-04-28 19:26:28 -0700152 mRow.getEntry().abortTask();
Selim Cinekdc1231c2017-04-27 17:30:50 -0700153 runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
154 mNotificationInflater);
Selim Cinek67ff2482017-05-25 10:27:28 -0700155 verify(mRow).onNotificationUpdated();
156 }
157
158 @Test
159 public void testRemovedNotInflated() throws Exception {
160 mRow.setRemoved();
Selim Cinek69d11c22019-06-10 23:52:32 -0700161 mNotificationInflater.setInflateSynchronously(true);
Selim Cinek67ff2482017-05-25 10:27:28 -0700162 mNotificationInflater.inflateNotificationViews();
163 Assert.assertNull(mRow.getEntry().getRunningTask());
164 }
165
Selim Cinekd246bed2017-06-19 16:58:35 -0700166 @Test
Jason Monkb05395f2017-07-11 10:05:03 -0400167 @Ignore
Selim Cinekd246bed2017-06-19 16:58:35 -0700168 public void testInflationIsRetriedIfAsyncFails() throws Exception {
Ned Burns1a5e22f2019-02-14 15:11:52 -0500169 NotificationContentInflater.InflationProgress result =
170 new NotificationContentInflater.InflationProgress();
Selim Cinekd246bed2017-06-19 16:58:35 -0700171 result.packageContext = mContext;
172 CountDownLatch countDownLatch = new CountDownLatch(1);
Ned Burns342c3a02019-02-15 18:08:39 -0500173 NotificationContentInflater.applyRemoteView(
174 false /* inflateSynchronously */,
175 result,
176 FLAG_CONTENT_VIEW_EXPANDED,
177 0,
Selim Cinekc3fec682019-06-06 18:11:07 -0700178 new ArrayMap() /* cachedContentViews */, mRow,
Sunny Goyal43c97042018-08-23 15:21:26 -0700179 true /* isNewView */, (v, p, r) -> true,
Ned Burns1a5e22f2019-02-14 15:11:52 -0500180 new InflationCallback() {
Selim Cinekd246bed2017-06-19 16:58:35 -0700181 @Override
182 public void handleInflationException(StatusBarNotification notification,
183 Exception e) {
184 countDownLatch.countDown();
185 throw new RuntimeException("No Exception expected");
186 }
187
188 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -0500189 public void onAsyncInflationFinished(NotificationEntry entry,
Ned Burns1a5e22f2019-02-14 15:11:52 -0500190 @NotificationContentInflater.InflationFlag int inflatedFlags) {
Selim Cinekd246bed2017-06-19 16:58:35 -0700191 countDownLatch.countDown();
192 }
Kevind4660b22018-09-27 10:57:35 -0700193 }, mRow.getPrivateLayout(), null, null, new HashMap<>(),
Ned Burns1a5e22f2019-02-14 15:11:52 -0500194 new NotificationContentInflater.ApplyCallback() {
Selim Cinekd246bed2017-06-19 16:58:35 -0700195 @Override
196 public void setResultView(View v) {
197 }
198
199 @Override
200 public RemoteViews getRemoteView() {
201 return new AsyncFailRemoteView(mContext.getPackageName(),
202 R.layout.custom_view_dark);
203 }
204 });
Jason Monk6dceace2018-05-15 20:24:07 -0400205 assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
Selim Cinekd246bed2017-06-19 16:58:35 -0700206 }
Selim Cinek67ff2482017-05-25 10:27:28 -0700207
Kevin38ce6fa2018-10-17 16:00:14 -0700208 @Test
209 public void testUpdateNeedsRedactionReinflatesChangedContentViews() {
Kevin38ce6fa2018-10-17 16:00:14 -0700210 mNotificationInflater.updateInflationFlag(FLAG_CONTENT_VIEW_PUBLIC, true);
211 mNotificationInflater.updateNeedsRedaction(true);
212
Ned Burns1a5e22f2019-02-14 15:11:52 -0500213 NotificationContentInflater.AsyncInflationTask asyncInflationTask =
214 (NotificationContentInflater.AsyncInflationTask) mRow.getEntry().getRunningTask();
Selim Cinekc3fec682019-06-06 18:11:07 -0700215 assertEquals(FLAG_CONTENT_VIEW_PUBLIC, asyncInflationTask.getReInflateFlags());
Kevin38ce6fa2018-10-17 16:00:14 -0700216 asyncInflationTask.abort();
217 }
218
Selim Cinek21f33662017-09-08 13:24:21 -0700219 /* Cancelling requires us to be on the UI thread otherwise we might have a race */
Selim Cinek67ff2482017-05-25 10:27:28 -0700220 @Test
Kevind4660b22018-09-27 10:57:35 -0700221 public void testSupersedesExistingTask() {
Kevind5022f92018-10-08 18:30:26 -0700222 mNotificationInflater.addInflationFlags(FLAG_CONTENT_VIEW_ALL);
Selim Cinek67ff2482017-05-25 10:27:28 -0700223 mNotificationInflater.inflateNotificationViews();
Kevind4660b22018-09-27 10:57:35 -0700224
225 // Trigger inflation of content and expanded only.
Selim Cinek67ff2482017-05-25 10:27:28 -0700226 mNotificationInflater.setIsLowPriority(true);
227 mNotificationInflater.setIsChildInGroup(true);
Kevind4660b22018-09-27 10:57:35 -0700228
Selim Cinek67ff2482017-05-25 10:27:28 -0700229 InflationTask runningTask = mRow.getEntry().getRunningTask();
Ned Burns1a5e22f2019-02-14 15:11:52 -0500230 NotificationContentInflater.AsyncInflationTask asyncInflationTask =
231 (NotificationContentInflater.AsyncInflationTask) runningTask;
Kevind4660b22018-09-27 10:57:35 -0700232 assertEquals("Successive inflations don't inherit the previous flags!",
Kevin38ce6fa2018-10-17 16:00:14 -0700233 FLAG_CONTENT_VIEW_ALL, asyncInflationTask.getReInflateFlags());
Selim Cinek67ff2482017-05-25 10:27:28 -0700234 runningTask.abort();
Selim Cinekdc1231c2017-04-27 17:30:50 -0700235 }
236
Selim Cinekfc8073c2017-08-16 17:50:20 -0700237 @Test
238 public void doesntReapplyDisallowedRemoteView() throws Exception {
239 mBuilder.setStyle(new Notification.MediaStyle());
240 RemoteViews mediaView = mBuilder.createContentView();
241 mBuilder.setStyle(new Notification.DecoratedCustomViewStyle());
242 mBuilder.setCustomContentView(new RemoteViews(getContext().getPackageName(),
243 R.layout.custom_view_dark));
244 RemoteViews decoratedMediaView = mBuilder.createContentView();
245 Assert.assertFalse("The decorated media style doesn't allow a view to be reapplied!",
Ned Burns1a5e22f2019-02-14 15:11:52 -0500246 NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView));
Selim Cinekfc8073c2017-08-16 17:50:20 -0700247 }
248
Selim Cinek2630dc72017-04-20 15:16:10 -0700249 public static void runThenWaitForInflation(Runnable block,
Ned Burns1a5e22f2019-02-14 15:11:52 -0500250 NotificationContentInflater inflater) throws Exception {
Selim Cinek2630dc72017-04-20 15:16:10 -0700251 runThenWaitForInflation(block, false /* expectingException */, inflater);
252 }
253
254 private static void runThenWaitForInflation(Runnable block, boolean expectingException,
Ned Burns1a5e22f2019-02-14 15:11:52 -0500255 NotificationContentInflater inflater) throws Exception {
Selim Cinek2630dc72017-04-20 15:16:10 -0700256 CountDownLatch countDownLatch = new CountDownLatch(1);
257 final ExceptionHolder exceptionHolder = new ExceptionHolder();
Ned Burns342c3a02019-02-15 18:08:39 -0500258 inflater.setInflateSynchronously(true);
Ned Burns1a5e22f2019-02-14 15:11:52 -0500259 inflater.setInflationCallback(new InflationCallback() {
Selim Cinek2630dc72017-04-20 15:16:10 -0700260 @Override
261 public void handleInflationException(StatusBarNotification notification,
Selim Cinek01d3da62017-04-28 15:03:48 -0700262 Exception e) {
Selim Cinek2630dc72017-04-20 15:16:10 -0700263 if (!expectingException) {
264 exceptionHolder.setException(e);
265 }
266 countDownLatch.countDown();
267 }
268
269 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -0500270 public void onAsyncInflationFinished(NotificationEntry entry,
Ned Burns1a5e22f2019-02-14 15:11:52 -0500271 @NotificationContentInflater.InflationFlag int inflatedFlags) {
Selim Cinek2630dc72017-04-20 15:16:10 -0700272 if (expectingException) {
273 exceptionHolder.setException(new RuntimeException(
274 "Inflation finished even though there should be an error"));
275 }
276 countDownLatch.countDown();
277 }
278 });
279 block.run();
Jason Monk6dceace2018-05-15 20:24:07 -0400280 assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
Selim Cinek2630dc72017-04-20 15:16:10 -0700281 if (exceptionHolder.mException != null) {
282 throw exceptionHolder.mException;
283 }
284 }
285
286 private static class ExceptionHolder {
287 private Exception mException;
288
289 public void setException(Exception exception) {
290 mException = exception;
291 }
292 }
Selim Cinekd246bed2017-06-19 16:58:35 -0700293
294 private class AsyncFailRemoteView extends RemoteViews {
Jason Monk6dceace2018-05-15 20:24:07 -0400295 Handler mHandler = Handler.createAsync(Looper.getMainLooper());
Selim Cinekd246bed2017-06-19 16:58:35 -0700296
297 public AsyncFailRemoteView(String packageName, int layoutId) {
298 super(packageName, layoutId);
299 }
300
301 @Override
302 public View apply(Context context, ViewGroup parent) {
303 return super.apply(context, parent);
304 }
305
306 @Override
307 public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor,
308 OnViewAppliedListener listener, OnClickHandler handler) {
309 mHandler.post(() -> listener.onError(new RuntimeException("Failed to inflate async")));
310 return new CancellationSignal();
311 }
312
313 @Override
314 public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor,
315 OnViewAppliedListener listener) {
316 return applyAsync(context, parent, executor, listener, null);
317 }
318 }
Selim Cinek2630dc72017-04-20 15:16:10 -0700319}