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