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