1 /* 2 * Copyright (C) 2018 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 17 package com.android.systemui.statusbar.notification.collection; 18 19 import static com.android.internal.util.Preconditions.checkNotNull; 20 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; 21 import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_AMBIENT; 22 import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_HEADS_UP; 23 24 import android.annotation.Nullable; 25 import android.content.Context; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.PackageManager; 28 import android.os.Build; 29 import android.service.notification.StatusBarNotification; 30 import android.util.Log; 31 import android.view.ViewGroup; 32 33 import com.android.internal.util.NotificationMessagingUtil; 34 import com.android.systemui.Dependency; 35 import com.android.systemui.R; 36 import com.android.systemui.UiOffloadThread; 37 import com.android.systemui.statusbar.NotificationLockscreenUserManager; 38 import com.android.systemui.statusbar.NotificationPresenter; 39 import com.android.systemui.statusbar.NotificationRemoteInputManager; 40 import com.android.systemui.statusbar.NotificationUiAdjustment; 41 import com.android.systemui.statusbar.notification.InflationException; 42 import com.android.systemui.statusbar.notification.NotificationClicker; 43 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; 44 import com.android.systemui.statusbar.notification.logging.NotificationLogger; 45 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 46 import com.android.systemui.statusbar.notification.row.NotificationContentInflater; 47 import com.android.systemui.statusbar.notification.row.NotificationGutsManager; 48 import com.android.systemui.statusbar.notification.row.RowInflaterTask; 49 import com.android.systemui.statusbar.notification.stack.NotificationListContainer; 50 import com.android.systemui.statusbar.phone.NotificationGroupManager; 51 import com.android.systemui.statusbar.phone.StatusBar; 52 import com.android.systemui.statusbar.policy.HeadsUpManager; 53 54 /** Handles inflating and updating views for notifications. */ 55 public class NotificationRowBinderImpl implements NotificationRowBinder { 56 57 private static final String TAG = "NotificationViewManager"; 58 59 private final NotificationGroupManager mGroupManager = 60 Dependency.get(NotificationGroupManager.class); 61 private final NotificationGutsManager mGutsManager = 62 Dependency.get(NotificationGutsManager.class); 63 private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class); 64 private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider = 65 Dependency.get(NotificationInterruptionStateProvider.class); 66 67 private final Context mContext; 68 private final NotificationMessagingUtil mMessagingUtil; 69 private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = 70 this::logNotificationExpansion; 71 private final boolean mAllowLongPress; 72 73 private NotificationRemoteInputManager mRemoteInputManager; 74 private NotificationPresenter mPresenter; 75 private NotificationListContainer mListContainer; 76 private HeadsUpManager mHeadsUpManager; 77 private NotificationContentInflater.InflationCallback mInflationCallback; 78 private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; 79 private BindRowCallback mBindRowCallback; 80 private NotificationClicker mNotificationClicker; 81 private final NotificationLogger mNotificationLogger = Dependency.get(NotificationLogger.class); 82 NotificationRowBinderImpl(Context context, boolean allowLongPress)83 public NotificationRowBinderImpl(Context context, boolean allowLongPress) { 84 mContext = context; 85 mMessagingUtil = new NotificationMessagingUtil(context); 86 mAllowLongPress = allowLongPress; 87 } 88 getRemoteInputManager()89 private NotificationRemoteInputManager getRemoteInputManager() { 90 if (mRemoteInputManager == null) { 91 mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class); 92 } 93 return mRemoteInputManager; 94 } 95 96 /** 97 * Sets up late-bound dependencies for this component. 98 */ setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer, HeadsUpManager headsUpManager, NotificationContentInflater.InflationCallback inflationCallback, BindRowCallback bindRowCallback)99 public void setUpWithPresenter(NotificationPresenter presenter, 100 NotificationListContainer listContainer, 101 HeadsUpManager headsUpManager, 102 NotificationContentInflater.InflationCallback inflationCallback, 103 BindRowCallback bindRowCallback) { 104 mPresenter = presenter; 105 mListContainer = listContainer; 106 mHeadsUpManager = headsUpManager; 107 mInflationCallback = inflationCallback; 108 mBindRowCallback = bindRowCallback; 109 mOnAppOpsClickListener = mGutsManager::openGuts; 110 } 111 setNotificationClicker(NotificationClicker clicker)112 public void setNotificationClicker(NotificationClicker clicker) { 113 mNotificationClicker = clicker; 114 } 115 116 /** 117 * Inflates the views for the given entry (possibly asynchronously). 118 */ 119 @Override inflateViews( NotificationEntry entry, Runnable onDismissRunnable)120 public void inflateViews( 121 NotificationEntry entry, 122 Runnable onDismissRunnable) 123 throws InflationException { 124 ViewGroup parent = mListContainer.getViewParentForNotification(entry); 125 PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, 126 entry.notification.getUser().getIdentifier()); 127 128 final StatusBarNotification sbn = entry.notification; 129 if (entry.rowExists()) { 130 entry.updateIcons(mContext, sbn); 131 entry.reset(); 132 updateNotification(entry, pmUser, sbn, entry.getRow()); 133 } else { 134 entry.createIcons(mContext, sbn); 135 new RowInflaterTask().inflate(mContext, parent, entry, 136 row -> { 137 bindRow(entry, pmUser, sbn, row, onDismissRunnable); 138 updateNotification(entry, pmUser, sbn, row); 139 }); 140 } 141 } 142 bindRow(NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row, Runnable onDismissRunnable)143 private void bindRow(NotificationEntry entry, PackageManager pmUser, 144 StatusBarNotification sbn, ExpandableNotificationRow row, 145 Runnable onDismissRunnable) { 146 row.setExpansionLogger(mExpansionLogger, entry.notification.getKey()); 147 row.setGroupManager(mGroupManager); 148 row.setHeadsUpManager(mHeadsUpManager); 149 row.setOnExpandClickListener(mPresenter); 150 row.setInflationCallback(mInflationCallback); 151 if (mAllowLongPress) { 152 row.setLongPressListener(mGutsManager::openGuts); 153 } 154 mListContainer.bindRow(row); 155 getRemoteInputManager().bindRow(row); 156 157 // Get the app name. 158 // Note that Notification.Builder#bindHeaderAppName has similar logic 159 // but since this field is used in the guts, it must be accurate. 160 // Therefore we will only show the application label, or, failing that, the 161 // package name. No substitutions. 162 final String pkg = sbn.getPackageName(); 163 String appname = pkg; 164 try { 165 final ApplicationInfo info = pmUser.getApplicationInfo(pkg, 166 PackageManager.MATCH_UNINSTALLED_PACKAGES 167 | PackageManager.MATCH_DISABLED_COMPONENTS); 168 if (info != null) { 169 appname = String.valueOf(pmUser.getApplicationLabel(info)); 170 } 171 } catch (PackageManager.NameNotFoundException e) { 172 // Do nothing 173 } 174 row.setAppName(appname); 175 row.setOnDismissRunnable(onDismissRunnable); 176 row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 177 if (ENABLE_REMOTE_INPUT) { 178 row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); 179 } 180 181 row.setAppOpsOnClickListener(mOnAppOpsClickListener); 182 183 mBindRowCallback.onBindRow(entry, pmUser, sbn, row); 184 } 185 186 /** 187 * Updates the views bound to an entry when the entry's ranking changes, either in-place or by 188 * reinflating them. 189 */ 190 @Override onNotificationRankingUpdated( NotificationEntry entry, @Nullable Integer oldImportance, NotificationUiAdjustment oldAdjustment, NotificationUiAdjustment newAdjustment)191 public void onNotificationRankingUpdated( 192 NotificationEntry entry, 193 @Nullable Integer oldImportance, 194 NotificationUiAdjustment oldAdjustment, 195 NotificationUiAdjustment newAdjustment) { 196 if (NotificationUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) { 197 if (entry.rowExists()) { 198 entry.reset(); 199 PackageManager pmUser = StatusBar.getPackageManagerForUser( 200 mContext, 201 entry.notification.getUser().getIdentifier()); 202 updateNotification(entry, pmUser, entry.notification, entry.getRow()); 203 } else { 204 // Once the RowInflaterTask is done, it will pick up the updated entry, so 205 // no-op here. 206 } 207 } else { 208 if (oldImportance != null && entry.importance != oldImportance) { 209 if (entry.rowExists()) { 210 entry.getRow().onNotificationRankingUpdated(); 211 } 212 } 213 } 214 } 215 216 //TODO: This method associates a row with an entry, but eventually needs to not do that updateNotification( NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row)217 private void updateNotification( 218 NotificationEntry entry, 219 PackageManager pmUser, 220 StatusBarNotification sbn, 221 ExpandableNotificationRow row) { 222 row.setIsLowPriority(entry.ambient); 223 224 // Extract target SDK version. 225 try { 226 ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); 227 entry.targetSdk = info.targetSdkVersion; 228 } catch (PackageManager.NameNotFoundException ex) { 229 Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); 230 } 231 row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD 232 && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); 233 234 // TODO: should updates to the entry be happening somewhere else? 235 entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); 236 entry.autoRedacted = entry.notification.getNotification().publicVersion == null; 237 238 entry.setRow(row); 239 row.setOnActivatedListener(mPresenter); 240 241 boolean useIncreasedCollapsedHeight = 242 mMessagingUtil.isImportantMessaging(sbn, entry.importance); 243 boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight 244 && !mPresenter.isPresenterFullyCollapsed(); 245 row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); 246 row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); 247 row.setEntry(entry); 248 249 if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) { 250 row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, true /* shouldInflate */); 251 } 252 if (mNotificationInterruptionStateProvider.shouldPulse(entry)) { 253 row.updateInflationFlag(FLAG_CONTENT_VIEW_AMBIENT, true /* shouldInflate */); 254 } 255 row.setNeedsRedaction( 256 Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry)); 257 row.inflateViews(); 258 259 // bind the click event to the content area 260 checkNotNull(mNotificationClicker).register(row, sbn); 261 } 262 logNotificationExpansion(String key, boolean userAction, boolean expanded)263 private void logNotificationExpansion(String key, boolean userAction, boolean expanded) { 264 mNotificationLogger.onExpansionChanged(key, userAction, expanded); 265 } 266 267 /** Callback for when a row is bound to an entry. */ 268 public interface BindRowCallback { 269 /** 270 * Called when a new notification and row is created. 271 * 272 * @param entry entry for the notification 273 * @param pmUser package manager for user 274 * @param sbn notification 275 * @param row row for the notification 276 */ onBindRow(NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row)277 void onBindRow(NotificationEntry entry, PackageManager pmUser, 278 StatusBarNotification sbn, ExpandableNotificationRow row); 279 } 280 } 281