blob: f9671182ab8c938920c2138933a3eb650350762d [file] [log] [blame]
Selim Cinek1a48bab2017-02-17 19:38:40 -08001/*
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
17package com.android.systemui.statusbar.notification;
18
Selim Cinek01d3da62017-04-28 15:03:48 -070019import android.annotation.Nullable;
Selim Cinek1a48bab2017-02-17 19:38:40 -080020import android.app.Notification;
21import android.content.Context;
Selim Cinek2630dc72017-04-20 15:16:10 -070022import android.os.AsyncTask;
Selim Cinek01d3da62017-04-28 15:03:48 -070023import android.os.CancellationSignal;
Selim Cinek1a48bab2017-02-17 19:38:40 -080024import android.service.notification.StatusBarNotification;
25import android.util.Log;
26import android.view.View;
27import android.widget.RemoteViews;
28
Selim Cinek10790672017-03-08 16:33:05 -080029import com.android.internal.annotations.VisibleForTesting;
Selim Cinekac5f0272017-05-02 16:05:41 -070030import com.android.systemui.R;
Selim Cinek67ff2482017-05-25 10:27:28 -070031import com.android.systemui.statusbar.InflationTask;
Selim Cinek1a48bab2017-02-17 19:38:40 -080032import com.android.systemui.statusbar.ExpandableNotificationRow;
33import com.android.systemui.statusbar.NotificationContentView;
34import com.android.systemui.statusbar.NotificationData;
35import com.android.systemui.statusbar.phone.StatusBar;
Selim Cinek01d3da62017-04-28 15:03:48 -070036import com.android.systemui.util.Assert;
37
38import java.util.HashMap;
Selim Cinekae8de8c2017-05-19 17:54:48 -070039import java.util.concurrent.Executor;
40import java.util.concurrent.LinkedBlockingQueue;
41import java.util.concurrent.ThreadFactory;
42import java.util.concurrent.ThreadPoolExecutor;
43import java.util.concurrent.TimeUnit;
44import java.util.concurrent.atomic.AtomicInteger;
Selim Cinek1a48bab2017-02-17 19:38:40 -080045
Selim Cinek1a48bab2017-02-17 19:38:40 -080046/**
47 * A utility that inflates the right kind of contentView based on the state
48 */
49public class NotificationInflater {
50
Selim Cinekd246bed2017-06-19 16:58:35 -070051 public static final String TAG = "NotificationInflater";
Selim Cinek10790672017-03-08 16:33:05 -080052 @VisibleForTesting
53 static final int FLAG_REINFLATE_ALL = ~0;
Selim Cinekc478f902017-02-22 20:55:44 -080054 private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0;
Selim Cinek2630dc72017-04-20 15:16:10 -070055 @VisibleForTesting
56 static final int FLAG_REINFLATE_EXPANDED_VIEW = 1<<1;
Selim Cinekc478f902017-02-22 20:55:44 -080057 private static final int FLAG_REINFLATE_HEADS_UP_VIEW = 1<<2;
58 private static final int FLAG_REINFLATE_PUBLIC_VIEW = 1<<3;
59 private static final int FLAG_REINFLATE_AMBIENT_VIEW = 1<<4;
Selim Cinekae8de8c2017-05-19 17:54:48 -070060 private static final InflationExecutor EXECUTOR = new InflationExecutor();
Selim Cinekc478f902017-02-22 20:55:44 -080061
Selim Cinek1a48bab2017-02-17 19:38:40 -080062 private final ExpandableNotificationRow mRow;
63 private boolean mIsLowPriority;
64 private boolean mUsesIncreasedHeight;
65 private boolean mUsesIncreasedHeadsUpHeight;
66 private RemoteViews.OnClickHandler mRemoteViewClickHandler;
Selim Cinekc478f902017-02-22 20:55:44 -080067 private boolean mIsChildInGroup;
Selim Cinek2630dc72017-04-20 15:16:10 -070068 private InflationCallback mCallback;
Adrian Roos1a1ecfc2017-04-17 11:17:59 -070069 private boolean mRedactAmbient;
Selim Cinek1a48bab2017-02-17 19:38:40 -080070
71 public NotificationInflater(ExpandableNotificationRow row) {
72 mRow = row;
73 }
74
75 public void setIsLowPriority(boolean isLowPriority) {
76 mIsLowPriority = isLowPriority;
77 }
78
Selim Cinekc478f902017-02-22 20:55:44 -080079 /**
80 * Set whether the notification is a child in a group
81 *
82 * @return whether the view was re-inflated
83 */
Selim Cinek2630dc72017-04-20 15:16:10 -070084 public void setIsChildInGroup(boolean childInGroup) {
Selim Cinekc478f902017-02-22 20:55:44 -080085 if (childInGroup != mIsChildInGroup) {
86 mIsChildInGroup = childInGroup;
87 if (mIsLowPriority) {
Selim Cinek2630dc72017-04-20 15:16:10 -070088 int flags = FLAG_REINFLATE_CONTENT_VIEW | FLAG_REINFLATE_EXPANDED_VIEW;
89 inflateNotificationViews(flags);
Selim Cinekc478f902017-02-22 20:55:44 -080090 }
Selim Cinek2630dc72017-04-20 15:16:10 -070091 } ;
Selim Cinekc478f902017-02-22 20:55:44 -080092 }
93
Selim Cinek1a48bab2017-02-17 19:38:40 -080094 public void setUsesIncreasedHeight(boolean usesIncreasedHeight) {
95 mUsesIncreasedHeight = usesIncreasedHeight;
96 }
97
98 public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) {
99 mUsesIncreasedHeadsUpHeight = usesIncreasedHeight;
100 }
101
102 public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
103 mRemoteViewClickHandler = remoteViewClickHandler;
104 }
105
Adrian Roos1a1ecfc2017-04-17 11:17:59 -0700106 public void setRedactAmbient(boolean redactAmbient) {
107 if (mRedactAmbient != redactAmbient) {
108 mRedactAmbient = redactAmbient;
109 if (mRow.getEntry() == null) {
110 return;
111 }
Selim Cinek2630dc72017-04-20 15:16:10 -0700112 inflateNotificationViews(FLAG_REINFLATE_AMBIENT_VIEW);
Adrian Roos1a1ecfc2017-04-17 11:17:59 -0700113 }
114 }
115
Selim Cinek2630dc72017-04-20 15:16:10 -0700116 /**
117 * Inflate all views of this notification on a background thread. This is asynchronous and will
118 * notify the callback once it's finished.
119 */
120 public void inflateNotificationViews() {
Selim Cinekc478f902017-02-22 20:55:44 -0800121 inflateNotificationViews(FLAG_REINFLATE_ALL);
122 }
123
124 /**
Selim Cinek2630dc72017-04-20 15:16:10 -0700125 * Reinflate all views for the specified flags on a background thread. This is asynchronous and
126 * will notify the callback once it's finished.
127 *
Selim Cinekc478f902017-02-22 20:55:44 -0800128 * @param reInflateFlags flags which views should be reinflated. Use {@link #FLAG_REINFLATE_ALL}
129 * to reinflate all of views.
Selim Cinekc478f902017-02-22 20:55:44 -0800130 */
Selim Cinek2630dc72017-04-20 15:16:10 -0700131 @VisibleForTesting
132 void inflateNotificationViews(int reInflateFlags) {
Selim Cinek67ff2482017-05-25 10:27:28 -0700133 if (mRow.isRemoved()) {
134 // We don't want to reinflate anything for removed notifications. Otherwise views might
135 // be readded to the stack, leading to leaks. This may happen with low-priority groups
136 // where the removal of already removed children can lead to a reinflation.
137 return;
138 }
Selim Cinek10790672017-03-08 16:33:05 -0800139 StatusBarNotification sbn = mRow.getEntry().notification;
Selim Cinek01d3da62017-04-28 15:03:48 -0700140 new AsyncInflationTask(sbn, reInflateFlags, mRow, mIsLowPriority,
141 mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
142 mCallback, mRemoteViewClickHandler).execute();
Selim Cinek1a48bab2017-02-17 19:38:40 -0800143 }
144
Selim Cinek10790672017-03-08 16:33:05 -0800145 @VisibleForTesting
Selim Cinek01d3da62017-04-28 15:03:48 -0700146 InflationProgress inflateNotificationViews(int reInflateFlags,
Selim Cinek10790672017-03-08 16:33:05 -0800147 Notification.Builder builder, Context packageContext) {
Selim Cinek01d3da62017-04-28 15:03:48 -0700148 InflationProgress result = createRemoteViews(reInflateFlags, builder, mIsLowPriority,
149 mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight,
150 mRedactAmbient, packageContext);
151 apply(result, reInflateFlags, mRow, mRedactAmbient, mRemoteViewClickHandler, null);
152 return result;
153 }
Adrian Roos1a1ecfc2017-04-17 11:17:59 -0700154
Selim Cinek01d3da62017-04-28 15:03:48 -0700155 private static InflationProgress createRemoteViews(int reInflateFlags,
156 Notification.Builder builder, boolean isLowPriority, boolean isChildInGroup,
157 boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
158 Context packageContext) {
159 InflationProgress result = new InflationProgress();
160 isLowPriority = isLowPriority && !isChildInGroup;
Selim Cinek10790672017-03-08 16:33:05 -0800161 if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) {
Selim Cinek01d3da62017-04-28 15:03:48 -0700162 result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight);
Selim Cinek10790672017-03-08 16:33:05 -0800163 }
164
165 if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) {
Selim Cinek01d3da62017-04-28 15:03:48 -0700166 result.newExpandedView = createExpandedView(builder, isLowPriority);
Selim Cinek10790672017-03-08 16:33:05 -0800167 }
168
169 if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) {
Selim Cinek01d3da62017-04-28 15:03:48 -0700170 result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight);
Selim Cinek10790672017-03-08 16:33:05 -0800171 }
172
173 if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) {
Selim Cinek01d3da62017-04-28 15:03:48 -0700174 result.newPublicView = builder.makePublicContentView();
Selim Cinek10790672017-03-08 16:33:05 -0800175 }
176
177 if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) {
Selim Cinek01d3da62017-04-28 15:03:48 -0700178 result.newAmbientView = redactAmbient ? builder.makePublicAmbientNotification()
Adrian Roos1a1ecfc2017-04-17 11:17:59 -0700179 : builder.makeAmbientNotification();
Selim Cinek01d3da62017-04-28 15:03:48 -0700180 }
181 result.packageContext = packageContext;
182 return result;
183 }
Adrian Roos1a1ecfc2017-04-17 11:17:59 -0700184
Selim Cinek01d3da62017-04-28 15:03:48 -0700185 public static CancellationSignal apply(InflationProgress result, int reInflateFlags,
186 ExpandableNotificationRow row, boolean redactAmbient,
187 RemoteViews.OnClickHandler remoteViewClickHandler,
188 @Nullable InflationCallback callback) {
189 NotificationData.Entry entry = row.getEntry();
190 NotificationContentView privateLayout = row.getPrivateLayout();
191 NotificationContentView publicLayout = row.getPublicLayout();
192 final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>();
193
194 int flag = FLAG_REINFLATE_CONTENT_VIEW;
195 if ((reInflateFlags & flag) != 0) {
Selim Cinekfc8073c2017-08-16 17:50:20 -0700196 boolean isNewView = !canReapplyRemoteView(result.newContentView, entry.cachedContentView);
Selim Cinek01d3da62017-04-28 15:03:48 -0700197 ApplyCallback applyCallback = new ApplyCallback() {
198 @Override
199 public void setResultView(View v) {
200 result.inflatedContentView = v;
201 }
202
203 @Override
204 public RemoteViews getRemoteView() {
205 return result.newContentView;
206 }
207 };
208 applyRemoteView(result, reInflateFlags, flag, row, redactAmbient,
209 isNewView, remoteViewClickHandler, callback, entry, privateLayout,
Selim Cinek131f1a42017-06-05 17:50:19 -0700210 privateLayout.getContractedChild(), privateLayout.getVisibleWrapper(
211 NotificationContentView.VISIBLE_TYPE_CONTRACTED),
Selim Cinek01d3da62017-04-28 15:03:48 -0700212 runningInflations, applyCallback);
213 }
214
215 flag = FLAG_REINFLATE_EXPANDED_VIEW;
216 if ((reInflateFlags & flag) != 0) {
217 if (result.newExpandedView != null) {
Selim Cinekfc8073c2017-08-16 17:50:20 -0700218 boolean isNewView = !canReapplyRemoteView(result.newExpandedView,
Selim Cinek01d3da62017-04-28 15:03:48 -0700219 entry.cachedBigContentView);
220 ApplyCallback applyCallback = new ApplyCallback() {
221 @Override
222 public void setResultView(View v) {
223 result.inflatedExpandedView = v;
224 }
225
226 @Override
227 public RemoteViews getRemoteView() {
228 return result.newExpandedView;
229 }
230 };
231 applyRemoteView(result, reInflateFlags, flag, row,
232 redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
Selim Cinek131f1a42017-06-05 17:50:19 -0700233 privateLayout, privateLayout.getExpandedChild(),
234 privateLayout.getVisibleWrapper(
235 NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations,
Selim Cinek01d3da62017-04-28 15:03:48 -0700236 applyCallback);
Selim Cinek10790672017-03-08 16:33:05 -0800237 }
Selim Cinek01d3da62017-04-28 15:03:48 -0700238 }
239
240 flag = FLAG_REINFLATE_HEADS_UP_VIEW;
241 if ((reInflateFlags & flag) != 0) {
242 if (result.newHeadsUpView != null) {
Selim Cinekfc8073c2017-08-16 17:50:20 -0700243 boolean isNewView = !canReapplyRemoteView(result.newHeadsUpView,
Selim Cinek01d3da62017-04-28 15:03:48 -0700244 entry.cachedHeadsUpContentView);
245 ApplyCallback applyCallback = new ApplyCallback() {
246 @Override
247 public void setResultView(View v) {
248 result.inflatedHeadsUpView = v;
249 }
250
251 @Override
252 public RemoteViews getRemoteView() {
253 return result.newHeadsUpView;
254 }
255 };
256 applyRemoteView(result, reInflateFlags, flag, row,
257 redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
Selim Cinek131f1a42017-06-05 17:50:19 -0700258 privateLayout, privateLayout.getHeadsUpChild(),
259 privateLayout.getVisibleWrapper(
260 NotificationContentView.VISIBLE_TYPE_HEADSUP), runningInflations,
Selim Cinek01d3da62017-04-28 15:03:48 -0700261 applyCallback);
262 }
263 }
264
265 flag = FLAG_REINFLATE_PUBLIC_VIEW;
266 if ((reInflateFlags & flag) != 0) {
Selim Cinekfc8073c2017-08-16 17:50:20 -0700267 boolean isNewView = !canReapplyRemoteView(result.newPublicView,
Selim Cinek01d3da62017-04-28 15:03:48 -0700268 entry.cachedPublicContentView);
269 ApplyCallback applyCallback = new ApplyCallback() {
270 @Override
271 public void setResultView(View v) {
272 result.inflatedPublicView = v;
273 }
274
275 @Override
276 public RemoteViews getRemoteView() {
277 return result.newPublicView;
278 }
279 };
280 applyRemoteView(result, reInflateFlags, flag, row,
281 redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
Selim Cinek131f1a42017-06-05 17:50:19 -0700282 publicLayout, publicLayout.getContractedChild(),
283 publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),
284 runningInflations, applyCallback);
Selim Cinek01d3da62017-04-28 15:03:48 -0700285 }
286
287 flag = FLAG_REINFLATE_AMBIENT_VIEW;
288 if ((reInflateFlags & flag) != 0) {
289 NotificationContentView newParent = redactAmbient ? publicLayout : privateLayout;
290 boolean isNewView = !canReapplyAmbient(row, redactAmbient) ||
Selim Cinekfc8073c2017-08-16 17:50:20 -0700291 !canReapplyRemoteView(result.newAmbientView, entry.cachedAmbientContentView);
Selim Cinek01d3da62017-04-28 15:03:48 -0700292 ApplyCallback applyCallback = new ApplyCallback() {
293 @Override
294 public void setResultView(View v) {
295 result.inflatedAmbientView = v;
296 }
297
298 @Override
299 public RemoteViews getRemoteView() {
300 return result.newAmbientView;
301 }
302 };
303 applyRemoteView(result, reInflateFlags, flag, row,
304 redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
Selim Cinek131f1a42017-06-05 17:50:19 -0700305 newParent, newParent.getAmbientChild(), newParent.getVisibleWrapper(
306 NotificationContentView.VISIBLE_TYPE_AMBIENT), runningInflations,
Selim Cinek01d3da62017-04-28 15:03:48 -0700307 applyCallback);
308 }
309
310 // Let's try to finish, maybe nobody is even inflating anything
311 finishIfDone(result, reInflateFlags, runningInflations, callback, row,
312 redactAmbient);
313 CancellationSignal cancellationSignal = new CancellationSignal();
314 cancellationSignal.setOnCancelListener(
315 () -> runningInflations.values().forEach(CancellationSignal::cancel));
316 return cancellationSignal;
317 }
318
Selim Cinekd246bed2017-06-19 16:58:35 -0700319 @VisibleForTesting
320 static void applyRemoteView(final InflationProgress result,
Selim Cinek01d3da62017-04-28 15:03:48 -0700321 final int reInflateFlags, int inflationId,
322 final ExpandableNotificationRow row,
323 final boolean redactAmbient, boolean isNewView,
324 RemoteViews.OnClickHandler remoteViewClickHandler,
325 @Nullable final InflationCallback callback, NotificationData.Entry entry,
326 NotificationContentView parentLayout, View existingView,
Selim Cinek131f1a42017-06-05 17:50:19 -0700327 NotificationViewWrapper existingWrapper,
Selim Cinek01d3da62017-04-28 15:03:48 -0700328 final HashMap<Integer, CancellationSignal> runningInflations,
329 ApplyCallback applyCallback) {
Selim Cinekd246bed2017-06-19 16:58:35 -0700330 RemoteViews newContentView = applyCallback.getRemoteView();
Selim Cinek01d3da62017-04-28 15:03:48 -0700331 RemoteViews.OnViewAppliedListener listener
332 = new RemoteViews.OnViewAppliedListener() {
333
334 @Override
335 public void onViewApplied(View v) {
336 if (isNewView) {
337 v.setIsRootNamespace(true);
338 applyCallback.setResultView(v);
Selim Cinek131f1a42017-06-05 17:50:19 -0700339 } else if (existingWrapper != null) {
340 existingWrapper.onReinflated();
Selim Cinek01d3da62017-04-28 15:03:48 -0700341 }
342 runningInflations.remove(inflationId);
343 finishIfDone(result, reInflateFlags, runningInflations, callback, row,
344 redactAmbient);
345 }
346
347 @Override
348 public void onError(Exception e) {
Selim Cinekd246bed2017-06-19 16:58:35 -0700349 // Uh oh the async inflation failed. Due to some bugs (see b/38190555), this could
350 // actually also be a system issue, so let's try on the UI thread again to be safe.
351 try {
352 View newView = existingView;
353 if (isNewView) {
354 newView = newContentView.apply(
355 result.packageContext,
356 parentLayout,
357 remoteViewClickHandler);
358 } else {
359 newContentView.reapply(
360 result.packageContext,
361 existingView,
362 remoteViewClickHandler);
363 }
364 Log.wtf(TAG, "Async Inflation failed but normal inflation finished normally.",
365 e);
366 onViewApplied(newView);
367 } catch (Exception anotherException) {
368 runningInflations.remove(inflationId);
369 handleInflationError(runningInflations, e, entry.notification, callback);
370 }
Selim Cinek01d3da62017-04-28 15:03:48 -0700371 }
372 };
373 CancellationSignal cancellationSignal;
Selim Cinek01d3da62017-04-28 15:03:48 -0700374 if (isNewView) {
375 cancellationSignal = newContentView.applyAsync(
376 result.packageContext,
377 parentLayout,
Selim Cinekae8de8c2017-05-19 17:54:48 -0700378 EXECUTOR,
Selim Cinek01d3da62017-04-28 15:03:48 -0700379 listener,
380 remoteViewClickHandler);
381 } else {
382 cancellationSignal = newContentView.reapplyAsync(
383 result.packageContext,
384 existingView,
Selim Cinekae8de8c2017-05-19 17:54:48 -0700385 EXECUTOR,
Selim Cinek01d3da62017-04-28 15:03:48 -0700386 listener,
387 remoteViewClickHandler);
388 }
389 runningInflations.put(inflationId, cancellationSignal);
390 }
391
392 private static void handleInflationError(HashMap<Integer, CancellationSignal> runningInflations,
393 Exception e, StatusBarNotification notification, @Nullable InflationCallback callback) {
394 Assert.isMainThread();
395 runningInflations.values().forEach(CancellationSignal::cancel);
396 if (callback != null) {
397 callback.handleInflationException(notification, e);
Selim Cinek10790672017-03-08 16:33:05 -0800398 }
399 }
400
Selim Cinek01d3da62017-04-28 15:03:48 -0700401 /**
402 * Finish the inflation of the views
403 *
404 * @return true if the inflation was finished
405 */
406 private static boolean finishIfDone(InflationProgress result, int reInflateFlags,
407 HashMap<Integer, CancellationSignal> runningInflations,
408 @Nullable InflationCallback endListener, ExpandableNotificationRow row,
409 boolean redactAmbient) {
410 Assert.isMainThread();
411 NotificationData.Entry entry = row.getEntry();
412 NotificationContentView privateLayout = row.getPrivateLayout();
413 NotificationContentView publicLayout = row.getPublicLayout();
414 if (runningInflations.isEmpty()) {
415 if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) {
416 if (result.inflatedContentView != null) {
417 privateLayout.setContractedChild(result.inflatedContentView);
418 }
419 entry.cachedContentView = result.newContentView;
420 }
421
422 if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) {
423 if (result.inflatedExpandedView != null) {
424 privateLayout.setExpandedChild(result.inflatedExpandedView);
425 } else if (result.newExpandedView == null) {
426 privateLayout.setExpandedChild(null);
427 }
428 entry.cachedBigContentView = result.newExpandedView;
429 row.setExpandable(result.newExpandedView != null);
430 }
431
432 if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) {
433 if (result.inflatedHeadsUpView != null) {
434 privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
435 } else if (result.newHeadsUpView == null) {
436 privateLayout.setHeadsUpChild(null);
437 }
438 entry.cachedHeadsUpContentView = result.newHeadsUpView;
439 }
440
441 if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) {
442 if (result.inflatedPublicView != null) {
443 publicLayout.setContractedChild(result.inflatedPublicView);
444 }
445 entry.cachedPublicContentView = result.newPublicView;
446 }
447
448 if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) {
449 if (result.inflatedAmbientView != null) {
450 NotificationContentView newParent = redactAmbient
451 ? publicLayout : privateLayout;
452 NotificationContentView otherParent = !redactAmbient
453 ? publicLayout : privateLayout;
454 newParent.setAmbientChild(result.inflatedAmbientView);
455 otherParent.setAmbientChild(null);
456 }
457 entry.cachedAmbientContentView = result.newAmbientView;
458 }
459 if (endListener != null) {
460 endListener.onAsyncInflationFinished(row.getEntry());
461 }
462 return true;
463 }
464 return false;
465 }
466
467 private static RemoteViews createExpandedView(Notification.Builder builder,
Selim Cinek1a48bab2017-02-17 19:38:40 -0800468 boolean isLowPriority) {
469 RemoteViews bigContentView = builder.createBigContentView();
470 if (bigContentView != null) {
471 return bigContentView;
472 }
473 if (isLowPriority) {
474 RemoteViews contentView = builder.createContentView();
475 Notification.Builder.makeHeaderExpanded(contentView);
476 return contentView;
477 }
478 return null;
479 }
480
Selim Cinek01d3da62017-04-28 15:03:48 -0700481 private static RemoteViews createContentView(Notification.Builder builder,
Selim Cinek1a48bab2017-02-17 19:38:40 -0800482 boolean isLowPriority, boolean useLarge) {
483 if (isLowPriority) {
484 return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
485 }
486 return builder.createContentView(useLarge);
487 }
488
Selim Cinekfc8073c2017-08-16 17:50:20 -0700489 /**
490 * @param newView The new view that will be applied
491 * @param oldView The old view that was applied to the existing view before
492 * @return {@code true} if the RemoteViews are the same and the view can be reused to reapply.
493 */
494 @VisibleForTesting
495 static boolean canReapplyRemoteView(final RemoteViews newView,
496 final RemoteViews oldView) {
497 return (newView == null && oldView == null) ||
498 (newView != null && oldView != null
499 && oldView.getPackage() != null
500 && newView.getPackage() != null
501 && newView.getPackage().equals(oldView.getPackage())
502 && newView.getLayoutId() == oldView.getLayoutId()
503 && !oldView.isReapplyDisallowed());
Selim Cinek1a48bab2017-02-17 19:38:40 -0800504 }
Selim Cinekc478f902017-02-22 20:55:44 -0800505
Selim Cinek2630dc72017-04-20 15:16:10 -0700506 public void setInflationCallback(InflationCallback callback) {
507 mCallback = callback;
Selim Cinekc478f902017-02-22 20:55:44 -0800508 }
509
Selim Cinek2630dc72017-04-20 15:16:10 -0700510 public interface InflationCallback {
Selim Cinek01d3da62017-04-28 15:03:48 -0700511 void handleInflationException(StatusBarNotification notification, Exception e);
Selim Cinek2630dc72017-04-20 15:16:10 -0700512 void onAsyncInflationFinished(NotificationData.Entry entry);
Selim Cinekc478f902017-02-22 20:55:44 -0800513 }
Selim Cinek10790672017-03-08 16:33:05 -0800514
Selim Cinek1a48bab2017-02-17 19:38:40 -0800515 public void onDensityOrFontScaleChanged() {
516 NotificationData.Entry entry = mRow.getEntry();
517 entry.cachedAmbientContentView = null;
518 entry.cachedBigContentView = null;
519 entry.cachedContentView = null;
520 entry.cachedHeadsUpContentView = null;
521 entry.cachedPublicContentView = null;
Selim Cinek2630dc72017-04-20 15:16:10 -0700522 inflateNotificationViews();
523 }
524
Selim Cinek01d3da62017-04-28 15:03:48 -0700525 private static boolean canReapplyAmbient(ExpandableNotificationRow row, boolean redactAmbient) {
526 NotificationContentView ambientView = redactAmbient ? row.getPublicLayout()
527 : row.getPrivateLayout(); ;
528 return ambientView.getAmbientChild() != null;
529 }
530
531 public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
Selim Cinek67ff2482017-05-25 10:27:28 -0700532 implements InflationCallback, InflationTask {
Selim Cinek2630dc72017-04-20 15:16:10 -0700533
534 private final StatusBarNotification mSbn;
535 private final Context mContext;
Selim Cinek01d3da62017-04-28 15:03:48 -0700536 private final boolean mIsLowPriority;
537 private final boolean mIsChildInGroup;
538 private final boolean mUsesIncreasedHeight;
539 private final InflationCallback mCallback;
540 private final boolean mUsesIncreasedHeadsUpHeight;
541 private final boolean mRedactAmbient;
Selim Cinek67ff2482017-05-25 10:27:28 -0700542 private int mReInflateFlags;
Selim Cinek01d3da62017-04-28 15:03:48 -0700543 private ExpandableNotificationRow mRow;
Selim Cinek2630dc72017-04-20 15:16:10 -0700544 private Exception mError;
Selim Cinek01d3da62017-04-28 15:03:48 -0700545 private RemoteViews.OnClickHandler mRemoteViewClickHandler;
546 private CancellationSignal mCancellationSignal;
Selim Cinek2630dc72017-04-20 15:16:10 -0700547
Selim Cinek01d3da62017-04-28 15:03:48 -0700548 private AsyncInflationTask(StatusBarNotification notification,
549 int reInflateFlags, ExpandableNotificationRow row, boolean isLowPriority,
550 boolean isChildInGroup, boolean usesIncreasedHeight,
551 boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
552 InflationCallback callback,
553 RemoteViews.OnClickHandler remoteViewClickHandler) {
554 mRow = row;
Selim Cinek2630dc72017-04-20 15:16:10 -0700555 mSbn = notification;
Selim Cinek2630dc72017-04-20 15:16:10 -0700556 mReInflateFlags = reInflateFlags;
Selim Cinek01d3da62017-04-28 15:03:48 -0700557 mContext = mRow.getContext();
558 mIsLowPriority = isLowPriority;
559 mIsChildInGroup = isChildInGroup;
560 mUsesIncreasedHeight = usesIncreasedHeight;
561 mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight;
562 mRedactAmbient = redactAmbient;
563 mRemoteViewClickHandler = remoteViewClickHandler;
564 mCallback = callback;
Selim Cinek67ff2482017-05-25 10:27:28 -0700565 NotificationData.Entry entry = row.getEntry();
566 entry.setInflationTask(this);
567 }
568
569 @VisibleForTesting
570 public int getReInflateFlags() {
571 return mReInflateFlags;
Selim Cinek2630dc72017-04-20 15:16:10 -0700572 }
573
574 @Override
Selim Cinek01d3da62017-04-28 15:03:48 -0700575 protected InflationProgress doInBackground(Void... params) {
Selim Cinek2630dc72017-04-20 15:16:10 -0700576 try {
577 final Notification.Builder recoveredBuilder
578 = Notification.Builder.recoverBuilder(mContext,
579 mSbn.getNotification());
Selim Cinek01d3da62017-04-28 15:03:48 -0700580 Context packageContext = mSbn.getPackageContext(mContext);
Selim Cinek5fb73f82017-04-20 16:55:38 -0700581 Notification notification = mSbn.getNotification();
Selim Cinekac5f0272017-05-02 16:05:41 -0700582 if (mIsLowPriority) {
583 int backgroundColor = mContext.getColor(
584 R.color.notification_material_background_low_priority_color);
585 recoveredBuilder.setBackgroundColorHint(backgroundColor);
586 }
Selim Cinek0847acd2017-04-24 19:48:29 -0700587 if (notification.isMediaNotification()) {
588 MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
Selim Cinek01d3da62017-04-28 15:03:48 -0700589 packageContext);
Selim Cinek0847acd2017-04-24 19:48:29 -0700590 processor.setIsLowPriority(mIsLowPriority);
Selim Cinek5fb73f82017-04-20 16:55:38 -0700591 processor.processNotification(notification, recoveredBuilder);
592 }
Selim Cinek01d3da62017-04-28 15:03:48 -0700593 return createRemoteViews(mReInflateFlags,
594 recoveredBuilder, mIsLowPriority, mIsChildInGroup,
595 mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
596 packageContext);
Selim Cinek2630dc72017-04-20 15:16:10 -0700597 } catch (Exception e) {
598 mError = e;
599 return null;
600 }
601 }
602
603 @Override
Selim Cinek01d3da62017-04-28 15:03:48 -0700604 protected void onPostExecute(InflationProgress result) {
Selim Cinek2630dc72017-04-20 15:16:10 -0700605 if (mError == null) {
Selim Cinek01d3da62017-04-28 15:03:48 -0700606 mCancellationSignal = apply(result, mReInflateFlags, mRow, mRedactAmbient,
607 mRemoteViewClickHandler, this);
Selim Cinek2630dc72017-04-20 15:16:10 -0700608 } else {
609 handleError(mError);
610 }
Selim Cinek1a48bab2017-02-17 19:38:40 -0800611 }
Selim Cinek1a48bab2017-02-17 19:38:40 -0800612
Selim Cinek01d3da62017-04-28 15:03:48 -0700613 private void handleError(Exception e) {
614 mRow.getEntry().onInflationTaskFinished();
615 StatusBarNotification sbn = mRow.getStatusBarNotification();
616 final String ident = sbn.getPackageName() + "/0x"
617 + Integer.toHexString(sbn.getId());
618 Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
619 mCallback.handleInflationException(sbn,
620 new InflationException("Couldn't inflate contentViews" + e));
Selim Cinek2630dc72017-04-20 15:16:10 -0700621 }
Selim Cinek01d3da62017-04-28 15:03:48 -0700622
Selim Cinek0f66a4c2017-04-28 19:26:28 -0700623 @Override
Selim Cinek01d3da62017-04-28 15:03:48 -0700624 public void abort() {
625 cancel(true /* mayInterruptIfRunning */);
626 if (mCancellationSignal != null) {
627 mCancellationSignal.cancel();
628 }
629 }
630
631 @Override
Selim Cinek67ff2482017-05-25 10:27:28 -0700632 public void supersedeTask(InflationTask task) {
633 if (task instanceof AsyncInflationTask) {
634 // We want to inflate all flags of the previous task as well
635 mReInflateFlags |= ((AsyncInflationTask) task).mReInflateFlags;
636 }
637 }
638
639 @Override
Selim Cinek01d3da62017-04-28 15:03:48 -0700640 public void handleInflationException(StatusBarNotification notification, Exception e) {
641 handleError(e);
642 }
643
644 @Override
645 public void onAsyncInflationFinished(NotificationData.Entry entry) {
646 mRow.getEntry().onInflationTaskFinished();
647 mRow.onNotificationUpdated();
648 mCallback.onAsyncInflationFinished(mRow.getEntry());
649 }
Selim Cinek2630dc72017-04-20 15:16:10 -0700650 }
651
Selim Cinekd246bed2017-06-19 16:58:35 -0700652 @VisibleForTesting
653 static class InflationProgress {
Selim Cinek01d3da62017-04-28 15:03:48 -0700654 private RemoteViews newContentView;
655 private RemoteViews newHeadsUpView;
656 private RemoteViews newExpandedView;
657 private RemoteViews newAmbientView;
658 private RemoteViews newPublicView;
659
Selim Cinekd246bed2017-06-19 16:58:35 -0700660 @VisibleForTesting
661 Context packageContext;
Selim Cinek01d3da62017-04-28 15:03:48 -0700662
663 private View inflatedContentView;
664 private View inflatedHeadsUpView;
665 private View inflatedExpandedView;
666 private View inflatedAmbientView;
667 private View inflatedPublicView;
668 }
669
Selim Cinekd246bed2017-06-19 16:58:35 -0700670 @VisibleForTesting
671 abstract static class ApplyCallback {
Selim Cinek01d3da62017-04-28 15:03:48 -0700672 public abstract void setResultView(View v);
673 public abstract RemoteViews getRemoteView();
Selim Cinek2630dc72017-04-20 15:16:10 -0700674 }
Selim Cinekae8de8c2017-05-19 17:54:48 -0700675
676 /**
677 * A custom executor that allows more tasks to be queued. Default values are copied from
678 * AsyncTask
679 */
680 private static class InflationExecutor implements Executor {
681 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
682 // We want at least 2 threads and at most 4 threads in the core pool,
683 // preferring to have 1 less than the CPU count to avoid saturating
684 // the CPU with background work
685 private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
686 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
687 private static final int KEEP_ALIVE_SECONDS = 30;
688
689 private static final ThreadFactory sThreadFactory = new ThreadFactory() {
690 private final AtomicInteger mCount = new AtomicInteger(1);
691
692 public Thread newThread(Runnable r) {
693 return new Thread(r, "InflaterThread #" + mCount.getAndIncrement());
694 }
695 };
696
697 private final ThreadPoolExecutor mExecutor;
698
699 private InflationExecutor() {
700 mExecutor = new ThreadPoolExecutor(
701 CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
702 new LinkedBlockingQueue<>(), sThreadFactory);
703 mExecutor.allowCoreThreadTimeOut(true);
704 }
705
706 @Override
707 public void execute(Runnable runnable) {
708 mExecutor.execute(runnable);
709 }
710 }
Selim Cinek1a48bab2017-02-17 19:38:40 -0800711}