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