blob: 6c6dfa45a9583bcd251c877bb2f982b58d10202d [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
19import android.app.Notification;
20import android.content.Context;
Selim Cinek2630dc72017-04-20 15:16:10 -070021import android.os.AsyncTask;
Selim Cinek1a48bab2017-02-17 19:38:40 -080022import android.service.notification.StatusBarNotification;
23import android.util.Log;
24import android.view.View;
25import android.widget.RemoteViews;
26
Selim Cinek10790672017-03-08 16:33:05 -080027import com.android.internal.annotations.VisibleForTesting;
Selim Cinek1a48bab2017-02-17 19:38:40 -080028import com.android.systemui.statusbar.ExpandableNotificationRow;
29import com.android.systemui.statusbar.NotificationContentView;
30import com.android.systemui.statusbar.NotificationData;
31import com.android.systemui.statusbar.phone.StatusBar;
32
Selim Cinek1a48bab2017-02-17 19:38:40 -080033/**
34 * A utility that inflates the right kind of contentView based on the state
35 */
36public class NotificationInflater {
37
Selim Cinek10790672017-03-08 16:33:05 -080038 @VisibleForTesting
39 static final int FLAG_REINFLATE_ALL = ~0;
Selim Cinekc478f902017-02-22 20:55:44 -080040 private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0;
Selim Cinek2630dc72017-04-20 15:16:10 -070041 @VisibleForTesting
42 static final int FLAG_REINFLATE_EXPANDED_VIEW = 1<<1;
Selim Cinekc478f902017-02-22 20:55:44 -080043 private static final int FLAG_REINFLATE_HEADS_UP_VIEW = 1<<2;
44 private static final int FLAG_REINFLATE_PUBLIC_VIEW = 1<<3;
45 private static final int FLAG_REINFLATE_AMBIENT_VIEW = 1<<4;
46
Selim Cinek1a48bab2017-02-17 19:38:40 -080047 private final ExpandableNotificationRow mRow;
48 private boolean mIsLowPriority;
49 private boolean mUsesIncreasedHeight;
50 private boolean mUsesIncreasedHeadsUpHeight;
51 private RemoteViews.OnClickHandler mRemoteViewClickHandler;
Selim Cinekc478f902017-02-22 20:55:44 -080052 private boolean mIsChildInGroup;
Selim Cinek2630dc72017-04-20 15:16:10 -070053 private InflationCallback mCallback;
Adrian Roos1a1ecfc2017-04-17 11:17:59 -070054 private boolean mRedactAmbient;
Selim Cinek1a48bab2017-02-17 19:38:40 -080055
56 public NotificationInflater(ExpandableNotificationRow row) {
57 mRow = row;
58 }
59
60 public void setIsLowPriority(boolean isLowPriority) {
61 mIsLowPriority = isLowPriority;
62 }
63
Selim Cinekc478f902017-02-22 20:55:44 -080064 /**
65 * Set whether the notification is a child in a group
66 *
67 * @return whether the view was re-inflated
68 */
Selim Cinek2630dc72017-04-20 15:16:10 -070069 public void setIsChildInGroup(boolean childInGroup) {
Selim Cinekc478f902017-02-22 20:55:44 -080070 if (childInGroup != mIsChildInGroup) {
71 mIsChildInGroup = childInGroup;
72 if (mIsLowPriority) {
Selim Cinek2630dc72017-04-20 15:16:10 -070073 int flags = FLAG_REINFLATE_CONTENT_VIEW | FLAG_REINFLATE_EXPANDED_VIEW;
74 inflateNotificationViews(flags);
Selim Cinekc478f902017-02-22 20:55:44 -080075 }
Selim Cinek2630dc72017-04-20 15:16:10 -070076 } ;
Selim Cinekc478f902017-02-22 20:55:44 -080077 }
78
Selim Cinek1a48bab2017-02-17 19:38:40 -080079 public void setUsesIncreasedHeight(boolean usesIncreasedHeight) {
80 mUsesIncreasedHeight = usesIncreasedHeight;
81 }
82
83 public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) {
84 mUsesIncreasedHeadsUpHeight = usesIncreasedHeight;
85 }
86
87 public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
88 mRemoteViewClickHandler = remoteViewClickHandler;
89 }
90
Adrian Roos1a1ecfc2017-04-17 11:17:59 -070091 public void setRedactAmbient(boolean redactAmbient) {
92 if (mRedactAmbient != redactAmbient) {
93 mRedactAmbient = redactAmbient;
94 if (mRow.getEntry() == null) {
95 return;
96 }
Selim Cinek2630dc72017-04-20 15:16:10 -070097 inflateNotificationViews(FLAG_REINFLATE_AMBIENT_VIEW);
Adrian Roos1a1ecfc2017-04-17 11:17:59 -070098 }
99 }
100
Selim Cinek2630dc72017-04-20 15:16:10 -0700101 /**
102 * Inflate all views of this notification on a background thread. This is asynchronous and will
103 * notify the callback once it's finished.
104 */
105 public void inflateNotificationViews() {
Selim Cinekc478f902017-02-22 20:55:44 -0800106 inflateNotificationViews(FLAG_REINFLATE_ALL);
107 }
108
109 /**
Selim Cinek2630dc72017-04-20 15:16:10 -0700110 * Reinflate all views for the specified flags on a background thread. This is asynchronous and
111 * will notify the callback once it's finished.
112 *
Selim Cinekc478f902017-02-22 20:55:44 -0800113 * @param reInflateFlags flags which views should be reinflated. Use {@link #FLAG_REINFLATE_ALL}
114 * to reinflate all of views.
Selim Cinekc478f902017-02-22 20:55:44 -0800115 */
Selim Cinek2630dc72017-04-20 15:16:10 -0700116 @VisibleForTesting
117 void inflateNotificationViews(int reInflateFlags) {
Selim Cinek10790672017-03-08 16:33:05 -0800118 StatusBarNotification sbn = mRow.getEntry().notification;
Selim Cinek2630dc72017-04-20 15:16:10 -0700119 new AsyncInflationTask(mRow.getContext(), sbn, reInflateFlags).execute();
Selim Cinek1a48bab2017-02-17 19:38:40 -0800120 }
121
Selim Cinek10790672017-03-08 16:33:05 -0800122 @VisibleForTesting
123 void inflateNotificationViews(int reInflateFlags,
124 Notification.Builder builder, Context packageContext) {
125 NotificationData.Entry entry = mRow.getEntry();
126 NotificationContentView privateLayout = mRow.getPrivateLayout();
Adrian Roos1a1ecfc2017-04-17 11:17:59 -0700127 NotificationContentView publicLayout = mRow.getPublicLayout();
128
Selim Cinek10790672017-03-08 16:33:05 -0800129 boolean isLowPriority = mIsLowPriority && !mIsChildInGroup;
130 if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) {
131 final RemoteViews newContentView = createContentView(builder,
132 isLowPriority, mUsesIncreasedHeight);
133 if (!compareRemoteViews(newContentView,
134 entry.cachedContentView)) {
135 View contentViewLocal = newContentView.apply(
136 packageContext,
137 privateLayout,
138 mRemoteViewClickHandler);
139 contentViewLocal.setIsRootNamespace(true);
140 privateLayout.setContractedChild(contentViewLocal);
141 } else {
142 newContentView.reapply(packageContext,
143 privateLayout.getContractedChild(),
144 mRemoteViewClickHandler);
145 }
146 entry.cachedContentView = newContentView;
147 }
148
149 if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) {
150 final RemoteViews newBigContentView = createBigContentView(
151 builder, isLowPriority);
152 if (newBigContentView != null) {
153 if (!compareRemoteViews(newBigContentView, entry.cachedBigContentView)) {
154 View bigContentViewLocal = newBigContentView.apply(
155 packageContext,
156 privateLayout,
157 mRemoteViewClickHandler);
158 bigContentViewLocal.setIsRootNamespace(true);
159 privateLayout.setExpandedChild(bigContentViewLocal);
160 } else {
161 newBigContentView.reapply(packageContext,
162 privateLayout.getExpandedChild(),
163 mRemoteViewClickHandler);
164 }
165 } else if (entry.cachedBigContentView != null) {
166 privateLayout.setExpandedChild(null);
167 }
168 entry.cachedBigContentView = newBigContentView;
169 mRow.setExpandable(newBigContentView != null);
170 }
171
172 if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) {
173 final RemoteViews newHeadsUpContentView =
174 builder.createHeadsUpContentView(mUsesIncreasedHeadsUpHeight);
175 if (newHeadsUpContentView != null) {
176 if (!compareRemoteViews(newHeadsUpContentView,
177 entry.cachedHeadsUpContentView)) {
178 View headsUpContentViewLocal = newHeadsUpContentView.apply(
179 packageContext,
180 privateLayout,
181 mRemoteViewClickHandler);
182 headsUpContentViewLocal.setIsRootNamespace(true);
183 privateLayout.setHeadsUpChild(headsUpContentViewLocal);
184 } else {
185 newHeadsUpContentView.reapply(packageContext,
186 privateLayout.getHeadsUpChild(),
187 mRemoteViewClickHandler);
188 }
189 } else if (entry.cachedHeadsUpContentView != null) {
190 privateLayout.setHeadsUpChild(null);
191 }
192 entry.cachedHeadsUpContentView = newHeadsUpContentView;
193 }
194
195 if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) {
Selim Cinek10790672017-03-08 16:33:05 -0800196 final RemoteViews newPublicNotification
197 = builder.makePublicContentView();
198 if (!compareRemoteViews(newPublicNotification, entry.cachedPublicContentView)) {
199 View publicContentView = newPublicNotification.apply(
200 packageContext,
201 publicLayout,
202 mRemoteViewClickHandler);
203 publicContentView.setIsRootNamespace(true);
204 publicLayout.setContractedChild(publicContentView);
205 } else {
206 newPublicNotification.reapply(packageContext,
207 publicLayout.getContractedChild(),
208 mRemoteViewClickHandler);
209 }
210 entry.cachedPublicContentView = newPublicNotification;
211 }
212
213 if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) {
Adrian Roos1a1ecfc2017-04-17 11:17:59 -0700214 final RemoteViews newAmbientNotification = mRedactAmbient
215 ? builder.makePublicAmbientNotification()
216 : builder.makeAmbientNotification();
217 NotificationContentView newParent = mRedactAmbient ? publicLayout : privateLayout;
218 NotificationContentView otherParent = !mRedactAmbient ? publicLayout : privateLayout;
219
220 if (newParent.getAmbientChild() == null ||
221 !compareRemoteViews(newAmbientNotification, entry.cachedAmbientContentView)) {
Selim Cinek10790672017-03-08 16:33:05 -0800222 View ambientContentView = newAmbientNotification.apply(
223 packageContext,
Adrian Roos1a1ecfc2017-04-17 11:17:59 -0700224 newParent,
Selim Cinek10790672017-03-08 16:33:05 -0800225 mRemoteViewClickHandler);
226 ambientContentView.setIsRootNamespace(true);
Adrian Roos1a1ecfc2017-04-17 11:17:59 -0700227 newParent.setAmbientChild(ambientContentView);
228 otherParent.setAmbientChild(null);
Selim Cinek10790672017-03-08 16:33:05 -0800229 } else {
230 newAmbientNotification.reapply(packageContext,
Adrian Roos1a1ecfc2017-04-17 11:17:59 -0700231 newParent.getAmbientChild(),
Selim Cinek10790672017-03-08 16:33:05 -0800232 mRemoteViewClickHandler);
233 }
234 entry.cachedAmbientContentView = newAmbientNotification;
235 }
236 }
237
Selim Cinek1a48bab2017-02-17 19:38:40 -0800238 private RemoteViews createBigContentView(Notification.Builder builder,
239 boolean isLowPriority) {
240 RemoteViews bigContentView = builder.createBigContentView();
241 if (bigContentView != null) {
242 return bigContentView;
243 }
244 if (isLowPriority) {
245 RemoteViews contentView = builder.createContentView();
246 Notification.Builder.makeHeaderExpanded(contentView);
247 return contentView;
248 }
249 return null;
250 }
251
252 private RemoteViews createContentView(Notification.Builder builder,
253 boolean isLowPriority, boolean useLarge) {
254 if (isLowPriority) {
255 return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
256 }
257 return builder.createContentView(useLarge);
258 }
259
260 // Returns true if the RemoteViews are the same.
261 private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
262 return (a == null && b == null) ||
263 (a != null && b != null
264 && b.getPackage() != null
265 && a.getPackage() != null
266 && a.getPackage().equals(b.getPackage())
267 && a.getLayoutId() == b.getLayoutId());
268 }
Selim Cinekc478f902017-02-22 20:55:44 -0800269
Selim Cinek2630dc72017-04-20 15:16:10 -0700270 public void setInflationCallback(InflationCallback callback) {
271 mCallback = callback;
Selim Cinekc478f902017-02-22 20:55:44 -0800272 }
273
Selim Cinek2630dc72017-04-20 15:16:10 -0700274 public interface InflationCallback {
Selim Cinekc478f902017-02-22 20:55:44 -0800275 void handleInflationException(StatusBarNotification notification, InflationException e);
Selim Cinek2630dc72017-04-20 15:16:10 -0700276 void onAsyncInflationFinished(NotificationData.Entry entry);
Selim Cinekc478f902017-02-22 20:55:44 -0800277 }
Selim Cinek10790672017-03-08 16:33:05 -0800278
Selim Cinek1a48bab2017-02-17 19:38:40 -0800279 public void onDensityOrFontScaleChanged() {
280 NotificationData.Entry entry = mRow.getEntry();
281 entry.cachedAmbientContentView = null;
282 entry.cachedBigContentView = null;
283 entry.cachedContentView = null;
284 entry.cachedHeadsUpContentView = null;
285 entry.cachedPublicContentView = null;
Selim Cinek2630dc72017-04-20 15:16:10 -0700286 inflateNotificationViews();
287 }
288
289 private class AsyncInflationTask extends AsyncTask<Void, Void, Notification.Builder> {
290
291 private final StatusBarNotification mSbn;
292 private final Context mContext;
293 private final int mReInflateFlags;
294 private Context mPackageContext = null;
295 private Exception mError;
296
297 private AsyncInflationTask(Context context, StatusBarNotification notification,
298 int reInflateFlags) {
299 mSbn = notification;
300 mContext = context;
301 mReInflateFlags = reInflateFlags;
302 mRow.getEntry().addInflationTask(this);
303 }
304
305 @Override
306 protected Notification.Builder doInBackground(Void... params) {
307 try {
308 final Notification.Builder recoveredBuilder
309 = Notification.Builder.recoverBuilder(mContext,
310 mSbn.getNotification());
311 mPackageContext = mSbn.getPackageContext(mContext);
Selim Cinek5fb73f82017-04-20 16:55:38 -0700312 Notification notification = mSbn.getNotification();
Selim Cinek0847acd2017-04-24 19:48:29 -0700313 if (notification.isMediaNotification()) {
314 MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
Selim Cinek5fb73f82017-04-20 16:55:38 -0700315 mPackageContext);
Selim Cinek0847acd2017-04-24 19:48:29 -0700316 processor.setIsLowPriority(mIsLowPriority);
Selim Cinek5fb73f82017-04-20 16:55:38 -0700317 processor.processNotification(notification, recoveredBuilder);
318 }
Selim Cinek2630dc72017-04-20 15:16:10 -0700319 return recoveredBuilder;
320 } catch (Exception e) {
321 mError = e;
322 return null;
323 }
324 }
325
326 @Override
327 protected void onPostExecute(Notification.Builder builder) {
328 if (mError == null) {
329 finishInflation(mReInflateFlags, builder, mPackageContext);
330 } else {
331 handleError(mError);
332 }
Selim Cinek1a48bab2017-02-17 19:38:40 -0800333 }
334 }
335
Selim Cinek2630dc72017-04-20 15:16:10 -0700336 private void finishInflation(int reinflationFlags, Notification.Builder builder,
337 Context context) {
338 try {
339 inflateNotificationViews(reinflationFlags, builder, context);
340 } catch (RuntimeException e){
341 handleError(e);
342 return;
343 }
344 mRow.onNotificationUpdated();
345 mCallback.onAsyncInflationFinished(mRow.getEntry());
346 }
347
348 private void handleError(Exception e) {
349 StatusBarNotification sbn = mRow.getStatusBarNotification();
350 final String ident = sbn.getPackageName() + "/0x"
351 + Integer.toHexString(sbn.getId());
352 Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
353 mCallback.handleInflationException(sbn,
354 new InflationException("Couldn't inflate contentViews" + e));
355 }
Selim Cinek1a48bab2017-02-17 19:38:40 -0800356}