NotificationInflater.java revision 1a1ecfcf5ae32482aee23ebc7c4389daf164cadd
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 17package com.android.systemui.statusbar.notification; 18 19import android.app.Notification; 20import android.content.Context; 21import android.service.notification.StatusBarNotification; 22import android.util.Log; 23import android.view.View; 24import android.view.ViewGroup; 25import android.view.ViewParent; 26import android.widget.RemoteViews; 27 28import com.android.internal.annotations.VisibleForTesting; 29import com.android.systemui.statusbar.ExpandableNotificationRow; 30import com.android.systemui.statusbar.NotificationContentView; 31import com.android.systemui.statusbar.NotificationData; 32import com.android.systemui.statusbar.phone.StatusBar; 33 34/** 35 * A utility that inflates the right kind of contentView based on the state 36 */ 37public class NotificationInflater { 38 39 @VisibleForTesting 40 static final int FLAG_REINFLATE_ALL = ~0; 41 private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0; 42 private static final int FLAG_REINFLATE_EXPANDED_VIEW = 1<<1; 43 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 47 private final ExpandableNotificationRow mRow; 48 private boolean mIsLowPriority; 49 private boolean mUsesIncreasedHeight; 50 private boolean mUsesIncreasedHeadsUpHeight; 51 private RemoteViews.OnClickHandler mRemoteViewClickHandler; 52 private boolean mIsChildInGroup; 53 private InflationExceptionHandler mInflateExceptionHandler; 54 private boolean mRedactAmbient; 55 56 public NotificationInflater(ExpandableNotificationRow row) { 57 mRow = row; 58 } 59 60 public void setIsLowPriority(boolean isLowPriority) { 61 mIsLowPriority = isLowPriority; 62 } 63 64 /** 65 * Set whether the notification is a child in a group 66 * 67 * @return whether the view was re-inflated 68 */ 69 public boolean setIsChildInGroup(boolean childInGroup) { 70 if (childInGroup != mIsChildInGroup) { 71 mIsChildInGroup = childInGroup; 72 if (mIsLowPriority) { 73 try { 74 int flags = FLAG_REINFLATE_CONTENT_VIEW | FLAG_REINFLATE_EXPANDED_VIEW; 75 inflateNotificationViews(flags); 76 } catch (InflationException e) { 77 mInflateExceptionHandler.handleInflationException( 78 mRow.getStatusBarNotification(), e); 79 } 80 } 81 return true; 82 } 83 return false; 84 } 85 86 public void setUsesIncreasedHeight(boolean usesIncreasedHeight) { 87 mUsesIncreasedHeight = usesIncreasedHeight; 88 } 89 90 public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) { 91 mUsesIncreasedHeadsUpHeight = usesIncreasedHeight; 92 } 93 94 public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) { 95 mRemoteViewClickHandler = remoteViewClickHandler; 96 } 97 98 public void setRedactAmbient(boolean redactAmbient) { 99 if (mRedactAmbient != redactAmbient) { 100 mRedactAmbient = redactAmbient; 101 if (mRow.getEntry() == null) { 102 return; 103 } 104 try { 105 inflateNotificationViews(FLAG_REINFLATE_AMBIENT_VIEW); 106 } catch (InflationException e) { 107 mInflateExceptionHandler.handleInflationException( 108 mRow.getStatusBarNotification(), e); 109 } 110 } 111 } 112 113 public void inflateNotificationViews() throws InflationException { 114 inflateNotificationViews(FLAG_REINFLATE_ALL); 115 } 116 117 /** 118 * reinflate all views for the specified flags 119 * @param reInflateFlags flags which views should be reinflated. Use {@link #FLAG_REINFLATE_ALL} 120 * to reinflate all of views. 121 * @throws InflationException 122 */ 123 private void inflateNotificationViews(int reInflateFlags) 124 throws InflationException { 125 StatusBarNotification sbn = mRow.getEntry().notification; 126 try { 127 final Notification.Builder recoveredBuilder 128 = Notification.Builder.recoverBuilder(mRow.getContext(), sbn.getNotification()); 129 Context packageContext = sbn.getPackageContext(mRow.getContext()); 130 inflateNotificationViews(reInflateFlags, recoveredBuilder, packageContext); 131 132 } catch (RuntimeException e) { 133 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 134 Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e); 135 throw new InflationException("Couldn't inflate contentViews"); 136 } 137 } 138 139 @VisibleForTesting 140 void inflateNotificationViews(int reInflateFlags, 141 Notification.Builder builder, Context packageContext) { 142 NotificationData.Entry entry = mRow.getEntry(); 143 NotificationContentView privateLayout = mRow.getPrivateLayout(); 144 NotificationContentView publicLayout = mRow.getPublicLayout(); 145 146 boolean isLowPriority = mIsLowPriority && !mIsChildInGroup; 147 if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) { 148 final RemoteViews newContentView = createContentView(builder, 149 isLowPriority, mUsesIncreasedHeight); 150 if (!compareRemoteViews(newContentView, 151 entry.cachedContentView)) { 152 View contentViewLocal = newContentView.apply( 153 packageContext, 154 privateLayout, 155 mRemoteViewClickHandler); 156 contentViewLocal.setIsRootNamespace(true); 157 privateLayout.setContractedChild(contentViewLocal); 158 } else { 159 newContentView.reapply(packageContext, 160 privateLayout.getContractedChild(), 161 mRemoteViewClickHandler); 162 } 163 entry.cachedContentView = newContentView; 164 } 165 166 if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) { 167 final RemoteViews newBigContentView = createBigContentView( 168 builder, isLowPriority); 169 if (newBigContentView != null) { 170 if (!compareRemoteViews(newBigContentView, entry.cachedBigContentView)) { 171 View bigContentViewLocal = newBigContentView.apply( 172 packageContext, 173 privateLayout, 174 mRemoteViewClickHandler); 175 bigContentViewLocal.setIsRootNamespace(true); 176 privateLayout.setExpandedChild(bigContentViewLocal); 177 } else { 178 newBigContentView.reapply(packageContext, 179 privateLayout.getExpandedChild(), 180 mRemoteViewClickHandler); 181 } 182 } else if (entry.cachedBigContentView != null) { 183 privateLayout.setExpandedChild(null); 184 } 185 entry.cachedBigContentView = newBigContentView; 186 mRow.setExpandable(newBigContentView != null); 187 } 188 189 if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) { 190 final RemoteViews newHeadsUpContentView = 191 builder.createHeadsUpContentView(mUsesIncreasedHeadsUpHeight); 192 if (newHeadsUpContentView != null) { 193 if (!compareRemoteViews(newHeadsUpContentView, 194 entry.cachedHeadsUpContentView)) { 195 View headsUpContentViewLocal = newHeadsUpContentView.apply( 196 packageContext, 197 privateLayout, 198 mRemoteViewClickHandler); 199 headsUpContentViewLocal.setIsRootNamespace(true); 200 privateLayout.setHeadsUpChild(headsUpContentViewLocal); 201 } else { 202 newHeadsUpContentView.reapply(packageContext, 203 privateLayout.getHeadsUpChild(), 204 mRemoteViewClickHandler); 205 } 206 } else if (entry.cachedHeadsUpContentView != null) { 207 privateLayout.setHeadsUpChild(null); 208 } 209 entry.cachedHeadsUpContentView = newHeadsUpContentView; 210 } 211 212 if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) { 213 final RemoteViews newPublicNotification 214 = builder.makePublicContentView(); 215 if (!compareRemoteViews(newPublicNotification, entry.cachedPublicContentView)) { 216 View publicContentView = newPublicNotification.apply( 217 packageContext, 218 publicLayout, 219 mRemoteViewClickHandler); 220 publicContentView.setIsRootNamespace(true); 221 publicLayout.setContractedChild(publicContentView); 222 } else { 223 newPublicNotification.reapply(packageContext, 224 publicLayout.getContractedChild(), 225 mRemoteViewClickHandler); 226 } 227 entry.cachedPublicContentView = newPublicNotification; 228 } 229 230 if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) { 231 final RemoteViews newAmbientNotification = mRedactAmbient 232 ? builder.makePublicAmbientNotification() 233 : builder.makeAmbientNotification(); 234 NotificationContentView newParent = mRedactAmbient ? publicLayout : privateLayout; 235 NotificationContentView otherParent = !mRedactAmbient ? publicLayout : privateLayout; 236 237 if (newParent.getAmbientChild() == null || 238 !compareRemoteViews(newAmbientNotification, entry.cachedAmbientContentView)) { 239 View ambientContentView = newAmbientNotification.apply( 240 packageContext, 241 newParent, 242 mRemoteViewClickHandler); 243 ambientContentView.setIsRootNamespace(true); 244 newParent.setAmbientChild(ambientContentView); 245 otherParent.setAmbientChild(null); 246 } else { 247 newAmbientNotification.reapply(packageContext, 248 newParent.getAmbientChild(), 249 mRemoteViewClickHandler); 250 } 251 entry.cachedAmbientContentView = newAmbientNotification; 252 } 253 } 254 255 private RemoteViews createBigContentView(Notification.Builder builder, 256 boolean isLowPriority) { 257 RemoteViews bigContentView = builder.createBigContentView(); 258 if (bigContentView != null) { 259 return bigContentView; 260 } 261 if (isLowPriority) { 262 RemoteViews contentView = builder.createContentView(); 263 Notification.Builder.makeHeaderExpanded(contentView); 264 return contentView; 265 } 266 return null; 267 } 268 269 private RemoteViews createContentView(Notification.Builder builder, 270 boolean isLowPriority, boolean useLarge) { 271 if (isLowPriority) { 272 return builder.makeLowPriorityContentView(false /* useRegularSubtext */); 273 } 274 return builder.createContentView(useLarge); 275 } 276 277 // Returns true if the RemoteViews are the same. 278 private boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) { 279 return (a == null && b == null) || 280 (a != null && b != null 281 && b.getPackage() != null 282 && a.getPackage() != null 283 && a.getPackage().equals(b.getPackage()) 284 && a.getLayoutId() == b.getLayoutId()); 285 } 286 287 public void setInflateExceptionHandler(InflationExceptionHandler inflateExceptionHandler) { 288 mInflateExceptionHandler = inflateExceptionHandler; 289 } 290 291 public interface InflationExceptionHandler { 292 void handleInflationException(StatusBarNotification notification, InflationException e); 293 } 294 295 public void onDensityOrFontScaleChanged() { 296 NotificationData.Entry entry = mRow.getEntry(); 297 entry.cachedAmbientContentView = null; 298 entry.cachedBigContentView = null; 299 entry.cachedContentView = null; 300 entry.cachedHeadsUpContentView = null; 301 entry.cachedPublicContentView = null; 302 try { 303 inflateNotificationViews(); 304 } catch (InflationException e) { 305 mInflateExceptionHandler.handleInflationException( 306 mRow.getStatusBarNotification(), e); 307 } 308 } 309 310} 311