OSDN Git Service

91a381fe13b0af81103074e021d20045c3616e3d
[android-x86/frameworks-base.git] / packages / SystemUI / src / com / android / systemui / statusbar / NotificationGutsManager.java
1 /*
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  */
16 package com.android.systemui.statusbar;
17
18 import static android.app.AppOpsManager.OP_CAMERA;
19 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
20 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
21 import static android.service.notification.NotificationListenerService.Ranking
22         .USER_SENTIMENT_NEGATIVE;
23
24 import android.app.INotificationManager;
25 import android.app.NotificationChannel;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.PackageManager;
29 import android.content.res.Resources;
30 import android.net.Uri;
31 import android.os.RemoteException;
32 import android.os.ServiceManager;
33 import android.os.UserHandle;
34 import android.provider.Settings;
35 import android.service.notification.StatusBarNotification;
36 import androidx.annotation.VisibleForTesting;
37 import android.util.ArraySet;
38 import android.util.Log;
39 import android.view.HapticFeedbackConstants;
40 import android.view.View;
41 import android.view.accessibility.AccessibilityManager;
42
43 import com.android.internal.logging.MetricsLogger;
44 import com.android.internal.logging.nano.MetricsProto;
45 import com.android.systemui.Dependency;
46 import com.android.systemui.Dumpable;
47 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
48 import com.android.systemui.statusbar.phone.StatusBar;
49
50 import java.io.FileDescriptor;
51 import java.io.PrintWriter;
52 import java.util.Collections;
53 import java.util.HashSet;
54 import java.util.List;
55 import java.util.Set;
56
57 /**
58  * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
59  * closing guts, and keeping track of the currently exposed notification guts.
60  */
61 public class NotificationGutsManager implements Dumpable {
62     private static final String TAG = "NotificationGutsManager";
63
64     // Must match constant in Settings. Used to highlight preferences when linking to Settings.
65     private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
66
67     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
68     private final Context mContext;
69     private final AccessibilityManager mAccessibilityManager;
70
71     // Dependencies:
72     private final NotificationLockscreenUserManager mLockscreenUserManager =
73             Dependency.get(NotificationLockscreenUserManager.class);
74
75     // which notification is currently being longpress-examined by the user
76     private NotificationGuts mNotificationGutsExposed;
77     private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
78     protected NotificationPresenter mPresenter;
79     protected NotificationEntryManager mEntryManager;
80     private NotificationListContainer mListContainer;
81     private NotificationInfo.CheckSaveListener mCheckSaveListener;
82     private OnSettingsClickListener mOnSettingsClickListener;
83     private String mKeyToRemoveOnGutsClosed;
84
85     public NotificationGutsManager(Context context) {
86         mContext = context;
87         Resources res = context.getResources();
88
89         mAccessibilityManager = (AccessibilityManager)
90                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
91     }
92
93     public void setUpWithPresenter(NotificationPresenter presenter,
94             NotificationEntryManager entryManager, NotificationListContainer listContainer,
95             NotificationInfo.CheckSaveListener checkSaveListener,
96             OnSettingsClickListener onSettingsClickListener) {
97         mPresenter = presenter;
98         mEntryManager = entryManager;
99         mListContainer = listContainer;
100         mCheckSaveListener = checkSaveListener;
101         mOnSettingsClickListener = onSettingsClickListener;
102     }
103
104     public String getKeyToRemoveOnGutsClosed() {
105         return mKeyToRemoveOnGutsClosed;
106     }
107
108     public void setKeyToRemoveOnGutsClosed(String keyToRemoveOnGutsClosed) {
109         mKeyToRemoveOnGutsClosed = keyToRemoveOnGutsClosed;
110     }
111
112     public void onDensityOrFontScaleChanged(ExpandableNotificationRow row) {
113         setExposedGuts(row.getGuts());
114         bindGuts(row);
115     }
116
117     /**
118      * Sends an intent to open the app settings for a particular package and optional
119      * channel.
120      */
121     private void startAppNotificationSettingsActivity(String packageName, final int appUid,
122             final NotificationChannel channel, ExpandableNotificationRow row) {
123         final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
124         intent.setData(Uri.fromParts("package", packageName, null));
125         intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
126         intent.putExtra(Settings.EXTRA_APP_UID, appUid);
127         if (channel != null) {
128             intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
129         }
130         mPresenter.startNotificationGutsIntent(intent, appUid, row);
131     }
132
133     protected void startAppOpsSettingsActivity(String pkg, int uid, ArraySet<Integer> ops,
134             ExpandableNotificationRow row) {
135         if (ops.contains(OP_SYSTEM_ALERT_WINDOW)) {
136             if (ops.contains(OP_CAMERA) || ops.contains(OP_RECORD_AUDIO)) {
137                 startAppNotificationSettingsActivity(pkg, uid, null, row);
138             } else {
139                 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
140                 intent.setData(Uri.fromParts("package", pkg, null));
141                 mPresenter.startNotificationGutsIntent(intent, uid, row);
142             }
143         } else if (ops.contains(OP_CAMERA) || ops.contains(OP_RECORD_AUDIO)) {
144             Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
145             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, pkg);
146             mPresenter.startNotificationGutsIntent(intent, uid, row);
147         }
148     }
149
150     public void bindGuts(final ExpandableNotificationRow row) {
151         bindGuts(row, mGutsMenuItem);
152     }
153
154     private void bindGuts(final ExpandableNotificationRow row,
155             NotificationMenuRowPlugin.MenuItem item) {
156         StatusBarNotification sbn = row.getStatusBarNotification();
157
158         row.inflateGuts();
159         row.setGutsView(item);
160         row.setTag(sbn.getPackageName());
161         row.getGuts().setClosedListener((NotificationGuts g) -> {
162             row.onGutsClosed();
163             if (!g.willBeRemoved() && !row.isRemoved()) {
164                 mListContainer.onHeightChanged(
165                         row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
166             }
167             if (mNotificationGutsExposed == g) {
168                 mNotificationGutsExposed = null;
169                 mGutsMenuItem = null;
170             }
171             String key = sbn.getKey();
172             if (key.equals(mKeyToRemoveOnGutsClosed)) {
173                 mKeyToRemoveOnGutsClosed = null;
174                 mEntryManager.removeNotification(key, mEntryManager.getLatestRankingMap());
175             }
176         });
177
178         View gutsView = item.getGutsView();
179         if (gutsView instanceof NotificationSnooze) {
180             initializeSnoozeView(row, (NotificationSnooze) gutsView);
181         } else if (gutsView instanceof AppOpsInfo) {
182             initializeAppOpsInfo(row, (AppOpsInfo) gutsView);
183         } else if (gutsView instanceof NotificationInfo) {
184             initializeNotificationInfo(row, (NotificationInfo) gutsView);
185         }
186     }
187
188     /**
189      * Sets up the {@link NotificationSnooze} inside the notification row's guts.
190      *
191      * @param row view to set up the guts for
192      * @param notificationSnoozeView view to set up/bind within {@code row}
193      */
194     private void initializeSnoozeView(
195             final ExpandableNotificationRow row,
196             NotificationSnooze notificationSnoozeView) {
197         NotificationGuts guts = row.getGuts();
198         StatusBarNotification sbn = row.getStatusBarNotification();
199
200         notificationSnoozeView.setSnoozeListener(mListContainer.getSwipeActionHelper());
201         notificationSnoozeView.setStatusBarNotification(sbn);
202         notificationSnoozeView.setSnoozeOptions(row.getEntry().snoozeCriteria);
203         guts.setHeightChangedListener((NotificationGuts g) -> {
204             mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
205         });
206     }
207
208     /**
209      * Sets up the {@link AppOpsInfo} inside the notification row's guts.
210      *
211      * @param row view to set up the guts for
212      * @param appOpsInfoView view to set up/bind within {@code row}
213      */
214     private void initializeAppOpsInfo(
215             final ExpandableNotificationRow row,
216             AppOpsInfo appOpsInfoView) {
217         NotificationGuts guts = row.getGuts();
218         StatusBarNotification sbn = row.getStatusBarNotification();
219         UserHandle userHandle = sbn.getUser();
220         PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
221                 userHandle.getIdentifier());
222
223         AppOpsInfo.OnSettingsClickListener onSettingsClick =
224                 (View v, String pkg, int uid, ArraySet<Integer> ops) -> {
225             mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_OPS_GUTS_SETTINGS);
226             guts.resetFalsingCheck();
227             startAppOpsSettingsActivity(pkg, uid, ops, row);
228         };
229         if (!row.getEntry().mActiveAppOps.isEmpty()) {
230             appOpsInfoView.bindGuts(pmUser, onSettingsClick, sbn, row.getEntry().mActiveAppOps);
231         }
232     }
233
234     /**
235      * Sets up the {@link NotificationInfo} inside the notification row's guts.
236      *
237      * @param row view to set up the guts for
238      * @param notificationInfoView view to set up/bind within {@code row}
239      */
240     @VisibleForTesting
241     void initializeNotificationInfo(
242             final ExpandableNotificationRow row,
243             NotificationInfo notificationInfoView) {
244         NotificationGuts guts = row.getGuts();
245         StatusBarNotification sbn = row.getStatusBarNotification();
246         String packageName = sbn.getPackageName();
247         // Settings link is only valid for notifications that specify a non-system user
248         NotificationInfo.OnSettingsClickListener onSettingsClick = null;
249         UserHandle userHandle = sbn.getUser();
250         PackageManager pmUser = StatusBar.getPackageManagerForUser(
251                 mContext, userHandle.getIdentifier());
252         INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
253                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
254         final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick =
255                 (View v, Intent intent) -> {
256                     mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
257                     guts.resetFalsingCheck();
258                     mPresenter.startNotificationGutsIntent(intent, sbn.getUid(), row);
259                 };
260         boolean isForBlockingHelper = row.isBlockingHelperShowing();
261
262         if (!userHandle.equals(UserHandle.ALL)
263                 || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
264             onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
265                 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
266                 guts.resetFalsingCheck();
267                 mOnSettingsClickListener.onClick(sbn.getKey());
268                 startAppNotificationSettingsActivity(packageName, appUid, channel, row);
269             };
270         }
271
272         try {
273             notificationInfoView.bindNotification(
274                     pmUser,
275                     iNotificationManager,
276                     packageName,
277                     row.getEntry().channel,
278                     row.getNumUniqueChannels(),
279                     sbn,
280                     mCheckSaveListener,
281                     onSettingsClick,
282                     onAppSettingsClick,
283                     row.getIsNonblockable(),
284                     isForBlockingHelper,
285                     row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE);
286         } catch (RemoteException e) {
287             Log.e(TAG, e.toString());
288         }
289     }
290
291     /**
292      * Closes guts or notification menus that might be visible and saves any changes.
293      *
294      * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
295      * @param force true if guts should be closed regardless of state (used for snooze only).
296      * @param removeControls true if controls (e.g. info) should be closed.
297      * @param x if closed based on touch location, this is the x touch location.
298      * @param y if closed based on touch location, this is the y touch location.
299      * @param resetMenu if any notification menus that might be revealed should be closed.
300      */
301     public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
302             int x, int y, boolean resetMenu) {
303         if (mNotificationGutsExposed != null) {
304             mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
305         }
306         if (resetMenu) {
307             mListContainer.resetExposedMenuView(false /* animate */, true /* force */);
308         }
309     }
310
311     /**
312      * Returns the exposed NotificationGuts or null if none are exposed.
313      */
314     public NotificationGuts getExposedGuts() {
315         return mNotificationGutsExposed;
316     }
317
318     public void setExposedGuts(NotificationGuts guts) {
319         mNotificationGutsExposed = guts;
320     }
321
322     /**
323      * Opens guts on the given ExpandableNotificationRow {@code view}. This handles opening guts for
324      * the normal half-swipe and long-press use cases via a circular reveal. When the blocking
325      * helper needs to be shown on the row, this will skip the circular reveal.
326      *
327      * @param view ExpandableNotificationRow to open guts on
328      * @param x x coordinate of origin of circular reveal
329      * @param y y coordinate of origin of circular reveal
330      * @param menuItem MenuItem the guts should display
331      * @return true if guts was opened
332      */
333     boolean openGuts(
334             View view,
335             int x,
336             int y,
337             NotificationMenuRowPlugin.MenuItem menuItem) {
338         if (!(view instanceof ExpandableNotificationRow)) {
339             return false;
340         }
341
342         if (view.getWindowToken() == null) {
343             Log.e(TAG, "Trying to show notification guts, but not attached to window");
344             return false;
345         }
346
347         final ExpandableNotificationRow row = (ExpandableNotificationRow) view;
348         if (row.isDark()) {
349             return false;
350         }
351         view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
352         if (row.areGutsExposed()) {
353             closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
354                     true /* removeControls */, -1 /* x */, -1 /* y */,
355                     true /* resetMenu */);
356             return false;
357         }
358         bindGuts(row, menuItem);
359         NotificationGuts guts = row.getGuts();
360
361         // Assume we are a status_bar_notification_row
362         if (guts == null) {
363             // This view has no guts. Examples are the more card or the dismiss all view
364             return false;
365         }
366
367         mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS);
368
369         // ensure that it's laid but not visible until actually laid out
370         guts.setVisibility(View.INVISIBLE);
371         // Post to ensure the the guts are properly laid out.
372         guts.post(new Runnable() {
373             @Override
374             public void run() {
375                 if (row.getWindowToken() == null) {
376                     Log.e(TAG, "Trying to show notification guts in post(), but not attached to "
377                             + "window");
378                     return;
379                 }
380                 closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
381                         true /* removeControls */, -1 /* x */, -1 /* y */,
382                         false /* resetMenu */);
383                 guts.setVisibility(View.VISIBLE);
384
385                 final boolean needsFalsingProtection =
386                         (mPresenter.isPresenterLocked() &&
387                                 !mAccessibilityManager.isTouchExplorationEnabled());
388
389                 guts.openControls(
390                         !row.isBlockingHelperShowing(),
391                         x,
392                         y,
393                         needsFalsingProtection,
394                         row::onGutsOpened);
395
396                 row.closeRemoteInput();
397                 mListContainer.onHeightChanged(row, true /* needsAnimation */);
398                 mNotificationGutsExposed = guts;
399                 mGutsMenuItem = menuItem;
400             }
401         });
402         return true;
403     }
404
405     @Override
406     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
407         pw.println("NotificationGutsManager state:");
408         pw.print("  mKeyToRemoveOnGutsClosed: ");
409         pw.println(mKeyToRemoveOnGutsClosed);
410     }
411
412     public interface OnSettingsClickListener {
413         void onClick(String key);
414     }
415 }