blob: 1aaa3b2593d263a41a314f91a21bd4876bc98754 [file] [log] [blame]
Eliot Courtney47098cb2017-10-18 17:30:30 +09001/*
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 */
16package com.android.systemui.statusbar;
17
Julia Reynolds0ef7d842018-01-24 17:50:31 -050018import static android.service.notification.NotificationListenerService.Ranking
19 .USER_SENTIMENT_NEGATIVE;
20
Eliot Courtney47098cb2017-10-18 17:30:30 +090021import android.app.INotificationManager;
22import android.app.NotificationChannel;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.PackageManager;
26import android.content.res.Resources;
27import android.os.RemoteException;
28import android.os.ServiceManager;
29import android.os.UserHandle;
30import android.provider.Settings;
31import android.service.notification.StatusBarNotification;
32import android.util.ArraySet;
33import android.util.Log;
34import android.view.HapticFeedbackConstants;
35import android.view.View;
Eliot Courtney47098cb2017-10-18 17:30:30 +090036import android.view.accessibility.AccessibilityManager;
37
38import com.android.internal.logging.MetricsLogger;
39import com.android.internal.logging.nano.MetricsProto;
40import com.android.systemui.Dependency;
41import com.android.systemui.Dumpable;
Eliot Courtney47098cb2017-10-18 17:30:30 +090042import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
43import com.android.systemui.statusbar.phone.StatusBar;
Eliot Courtney47098cb2017-10-18 17:30:30 +090044
45import java.io.FileDescriptor;
46import java.io.PrintWriter;
Eliot Courtney47098cb2017-10-18 17:30:30 +090047import java.util.Collections;
48import java.util.HashSet;
49import java.util.List;
50import java.util.Set;
51
52/**
53 * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
54 * closing guts, and keeping track of the currently exposed notification guts.
55 */
56public class NotificationGutsManager implements Dumpable {
57 private static final String TAG = "NotificationGutsManager";
58
59 // Must match constant in Settings. Used to highlight preferences when linking to Settings.
60 private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
61
62 private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
63 private final Set<String> mNonBlockablePkgs;
Eliot Courtney47098cb2017-10-18 17:30:30 +090064 private final Context mContext;
65 private final AccessibilityManager mAccessibilityManager;
Eliot Courtney6c313d32017-12-14 19:57:51 +090066
67 // Dependencies:
68 private final NotificationLockscreenUserManager mLockscreenUserManager =
69 Dependency.get(NotificationLockscreenUserManager.class);
Eliot Courtney09322282017-11-09 15:31:19 +090070
Eliot Courtney47098cb2017-10-18 17:30:30 +090071 // which notification is currently being longpress-examined by the user
72 private NotificationGuts mNotificationGutsExposed;
73 private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
Eliot Courtney4a96b362017-12-14 19:38:52 +090074 protected NotificationPresenter mPresenter;
75 protected NotificationEntryManager mEntryManager;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090076 private NotificationListContainer mListContainer;
Eliot Courtney09322282017-11-09 15:31:19 +090077 private NotificationInfo.CheckSaveListener mCheckSaveListener;
78 private OnSettingsClickListener mOnSettingsClickListener;
Eliot Courtney47098cb2017-10-18 17:30:30 +090079 private String mKeyToRemoveOnGutsClosed;
80
Eliot Courtney6c313d32017-12-14 19:57:51 +090081 public NotificationGutsManager(Context context) {
Eliot Courtney47098cb2017-10-18 17:30:30 +090082 mContext = context;
83 Resources res = context.getResources();
84
85 mNonBlockablePkgs = new HashSet<>();
86 Collections.addAll(mNonBlockablePkgs, res.getStringArray(
87 com.android.internal.R.array.config_nonBlockableNotificationPackages));
88
89 mAccessibilityManager = (AccessibilityManager)
90 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
91 }
92
Eliot Courtney4a96b362017-12-14 19:38:52 +090093 public void setUpWithPresenter(NotificationPresenter presenter,
94 NotificationEntryManager entryManager, NotificationListContainer listContainer,
Eliot Courtney09322282017-11-09 15:31:19 +090095 NotificationInfo.CheckSaveListener checkSaveListener,
96 OnSettingsClickListener onSettingsClickListener) {
97 mPresenter = presenter;
Eliot Courtney4a96b362017-12-14 19:38:52 +090098 mEntryManager = entryManager;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090099 mListContainer = listContainer;
Eliot Courtney09322282017-11-09 15:31:19 +0900100 mCheckSaveListener = checkSaveListener;
101 mOnSettingsClickListener = onSettingsClickListener;
102 }
103
Eliot Courtney47098cb2017-10-18 17:30:30 +0900104 public String getKeyToRemoveOnGutsClosed() {
105 return mKeyToRemoveOnGutsClosed;
106 }
107
108 public void setKeyToRemoveOnGutsClosed(String keyToRemoveOnGutsClosed) {
109 mKeyToRemoveOnGutsClosed = keyToRemoveOnGutsClosed;
110 }
111
yoshiki iguchia85c2a02018-01-12 11:28:06 +0900112 public void onDensityOrFontScaleChanged(ExpandableNotificationRow row) {
113 setExposedGuts(row.getGuts());
114 bindGuts(row);
115 }
116
Eliot Courtney47098cb2017-10-18 17:30:30 +0900117 private void saveAndCloseNotificationMenu(
118 ExpandableNotificationRow row, NotificationGuts guts, View done) {
119 guts.resetFalsingCheck();
120 int[] rowLocation = new int[2];
121 int[] doneLocation = new int[2];
122 row.getLocationOnScreen(rowLocation);
123 done.getLocationOnScreen(doneLocation);
124
125 final int centerX = done.getWidth() / 2;
126 final int centerY = done.getHeight() / 2;
127 final int x = doneLocation[0] - rowLocation[0] + centerX;
128 final int y = doneLocation[1] - rowLocation[1] + centerY;
129 closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
130 true /* removeControls */, x, y, true /* resetMenu */);
131 }
132
133 /**
134 * Sends an intent to open the notification settings for a particular package and optional
135 * channel.
136 */
137 private void startAppNotificationSettingsActivity(String packageName, final int appUid,
Selim Cinek2627d722018-01-19 12:16:49 -0800138 final NotificationChannel channel, ExpandableNotificationRow row) {
Eliot Courtney47098cb2017-10-18 17:30:30 +0900139 final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
140 intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
141 intent.putExtra(Settings.EXTRA_APP_UID, appUid);
142 if (channel != null) {
143 intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
144 }
Selim Cinek2627d722018-01-19 12:16:49 -0800145 mPresenter.startNotificationGutsIntent(intent, appUid, row);
Eliot Courtney47098cb2017-10-18 17:30:30 +0900146 }
147
148 public void bindGuts(final ExpandableNotificationRow row) {
149 bindGuts(row, mGutsMenuItem);
150 }
151
152 private void bindGuts(final ExpandableNotificationRow row,
153 NotificationMenuRowPlugin.MenuItem item) {
154 row.inflateGuts();
155 row.setGutsView(item);
156 final StatusBarNotification sbn = row.getStatusBarNotification();
157 row.setTag(sbn.getPackageName());
158 final NotificationGuts guts = row.getGuts();
159 guts.setClosedListener((NotificationGuts g) -> {
160 if (!g.willBeRemoved() && !row.isRemoved()) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900161 mListContainer.onHeightChanged(
Eliot Courtney47098cb2017-10-18 17:30:30 +0900162 row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
163 }
164 if (mNotificationGutsExposed == g) {
165 mNotificationGutsExposed = null;
166 mGutsMenuItem = null;
167 }
168 String key = sbn.getKey();
169 if (key.equals(mKeyToRemoveOnGutsClosed)) {
170 mKeyToRemoveOnGutsClosed = null;
Eliot Courtney4a96b362017-12-14 19:38:52 +0900171 mEntryManager.removeNotification(key, mEntryManager.getLatestRankingMap());
Eliot Courtney47098cb2017-10-18 17:30:30 +0900172 }
173 });
174
175 View gutsView = item.getGutsView();
176 if (gutsView instanceof NotificationSnooze) {
177 NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900178 snoozeGuts.setSnoozeListener(mListContainer.getSwipeActionHelper());
Eliot Courtney47098cb2017-10-18 17:30:30 +0900179 snoozeGuts.setStatusBarNotification(sbn);
180 snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
181 guts.setHeightChangedListener((NotificationGuts g) -> {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900182 mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
Eliot Courtney47098cb2017-10-18 17:30:30 +0900183 });
184 }
185
186 if (gutsView instanceof NotificationInfo) {
187 final UserHandle userHandle = sbn.getUser();
188 PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
189 userHandle.getIdentifier());
190 final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
191 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
192 final String pkg = sbn.getPackageName();
193 NotificationInfo info = (NotificationInfo) gutsView;
194 // Settings link is only valid for notifications that specify a user, unless this is the
195 // system user.
196 NotificationInfo.OnSettingsClickListener onSettingsClick = null;
197 if (!userHandle.equals(UserHandle.ALL)
Eliot Courtney09322282017-11-09 15:31:19 +0900198 || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
Eliot Courtney47098cb2017-10-18 17:30:30 +0900199 onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
200 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
201 guts.resetFalsingCheck();
Julia Reynolds84dc96b2017-11-14 09:51:01 -0500202 mOnSettingsClickListener.onClick(sbn.getKey());
Selim Cinek2627d722018-01-19 12:16:49 -0800203 startAppNotificationSettingsActivity(pkg, appUid, channel, row);
Eliot Courtney47098cb2017-10-18 17:30:30 +0900204 };
205 }
206 final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
207 Intent intent) -> {
208 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
209 guts.resetFalsingCheck();
Selim Cinek2627d722018-01-19 12:16:49 -0800210 mPresenter.startNotificationGutsIntent(intent, sbn.getUid(), row);
Eliot Courtney47098cb2017-10-18 17:30:30 +0900211 };
212 final View.OnClickListener onDoneClick = (View v) -> {
213 saveAndCloseNotificationMenu(row, guts, v);
214 };
215
216 ArraySet<NotificationChannel> channels = new ArraySet<>();
217 channels.add(row.getEntry().channel);
218 if (row.isSummaryWithChildren()) {
219 // If this is a summary, then add in the children notification channels for the
220 // same user and pkg.
221 final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
222 final int numChildren = childrenRows.size();
223 for (int i = 0; i < numChildren; i++) {
224 final ExpandableNotificationRow childRow = childrenRows.get(i);
225 final NotificationChannel childChannel = childRow.getEntry().channel;
226 final StatusBarNotification childSbn = childRow.getStatusBarNotification();
227 if (childSbn.getUser().equals(userHandle) &&
228 childSbn.getPackageName().equals(pkg)) {
229 channels.add(childChannel);
230 }
231 }
232 }
233 try {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500234 info.bindNotification(pmUser, iNotificationManager, pkg, row.getEntry().channel,
235 channels.size(), sbn, mCheckSaveListener, onSettingsClick,
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500236 onAppSettingsClick, mNonBlockablePkgs,
237 row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE);
Eliot Courtney47098cb2017-10-18 17:30:30 +0900238 } catch (RemoteException e) {
239 Log.e(TAG, e.toString());
240 }
241 }
242 }
243
244 /**
245 * Closes guts or notification menus that might be visible and saves any changes.
246 *
247 * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
248 * @param force true if guts should be closed regardless of state (used for snooze only).
249 * @param removeControls true if controls (e.g. info) should be closed.
250 * @param x if closed based on touch location, this is the x touch location.
251 * @param y if closed based on touch location, this is the y touch location.
252 * @param resetMenu if any notification menus that might be revealed should be closed.
253 */
254 public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
255 int x, int y, boolean resetMenu) {
256 if (mNotificationGutsExposed != null) {
257 mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
258 }
259 if (resetMenu) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900260 mListContainer.resetExposedMenuView(false /* animate */, true /* force */);
Eliot Courtney47098cb2017-10-18 17:30:30 +0900261 }
262 }
263
264 /**
265 * Returns the exposed NotificationGuts or null if none are exposed.
266 */
267 public NotificationGuts getExposedGuts() {
268 return mNotificationGutsExposed;
269 }
270
271 public void setExposedGuts(NotificationGuts guts) {
272 mNotificationGutsExposed = guts;
273 }
274
275 /**
yoshiki iguchia85c2a02018-01-12 11:28:06 +0900276 * Opens guts on the given ExpandableNotificationRow |v|.
Eliot Courtney47098cb2017-10-18 17:30:30 +0900277 *
278 * @param v ExpandableNotificationRow to open guts on
279 * @param x x coordinate of origin of circular reveal
280 * @param y y coordinate of origin of circular reveal
281 * @param item MenuItem the guts should display
282 * @return true if guts was opened
283 */
284 public boolean openGuts(View v, int x, int y,
285 NotificationMenuRowPlugin.MenuItem item) {
286 if (!(v instanceof ExpandableNotificationRow)) {
287 return false;
288 }
289
290 if (v.getWindowToken() == null) {
291 Log.e(TAG, "Trying to show notification guts, but not attached to window");
292 return false;
293 }
294
295 final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
296 if (row.isDark()) {
297 return false;
298 }
299 v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
300 if (row.areGutsExposed()) {
301 closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
302 true /* removeControls */, -1 /* x */, -1 /* y */,
303 true /* resetMenu */);
304 return false;
305 }
306 bindGuts(row, item);
307 NotificationGuts guts = row.getGuts();
308
309 // Assume we are a status_bar_notification_row
310 if (guts == null) {
311 // This view has no guts. Examples are the more card or the dismiss all view
312 return false;
313 }
314
315 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS);
316
317 // ensure that it's laid but not visible until actually laid out
318 guts.setVisibility(View.INVISIBLE);
319 // Post to ensure the the guts are properly laid out.
320 guts.post(new Runnable() {
321 @Override
322 public void run() {
323 if (row.getWindowToken() == null) {
324 Log.e(TAG, "Trying to show notification guts, but not attached to "
325 + "window");
326 return;
327 }
328 closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
329 true /* removeControls */, -1 /* x */, -1 /* y */,
330 false /* resetMenu */);
331 guts.setVisibility(View.VISIBLE);
yoshiki iguchia85c2a02018-01-12 11:28:06 +0900332
Eliot Courtney47098cb2017-10-18 17:30:30 +0900333 final boolean needsFalsingProtection =
334 (mPresenter.isPresenterLocked() &&
335 !mAccessibilityManager.isTouchExplorationEnabled());
yoshiki iguchia85c2a02018-01-12 11:28:06 +0900336 guts.openControls(x, y, needsFalsingProtection, () -> {
337 // Move the notification view back over the menu
338 row.resetTranslation();
339 });
340
Eliot Courtney47098cb2017-10-18 17:30:30 +0900341 row.closeRemoteInput();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900342 mListContainer.onHeightChanged(row, true /* needsAnimation */);
Eliot Courtney47098cb2017-10-18 17:30:30 +0900343 mNotificationGutsExposed = guts;
344 mGutsMenuItem = item;
345 }
346 });
347 return true;
348 }
349
350 @Override
351 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Eliot Courtney09322282017-11-09 15:31:19 +0900352 pw.println("NotificationGutsManager state:");
353 pw.print(" mKeyToRemoveOnGutsClosed: ");
Eliot Courtney47098cb2017-10-18 17:30:30 +0900354 pw.println(mKeyToRemoveOnGutsClosed);
355 }
Julia Reynolds84dc96b2017-11-14 09:51:01 -0500356
357 public interface OnSettingsClickListener {
358 void onClick(String key);
359 }
Eliot Courtney47098cb2017-10-18 17:30:30 +0900360}