blob: 06ff0478a8042d6e81fb9733705b0165410b0947 [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;
20import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_AMBIENT;
21import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_EXPANDED;
22import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_HEADS_UP;
23import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_PUBLIC;
Selim Cinek2630dc72017-04-20 15:16:10 -070024
Kevind4660b22018-09-27 10:57:35 -070025import static org.junit.Assert.assertEquals;
26import static org.junit.Assert.assertNotNull;
27import static org.junit.Assert.assertNull;
Jason Monk6dceace2018-05-15 20:24:07 -040028import static org.junit.Assert.assertTrue;
Selim Cinek2630dc72017-04-20 15:16:10 -070029import static org.mockito.Mockito.spy;
30import static org.mockito.Mockito.times;
31import static org.mockito.Mockito.verify;
32
33import android.app.Notification;
34import android.content.Context;
Selim Cinekd246bed2017-06-19 16:58:35 -070035import android.os.CancellationSignal;
36import android.os.Handler;
37import android.os.Looper;
Selim Cinek2630dc72017-04-20 15:16:10 -070038import android.service.notification.StatusBarNotification;
Jason Monk6dceace2018-05-15 20:24:07 -040039import android.testing.AndroidTestingRunner;
40import android.testing.TestableLooper.RunWithLooper;
Kevind4660b22018-09-27 10:57:35 -070041import android.util.ArrayMap;
Selim Cinekd246bed2017-06-19 16:58:35 -070042import android.view.View;
43import android.view.ViewGroup;
Selim Cinek2630dc72017-04-20 15:16:10 -070044import android.widget.RemoteViews;
45
Brett Chabot84151d92019-02-27 15:37:59 -080046import androidx.test.filters.SmallTest;
47
Selim Cinek2630dc72017-04-20 15:16:10 -070048import com.android.systemui.R;
Jason Monkfba8faf2017-05-23 10:42:59 -040049import com.android.systemui.SysuiTestCase;
Jason Monkb05395f2017-07-11 10:05:03 -040050import com.android.systemui.statusbar.InflationTask;
Selim Cinek2630dc72017-04-20 15:16:10 -070051import com.android.systemui.statusbar.NotificationTestHelper;
Ned Burnsf81c4c42019-01-07 14:10:43 -050052import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Ned Burns1a5e22f2019-02-14 15:11:52 -050053import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationCallback;
Selim Cinek2630dc72017-04-20 15:16:10 -070054
55import org.junit.Assert;
56import org.junit.Before;
Geoffrey Pitsch351a3212017-05-22 15:20:20 -040057import org.junit.Ignore;
Selim Cinek2630dc72017-04-20 15:16:10 -070058import org.junit.Test;
59import org.junit.runner.RunWith;
60
Selim Cinekd246bed2017-06-19 16:58:35 -070061import java.util.HashMap;
Selim Cinek2630dc72017-04-20 15:16:10 -070062import java.util.concurrent.CountDownLatch;
Selim Cinekd246bed2017-06-19 16:58:35 -070063import java.util.concurrent.Executor;
Jason Monk6dceace2018-05-15 20:24:07 -040064import java.util.concurrent.TimeUnit;
Selim Cinek2630dc72017-04-20 15:16:10 -070065
66@SmallTest
Jason Monk6dceace2018-05-15 20:24:07 -040067@RunWith(AndroidTestingRunner.class)
68@RunWithLooper(setAsMainLooper = true)
Ned Burns1a5e22f2019-02-14 15:11:52 -050069public class NotificationContentInflaterTest extends SysuiTestCase {
Selim Cinek2630dc72017-04-20 15:16:10 -070070
Ned Burns1a5e22f2019-02-14 15:11:52 -050071 private NotificationContentInflater mNotificationInflater;
Selim Cinek2630dc72017-04-20 15:16:10 -070072 private Notification.Builder mBuilder;
73 private ExpandableNotificationRow mRow;
74
75 @Before
76 public void setUp() throws Exception {
Selim Cinek2630dc72017-04-20 15:16:10 -070077 mBuilder = new Notification.Builder(mContext).setSmallIcon(
78 R.drawable.ic_person)
79 .setContentTitle("Title")
80 .setContentText("Text")
81 .setStyle(new Notification.BigTextStyle().bigText("big text"));
82 ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow(
83 mBuilder.build());
84 mRow = spy(row);
Ned Burns1a5e22f2019-02-14 15:11:52 -050085 mNotificationInflater = new NotificationContentInflater(mRow);
86 mNotificationInflater.setInflationCallback(new InflationCallback() {
Selim Cinek2630dc72017-04-20 15:16:10 -070087 @Override
88 public void handleInflationException(StatusBarNotification notification,
Selim Cinek01d3da62017-04-28 15:03:48 -070089 Exception e) {
Selim Cinek2630dc72017-04-20 15:16:10 -070090 }
91
92 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -050093 public void onAsyncInflationFinished(NotificationEntry entry,
Ned Burns1a5e22f2019-02-14 15:11:52 -050094 @NotificationContentInflater.InflationFlag int inflatedFlags) {
Selim Cinek2630dc72017-04-20 15:16:10 -070095 }
96 });
97 }
98
99 @Test
100 public void testIncreasedHeadsUpBeingUsed() {
101 mNotificationInflater.setUsesIncreasedHeadsUpHeight(true);
102 Notification.Builder builder = spy(mBuilder);
Ned Burns342c3a02019-02-15 18:08:39 -0500103 mNotificationInflater.inflateNotificationViews(
Selim Cinek69d11c22019-06-10 23:52:32 -0700104 true /* inflateSynchronously */,
Ned Burns342c3a02019-02-15 18:08:39 -0500105 FLAG_CONTENT_VIEW_ALL,
106 builder,
107 mContext);
Selim Cinek2630dc72017-04-20 15:16:10 -0700108 verify(builder).createHeadsUpContentView(true);
109 }
110
111 @Test
112 public void testIncreasedHeightBeingUsed() {
113 mNotificationInflater.setUsesIncreasedHeight(true);
114 Notification.Builder builder = spy(mBuilder);
Ned Burns342c3a02019-02-15 18:08:39 -0500115 mNotificationInflater.inflateNotificationViews(
Selim Cinek69d11c22019-06-10 23:52:32 -0700116 true /* inflateSynchronously */,
Ned Burns342c3a02019-02-15 18:08:39 -0500117 FLAG_CONTENT_VIEW_ALL,
118 builder,
119 mContext);
Selim Cinek2630dc72017-04-20 15:16:10 -0700120 verify(builder).createContentView(true);
121 }
122
123 @Test
124 public void testInflationCallsUpdated() throws Exception {
125 runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
126 mNotificationInflater);
127 verify(mRow).onNotificationUpdated();
128 }
129
130 @Test
Kevind4660b22018-09-27 10:57:35 -0700131 public void testInflationOnlyInflatesSetFlags() throws Exception {
Kevind5022f92018-10-08 18:30:26 -0700132 mNotificationInflater.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP,
Kevind4660b22018-09-27 10:57:35 -0700133 true /* shouldInflate */);
134 runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
135 mNotificationInflater);
136
137 assertNotNull(mRow.getPrivateLayout().getHeadsUpChild());
138 assertNull(mRow.getShowingLayout().getAmbientChild());
Selim Cinek2630dc72017-04-20 15:16:10 -0700139 verify(mRow).onNotificationUpdated();
140 }
141
142 @Test
143 public void testInflationThrowsErrorDoesntCallUpdated() throws Exception {
144 mRow.getPrivateLayout().removeAllViews();
145 mRow.getStatusBarNotification().getNotification().contentView
Selim Cinekdc1231c2017-04-27 17:30:50 -0700146 = new RemoteViews(mContext.getPackageName(), R.layout.status_bar);
Selim Cinek2630dc72017-04-20 15:16:10 -0700147 runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
148 true /* expectingException */, mNotificationInflater);
Jason Monk6dceace2018-05-15 20:24:07 -0400149 assertTrue(mRow.getPrivateLayout().getChildCount() == 0);
Selim Cinek2630dc72017-04-20 15:16:10 -0700150 verify(mRow, times(0)).onNotificationUpdated();
151 }
152
Selim Cinekdc1231c2017-04-27 17:30:50 -0700153 @Test
154 public void testAsyncTaskRemoved() throws Exception {
Selim Cinek0f66a4c2017-04-28 19:26:28 -0700155 mRow.getEntry().abortTask();
Selim Cinekdc1231c2017-04-27 17:30:50 -0700156 runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
157 mNotificationInflater);
Selim Cinek67ff2482017-05-25 10:27:28 -0700158 verify(mRow).onNotificationUpdated();
159 }
160
161 @Test
162 public void testRemovedNotInflated() throws Exception {
163 mRow.setRemoved();
Selim Cinek69d11c22019-06-10 23:52:32 -0700164 mNotificationInflater.setInflateSynchronously(true);
Selim Cinek67ff2482017-05-25 10:27:28 -0700165 mNotificationInflater.inflateNotificationViews();
166 Assert.assertNull(mRow.getEntry().getRunningTask());
167 }
168
Selim Cinekd246bed2017-06-19 16:58:35 -0700169 @Test
Jason Monkb05395f2017-07-11 10:05:03 -0400170 @Ignore
Selim Cinekd246bed2017-06-19 16:58:35 -0700171 public void testInflationIsRetriedIfAsyncFails() throws Exception {
Ned Burns1a5e22f2019-02-14 15:11:52 -0500172 NotificationContentInflater.InflationProgress result =
173 new NotificationContentInflater.InflationProgress();
Selim Cinekd246bed2017-06-19 16:58:35 -0700174 result.packageContext = mContext;
175 CountDownLatch countDownLatch = new CountDownLatch(1);
Ned Burns342c3a02019-02-15 18:08:39 -0500176 NotificationContentInflater.applyRemoteView(
177 false /* inflateSynchronously */,
178 result,
179 FLAG_CONTENT_VIEW_EXPANDED,
180 0,
Kevind4660b22018-09-27 10:57:35 -0700181 new ArrayMap() /* cachedContentViews */, mRow, false /* redactAmbient */,
Sunny Goyal43c97042018-08-23 15:21:26 -0700182 true /* isNewView */, (v, p, r) -> true,
Ned Burns1a5e22f2019-02-14 15:11:52 -0500183 new InflationCallback() {
Selim Cinekd246bed2017-06-19 16:58:35 -0700184 @Override
185 public void handleInflationException(StatusBarNotification notification,
186 Exception e) {
187 countDownLatch.countDown();
188 throw new RuntimeException("No Exception expected");
189 }
190
191 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -0500192 public void onAsyncInflationFinished(NotificationEntry entry,
Ned Burns1a5e22f2019-02-14 15:11:52 -0500193 @NotificationContentInflater.InflationFlag int inflatedFlags) {
Selim Cinekd246bed2017-06-19 16:58:35 -0700194 countDownLatch.countDown();
195 }
Kevind4660b22018-09-27 10:57:35 -0700196 }, mRow.getPrivateLayout(), null, null, new HashMap<>(),
Ned Burns1a5e22f2019-02-14 15:11:52 -0500197 new NotificationContentInflater.ApplyCallback() {
Selim Cinekd246bed2017-06-19 16:58:35 -0700198 @Override
199 public void setResultView(View v) {
200 }
201
202 @Override
203 public RemoteViews getRemoteView() {
204 return new AsyncFailRemoteView(mContext.getPackageName(),
205 R.layout.custom_view_dark);
206 }
207 });
Jason Monk6dceace2018-05-15 20:24:07 -0400208 assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
Selim Cinekd246bed2017-06-19 16:58:35 -0700209 }
Selim Cinek67ff2482017-05-25 10:27:28 -0700210
Kevin38ce6fa2018-10-17 16:00:14 -0700211 @Test
212 public void testUpdateNeedsRedactionReinflatesChangedContentViews() {
213 mNotificationInflater.updateInflationFlag(FLAG_CONTENT_VIEW_AMBIENT, true);
214 mNotificationInflater.updateInflationFlag(FLAG_CONTENT_VIEW_PUBLIC, true);
215 mNotificationInflater.updateNeedsRedaction(true);
216
Ned Burns1a5e22f2019-02-14 15:11:52 -0500217 NotificationContentInflater.AsyncInflationTask asyncInflationTask =
218 (NotificationContentInflater.AsyncInflationTask) mRow.getEntry().getRunningTask();
Kevin38ce6fa2018-10-17 16:00:14 -0700219 assertEquals(FLAG_CONTENT_VIEW_AMBIENT | FLAG_CONTENT_VIEW_PUBLIC,
220 asyncInflationTask.getReInflateFlags());
221 asyncInflationTask.abort();
222 }
223
Selim Cinek21f33662017-09-08 13:24:21 -0700224 /* Cancelling requires us to be on the UI thread otherwise we might have a race */
Selim Cinek67ff2482017-05-25 10:27:28 -0700225 @Test
Kevind4660b22018-09-27 10:57:35 -0700226 public void testSupersedesExistingTask() {
Kevind5022f92018-10-08 18:30:26 -0700227 mNotificationInflater.addInflationFlags(FLAG_CONTENT_VIEW_ALL);
Selim Cinek67ff2482017-05-25 10:27:28 -0700228 mNotificationInflater.inflateNotificationViews();
Kevind4660b22018-09-27 10:57:35 -0700229
230 // Trigger inflation of content and expanded only.
Selim Cinek67ff2482017-05-25 10:27:28 -0700231 mNotificationInflater.setIsLowPriority(true);
232 mNotificationInflater.setIsChildInGroup(true);
Kevind4660b22018-09-27 10:57:35 -0700233
Selim Cinek67ff2482017-05-25 10:27:28 -0700234 InflationTask runningTask = mRow.getEntry().getRunningTask();
Ned Burns1a5e22f2019-02-14 15:11:52 -0500235 NotificationContentInflater.AsyncInflationTask asyncInflationTask =
236 (NotificationContentInflater.AsyncInflationTask) runningTask;
Kevind4660b22018-09-27 10:57:35 -0700237 assertEquals("Successive inflations don't inherit the previous flags!",
Kevin38ce6fa2018-10-17 16:00:14 -0700238 FLAG_CONTENT_VIEW_ALL, asyncInflationTask.getReInflateFlags());
Selim Cinek67ff2482017-05-25 10:27:28 -0700239 runningTask.abort();
Selim Cinekdc1231c2017-04-27 17:30:50 -0700240 }
241
Selim Cinekfc8073c2017-08-16 17:50:20 -0700242 @Test
243 public void doesntReapplyDisallowedRemoteView() throws Exception {
244 mBuilder.setStyle(new Notification.MediaStyle());
245 RemoteViews mediaView = mBuilder.createContentView();
246 mBuilder.setStyle(new Notification.DecoratedCustomViewStyle());
247 mBuilder.setCustomContentView(new RemoteViews(getContext().getPackageName(),
248 R.layout.custom_view_dark));
249 RemoteViews decoratedMediaView = mBuilder.createContentView();
250 Assert.assertFalse("The decorated media style doesn't allow a view to be reapplied!",
Ned Burns1a5e22f2019-02-14 15:11:52 -0500251 NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView));
Selim Cinekfc8073c2017-08-16 17:50:20 -0700252 }
253
Selim Cinek2630dc72017-04-20 15:16:10 -0700254 public static void runThenWaitForInflation(Runnable block,
Ned Burns1a5e22f2019-02-14 15:11:52 -0500255 NotificationContentInflater inflater) throws Exception {
Selim Cinek2630dc72017-04-20 15:16:10 -0700256 runThenWaitForInflation(block, false /* expectingException */, inflater);
257 }
258
259 private static void runThenWaitForInflation(Runnable block, boolean expectingException,
Ned Burns1a5e22f2019-02-14 15:11:52 -0500260 NotificationContentInflater inflater) throws Exception {
Selim Cinek2630dc72017-04-20 15:16:10 -0700261 CountDownLatch countDownLatch = new CountDownLatch(1);
262 final ExceptionHolder exceptionHolder = new ExceptionHolder();
Ned Burns342c3a02019-02-15 18:08:39 -0500263 inflater.setInflateSynchronously(true);
Ned Burns1a5e22f2019-02-14 15:11:52 -0500264 inflater.setInflationCallback(new InflationCallback() {
Selim Cinek2630dc72017-04-20 15:16:10 -0700265 @Override
266 public void handleInflationException(StatusBarNotification notification,
Selim Cinek01d3da62017-04-28 15:03:48 -0700267 Exception e) {
Selim Cinek2630dc72017-04-20 15:16:10 -0700268 if (!expectingException) {
269 exceptionHolder.setException(e);
270 }
271 countDownLatch.countDown();
272 }
273
274 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -0500275 public void onAsyncInflationFinished(NotificationEntry entry,
Ned Burns1a5e22f2019-02-14 15:11:52 -0500276 @NotificationContentInflater.InflationFlag int inflatedFlags) {
Selim Cinek2630dc72017-04-20 15:16:10 -0700277 if (expectingException) {
278 exceptionHolder.setException(new RuntimeException(
279 "Inflation finished even though there should be an error"));
280 }
281 countDownLatch.countDown();
282 }
283 });
284 block.run();
Jason Monk6dceace2018-05-15 20:24:07 -0400285 assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
Selim Cinek2630dc72017-04-20 15:16:10 -0700286 if (exceptionHolder.mException != null) {
287 throw exceptionHolder.mException;
288 }
289 }
290
291 private static class ExceptionHolder {
292 private Exception mException;
293
294 public void setException(Exception exception) {
295 mException = exception;
296 }
297 }
Selim Cinekd246bed2017-06-19 16:58:35 -0700298
299 private class AsyncFailRemoteView extends RemoteViews {
Jason Monk6dceace2018-05-15 20:24:07 -0400300 Handler mHandler = Handler.createAsync(Looper.getMainLooper());
Selim Cinekd246bed2017-06-19 16:58:35 -0700301
302 public AsyncFailRemoteView(String packageName, int layoutId) {
303 super(packageName, layoutId);
304 }
305
306 @Override
307 public View apply(Context context, ViewGroup parent) {
308 return super.apply(context, parent);
309 }
310
311 @Override
312 public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor,
313 OnViewAppliedListener listener, OnClickHandler handler) {
314 mHandler.post(() -> listener.onError(new RuntimeException("Failed to inflate async")));
315 return new CancellationSignal();
316 }
317
318 @Override
319 public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor,
320 OnViewAppliedListener listener) {
321 return applyAsync(context, parent, executor, listener, null);
322 }
323 }
Selim Cinek2630dc72017-04-20 15:16:10 -0700324}