blob: 9d8892da3c748faa9b48c6d2801acda328a61990 [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
Eliot Courtney47098cb2017-10-18 17:30:30 +090018import android.app.INotificationManager;
19import android.app.NotificationChannel;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.PackageManager;
23import android.content.res.Resources;
24import android.os.RemoteException;
25import android.os.ServiceManager;
26import android.os.UserHandle;
27import android.provider.Settings;
28import android.service.notification.StatusBarNotification;
29import android.util.ArraySet;
30import android.util.Log;
31import android.view.HapticFeedbackConstants;
32import android.view.View;
Eliot Courtney47098cb2017-10-18 17:30:30 +090033import android.view.accessibility.AccessibilityManager;
34
35import com.android.internal.logging.MetricsLogger;
36import com.android.internal.logging.nano.MetricsProto;
37import com.android.systemui.Dependency;
38import com.android.systemui.Dumpable;
Eliot Courtney47098cb2017-10-18 17:30:30 +090039import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
40import com.android.systemui.statusbar.phone.StatusBar;
Eliot Courtney47098cb2017-10-18 17:30:30 +090041
42import java.io.FileDescriptor;
43import java.io.PrintWriter;
44import java.util.ArrayList;
45import java.util.Collections;
46import java.util.HashSet;
47import java.util.List;
48import java.util.Set;
49
50/**
51 * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
52 * closing guts, and keeping track of the currently exposed notification guts.
53 */
54public class NotificationGutsManager implements Dumpable {
55 private static final String TAG = "NotificationGutsManager";
56
57 // Must match constant in Settings. Used to highlight preferences when linking to Settings.
58 private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
59
60 private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
61 private final Set<String> mNonBlockablePkgs;
Eliot Courtney47098cb2017-10-18 17:30:30 +090062 private final Context mContext;
63 private final AccessibilityManager mAccessibilityManager;
Eliot Courtney6c313d32017-12-14 19:57:51 +090064
65 // Dependencies:
66 private final NotificationLockscreenUserManager mLockscreenUserManager =
67 Dependency.get(NotificationLockscreenUserManager.class);
Eliot Courtney09322282017-11-09 15:31:19 +090068
Eliot Courtney47098cb2017-10-18 17:30:30 +090069 // which notification is currently being longpress-examined by the user
70 private NotificationGuts mNotificationGutsExposed;
71 private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
Eliot Courtney4a96b362017-12-14 19:38:52 +090072 protected NotificationPresenter mPresenter;
73 protected NotificationEntryManager mEntryManager;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090074 private NotificationListContainer mListContainer;
Eliot Courtney09322282017-11-09 15:31:19 +090075 private NotificationInfo.CheckSaveListener mCheckSaveListener;
76 private OnSettingsClickListener mOnSettingsClickListener;
Eliot Courtney47098cb2017-10-18 17:30:30 +090077 private String mKeyToRemoveOnGutsClosed;
78
Eliot Courtney6c313d32017-12-14 19:57:51 +090079 public NotificationGutsManager(Context context) {
Eliot Courtney47098cb2017-10-18 17:30:30 +090080 mContext = context;
81 Resources res = context.getResources();
82
83 mNonBlockablePkgs = new HashSet<>();
84 Collections.addAll(mNonBlockablePkgs, res.getStringArray(
85 com.android.internal.R.array.config_nonBlockableNotificationPackages));
86
87 mAccessibilityManager = (AccessibilityManager)
88 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
89 }
90
Eliot Courtney4a96b362017-12-14 19:38:52 +090091 public void setUpWithPresenter(NotificationPresenter presenter,
92 NotificationEntryManager entryManager, NotificationListContainer listContainer,
Eliot Courtney09322282017-11-09 15:31:19 +090093 NotificationInfo.CheckSaveListener checkSaveListener,
94 OnSettingsClickListener onSettingsClickListener) {
95 mPresenter = presenter;
Eliot Courtney4a96b362017-12-14 19:38:52 +090096 mEntryManager = entryManager;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +090097 mListContainer = listContainer;
Eliot Courtney09322282017-11-09 15:31:19 +090098 mCheckSaveListener = checkSaveListener;
99 mOnSettingsClickListener = onSettingsClickListener;
100 }
101
Eliot Courtney47098cb2017-10-18 17:30:30 +0900102 public String getKeyToRemoveOnGutsClosed() {
103 return mKeyToRemoveOnGutsClosed;
104 }
105
106 public void setKeyToRemoveOnGutsClosed(String keyToRemoveOnGutsClosed) {
107 mKeyToRemoveOnGutsClosed = keyToRemoveOnGutsClosed;
108 }
109
yoshiki iguchia85c2a02018-01-12 11:28:06 +0900110 public void onDensityOrFontScaleChanged(ExpandableNotificationRow row) {
111 setExposedGuts(row.getGuts());
112 bindGuts(row);
113 }
114
Eliot Courtney47098cb2017-10-18 17:30:30 +0900115 private void saveAndCloseNotificationMenu(
116 ExpandableNotificationRow row, NotificationGuts guts, View done) {
117 guts.resetFalsingCheck();
118 int[] rowLocation = new int[2];
119 int[] doneLocation = new int[2];
120 row.getLocationOnScreen(rowLocation);
121 done.getLocationOnScreen(doneLocation);
122
123 final int centerX = done.getWidth() / 2;
124 final int centerY = done.getHeight() / 2;
125 final int x = doneLocation[0] - rowLocation[0] + centerX;
126 final int y = doneLocation[1] - rowLocation[1] + centerY;
127 closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
128 true /* removeControls */, x, y, true /* resetMenu */);
129 }
130
131 /**
132 * Sends an intent to open the notification settings for a particular package and optional
133 * channel.
134 */
135 private void startAppNotificationSettingsActivity(String packageName, final int appUid,
136 final NotificationChannel channel) {
137 final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
138 intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
139 intent.putExtra(Settings.EXTRA_APP_UID, appUid);
140 if (channel != null) {
141 intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
142 }
143 mPresenter.startNotificationGutsIntent(intent, appUid);
144 }
145
146 public void bindGuts(final ExpandableNotificationRow row) {
147 bindGuts(row, mGutsMenuItem);
148 }
149
150 private void bindGuts(final ExpandableNotificationRow row,
151 NotificationMenuRowPlugin.MenuItem item) {
152 row.inflateGuts();
153 row.setGutsView(item);
154 final StatusBarNotification sbn = row.getStatusBarNotification();
155 row.setTag(sbn.getPackageName());
156 final NotificationGuts guts = row.getGuts();
157 guts.setClosedListener((NotificationGuts g) -> {
158 if (!g.willBeRemoved() && !row.isRemoved()) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900159 mListContainer.onHeightChanged(
Eliot Courtney47098cb2017-10-18 17:30:30 +0900160 row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
161 }
162 if (mNotificationGutsExposed == g) {
163 mNotificationGutsExposed = null;
164 mGutsMenuItem = null;
165 }
166 String key = sbn.getKey();
167 if (key.equals(mKeyToRemoveOnGutsClosed)) {
168 mKeyToRemoveOnGutsClosed = null;
Eliot Courtney4a96b362017-12-14 19:38:52 +0900169 mEntryManager.removeNotification(key, mEntryManager.getLatestRankingMap());
Eliot Courtney47098cb2017-10-18 17:30:30 +0900170 }
171 });
172
173 View gutsView = item.getGutsView();
174 if (gutsView instanceof NotificationSnooze) {
175 NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900176 snoozeGuts.setSnoozeListener(mListContainer.getSwipeActionHelper());
Eliot Courtney47098cb2017-10-18 17:30:30 +0900177 snoozeGuts.setStatusBarNotification(sbn);
178 snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
179 guts.setHeightChangedListener((NotificationGuts g) -> {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900180 mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
Eliot Courtney47098cb2017-10-18 17:30:30 +0900181 });
182 }
183
184 if (gutsView instanceof NotificationInfo) {
185 final UserHandle userHandle = sbn.getUser();
186 PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
187 userHandle.getIdentifier());
188 final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
189 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
190 final String pkg = sbn.getPackageName();
191 NotificationInfo info = (NotificationInfo) gutsView;
192 // Settings link is only valid for notifications that specify a user, unless this is the
193 // system user.
194 NotificationInfo.OnSettingsClickListener onSettingsClick = null;
195 if (!userHandle.equals(UserHandle.ALL)
Eliot Courtney09322282017-11-09 15:31:19 +0900196 || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
Eliot Courtney47098cb2017-10-18 17:30:30 +0900197 onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
198 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
199 guts.resetFalsingCheck();
Julia Reynolds84dc96b2017-11-14 09:51:01 -0500200 mOnSettingsClickListener.onClick(sbn.getKey());
Eliot Courtney47098cb2017-10-18 17:30:30 +0900201 startAppNotificationSettingsActivity(pkg, appUid, channel);
202 };
203 }
204 final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
205 Intent intent) -> {
206 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
207 guts.resetFalsingCheck();
208 mPresenter.startNotificationGutsIntent(intent, sbn.getUid());
209 };
210 final View.OnClickListener onDoneClick = (View v) -> {
211 saveAndCloseNotificationMenu(row, guts, v);
212 };
213
214 ArraySet<NotificationChannel> channels = new ArraySet<>();
215 channels.add(row.getEntry().channel);
216 if (row.isSummaryWithChildren()) {
217 // If this is a summary, then add in the children notification channels for the
218 // same user and pkg.
219 final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
220 final int numChildren = childrenRows.size();
221 for (int i = 0; i < numChildren; i++) {
222 final ExpandableNotificationRow childRow = childrenRows.get(i);
223 final NotificationChannel childChannel = childRow.getEntry().channel;
224 final StatusBarNotification childSbn = childRow.getStatusBarNotification();
225 if (childSbn.getUser().equals(userHandle) &&
226 childSbn.getPackageName().equals(pkg)) {
227 channels.add(childChannel);
228 }
229 }
230 }
231 try {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500232 info.bindNotification(pmUser, iNotificationManager, pkg, row.getEntry().channel,
233 channels.size(), sbn, mCheckSaveListener, onSettingsClick,
234 onAppSettingsClick, mNonBlockablePkgs);
Eliot Courtney47098cb2017-10-18 17:30:30 +0900235 } catch (RemoteException e) {
236 Log.e(TAG, e.toString());
237 }
238 }
239 }
240
241 /**
242 * Closes guts or notification menus that might be visible and saves any changes.
243 *
244 * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
245 * @param force true if guts should be closed regardless of state (used for snooze only).
246 * @param removeControls true if controls (e.g. info) should be closed.
247 * @param x if closed based on touch location, this is the x touch location.
248 * @param y if closed based on touch location, this is the y touch location.
249 * @param resetMenu if any notification menus that might be revealed should be closed.
250 */
251 public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
252 int x, int y, boolean resetMenu) {
253 if (mNotificationGutsExposed != null) {
254 mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
255 }
256 if (resetMenu) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900257 mListContainer.resetExposedMenuView(false /* animate */, true /* force */);
Eliot Courtney47098cb2017-10-18 17:30:30 +0900258 }
259 }
260
261 /**
262 * Returns the exposed NotificationGuts or null if none are exposed.
263 */
264 public NotificationGuts getExposedGuts() {
265 return mNotificationGutsExposed;
266 }
267
268 public void setExposedGuts(NotificationGuts guts) {
269 mNotificationGutsExposed = guts;
270 }
271
272 /**
yoshiki iguchia85c2a02018-01-12 11:28:06 +0900273 * Opens guts on the given ExpandableNotificationRow |v|.
Eliot Courtney47098cb2017-10-18 17:30:30 +0900274 *
275 * @param v ExpandableNotificationRow to open guts on
276 * @param x x coordinate of origin of circular reveal
277 * @param y y coordinate of origin of circular reveal
278 * @param item MenuItem the guts should display
279 * @return true if guts was opened
280 */
281 public boolean openGuts(View v, int x, int y,
282 NotificationMenuRowPlugin.MenuItem item) {
283 if (!(v instanceof ExpandableNotificationRow)) {
284 return false;
285 }
286
287 if (v.getWindowToken() == null) {
288 Log.e(TAG, "Trying to show notification guts, but not attached to window");
289 return false;
290 }
291
292 final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
293 if (row.isDark()) {
294 return false;
295 }
296 v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
297 if (row.areGutsExposed()) {
298 closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
299 true /* removeControls */, -1 /* x */, -1 /* y */,
300 true /* resetMenu */);
301 return false;
302 }
303 bindGuts(row, item);
304 NotificationGuts guts = row.getGuts();
305
306 // Assume we are a status_bar_notification_row
307 if (guts == null) {
308 // This view has no guts. Examples are the more card or the dismiss all view
309 return false;
310 }
311
312 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS);
313
314 // ensure that it's laid but not visible until actually laid out
315 guts.setVisibility(View.INVISIBLE);
316 // Post to ensure the the guts are properly laid out.
317 guts.post(new Runnable() {
318 @Override
319 public void run() {
320 if (row.getWindowToken() == null) {
321 Log.e(TAG, "Trying to show notification guts, but not attached to "
322 + "window");
323 return;
324 }
325 closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
326 true /* removeControls */, -1 /* x */, -1 /* y */,
327 false /* resetMenu */);
328 guts.setVisibility(View.VISIBLE);
yoshiki iguchia85c2a02018-01-12 11:28:06 +0900329
Eliot Courtney47098cb2017-10-18 17:30:30 +0900330 final boolean needsFalsingProtection =
331 (mPresenter.isPresenterLocked() &&
332 !mAccessibilityManager.isTouchExplorationEnabled());
yoshiki iguchia85c2a02018-01-12 11:28:06 +0900333 guts.openControls(x, y, needsFalsingProtection, () -> {
334 // Move the notification view back over the menu
335 row.resetTranslation();
336 });
337
Eliot Courtney47098cb2017-10-18 17:30:30 +0900338 row.closeRemoteInput();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900339 mListContainer.onHeightChanged(row, true /* needsAnimation */);
Eliot Courtney47098cb2017-10-18 17:30:30 +0900340 mNotificationGutsExposed = guts;
341 mGutsMenuItem = item;
342 }
343 });
344 return true;
345 }
346
347 @Override
348 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Eliot Courtney09322282017-11-09 15:31:19 +0900349 pw.println("NotificationGutsManager state:");
350 pw.print(" mKeyToRemoveOnGutsClosed: ");
Eliot Courtney47098cb2017-10-18 17:30:30 +0900351 pw.println(mKeyToRemoveOnGutsClosed);
352 }
Julia Reynolds84dc96b2017-11-14 09:51:01 -0500353
354 public interface OnSettingsClickListener {
355 void onClick(String key);
356 }
Eliot Courtney47098cb2017-10-18 17:30:30 +0900357}