blob: 2a9a815e12d1503f0cc871c6102ceebdc5a7c0ec [file] [log] [blame]
Mady Mellor87d79452017-01-10 11:52:52 -08001/*
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
Rohan Shah20790b82018-07-02 17:21:04 -070017package com.android.systemui.statusbar.notification.row;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -050018
Gus Prevas9abc5062018-10-31 16:11:04 -040019import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
20import static android.app.NotificationManager.IMPORTANCE_HIGH;
21import static android.app.NotificationManager.IMPORTANCE_LOW;
Julia Reynoldse0341482018-03-08 14:42:50 -050022import static android.app.NotificationManager.IMPORTANCE_MIN;
Julia Reynolds3aedded2017-03-31 14:42:09 -040023import static android.app.NotificationManager.IMPORTANCE_NONE;
24
Julia Reynolds437cdb12018-01-03 12:27:24 -050025import android.animation.Animator;
26import android.animation.AnimatorListenerAdapter;
27import android.animation.AnimatorSet;
28import android.animation.ObjectAnimator;
Gus Prevas9abc5062018-10-31 16:11:04 -040029import android.annotation.IntDef;
Rohan Shahca0447e2018-03-30 15:18:27 -070030import android.annotation.Nullable;
Mady Mellor87d79452017-01-10 11:52:52 -080031import android.app.INotificationManager;
Julia Reynolds3aedded2017-03-31 14:42:09 -040032import android.app.Notification;
Mady Mellor87d79452017-01-10 11:52:52 -080033import android.app.NotificationChannel;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -050034import android.app.NotificationChannelGroup;
Mady Mellor87d79452017-01-10 11:52:52 -080035import android.content.Context;
Julia Reynolds3aedded2017-03-31 14:42:09 -040036import android.content.Intent;
37import android.content.pm.ActivityInfo;
Mady Mellor87d79452017-01-10 11:52:52 -080038import android.content.pm.ApplicationInfo;
Mady Mellor87d79452017-01-10 11:52:52 -080039import android.content.pm.PackageManager;
Julia Reynolds3aedded2017-03-31 14:42:09 -040040import android.content.pm.ResolveInfo;
Mady Mellor87d79452017-01-10 11:52:52 -080041import android.graphics.drawable.Drawable;
William Brockman75cf66a2018-12-21 13:25:01 -050042import android.metrics.LogMaker;
Rohan Shahca0447e2018-03-30 15:18:27 -070043import android.os.Handler;
Mady Mellor87d79452017-01-10 11:52:52 -080044import android.os.RemoteException;
Julia Reynolds3aedded2017-03-31 14:42:09 -040045import android.service.notification.StatusBarNotification;
46import android.text.TextUtils;
Mady Mellor87d79452017-01-10 11:52:52 -080047import android.util.AttributeSet;
Rohan Shahca0447e2018-03-30 15:18:27 -070048import android.util.Log;
Mady Mellor87d79452017-01-10 11:52:52 -080049import android.view.View;
Julia Reynoldse0341482018-03-08 14:42:50 -050050import android.view.ViewGroup;
Geoffrey Pitschd94e7882017-04-06 09:52:11 -040051import android.view.accessibility.AccessibilityEvent;
Mady Mellor87d79452017-01-10 11:52:52 -080052import android.widget.ImageView;
53import android.widget.LinearLayout;
Mady Mellor87d79452017-01-10 11:52:52 -080054import android.widget.TextView;
55
Rohan Shah524cf7b2018-03-15 14:40:02 -070056import com.android.internal.annotations.VisibleForTesting;
Mady Mellor87d79452017-01-10 11:52:52 -080057import com.android.internal.logging.MetricsLogger;
58import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Rohan Shah524cf7b2018-03-15 14:40:02 -070059import com.android.systemui.Dependency;
Julia Reynolds437cdb12018-01-03 12:27:24 -050060import com.android.systemui.Interpolators;
Mady Mellor87d79452017-01-10 11:52:52 -080061import com.android.systemui.R;
Gus Prevas9abc5062018-10-31 16:11:04 -040062import com.android.systemui.statusbar.notification.NotificationUtils;
Rohan Shah20790b82018-07-02 17:21:04 -070063import com.android.systemui.statusbar.notification.logging.NotificationCounters;
Mady Mellor87d79452017-01-10 11:52:52 -080064
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050065import java.util.List;
Mady Mellor87d79452017-01-10 11:52:52 -080066
67/**
Rohan Shahda5dcdd2018-04-27 17:21:50 -070068 * The guts of a notification revealed when performing a long press. This also houses the blocking
69 * helper affordance that allows a user to keep/stop notifications after swiping one away.
Mady Mellor87d79452017-01-10 11:52:52 -080070 */
Mady Mellor95d743c2017-01-10 12:05:27 -080071public class NotificationInfo extends LinearLayout implements NotificationGuts.GutsContent {
Mady Mellor87d79452017-01-10 11:52:52 -080072 private static final String TAG = "InfoGuts";
73
Gus Prevas894d9152018-11-12 13:51:40 -050074 @IntDef(prefix = { "ACTION_" }, value = {
75 ACTION_NONE,
76 ACTION_UNDO,
77 ACTION_TOGGLE_SILENT,
78 ACTION_BLOCK,
Gus Prevas9abc5062018-10-31 16:11:04 -040079 })
Gus Prevas894d9152018-11-12 13:51:40 -050080 public @interface NotificationInfoAction {
81 }
Gus Prevas9abc5062018-10-31 16:11:04 -040082
Gus Prevas894d9152018-11-12 13:51:40 -050083 public static final int ACTION_NONE = 0;
84 public static final int ACTION_UNDO = 1;
85 public static final int ACTION_TOGGLE_SILENT = 2;
86 public static final int ACTION_BLOCK = 3;
Gus Prevas9abc5062018-10-31 16:11:04 -040087
Mady Mellor87d79452017-01-10 11:52:52 -080088 private INotificationManager mINotificationManager;
Julia Reynolds437cdb12018-01-03 12:27:24 -050089 private PackageManager mPm;
Rohan Shahda5dcdd2018-04-27 17:21:50 -070090 private MetricsLogger mMetricsLogger;
Julia Reynolds437cdb12018-01-03 12:27:24 -050091
Rohan Shahca0447e2018-03-30 15:18:27 -070092 private String mPackageName;
Geoffrey Pitschd94e7882017-04-06 09:52:11 -040093 private String mAppName;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050094 private int mAppUid;
Julia Reynolds268647a2018-10-25 16:54:27 -040095 private String mDelegatePkg;
Rohan Shahca0447e2018-03-30 15:18:27 -070096 private int mNumUniqueChannelsInRow;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050097 private NotificationChannel mSingleNotificationChannel;
Gus Prevas9abc5062018-10-31 16:11:04 -040098 private int mStartingChannelImportance;
Gus Prevascaed15c2019-01-18 14:19:51 -050099 private boolean mWasShownHighPriority;
100 /**
101 * The last importance level chosen by the user. Null if the user has not chosen an importance
102 * level; non-null once the user takes an action which indicates an explicit preference.
103 */
104 @Nullable private Integer mChosenImportance;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500105 private boolean mIsSingleDefaultChannel;
Rohan Shah63411fc2018-03-28 19:05:52 -0700106 private boolean mIsNonblockable;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500107 private StatusBarNotification mSbn;
108 private AnimatorSet mExpandAnimation;
Julia Reynoldse0341482018-03-08 14:42:50 -0500109 private boolean mIsForeground;
Julia Reynolds35765d82018-08-17 11:39:19 -0400110 private boolean mIsDeviceProvisioned;
Mady Mellor87d79452017-01-10 11:52:52 -0800111
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400112 private CheckSaveListener mCheckSaveListener;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500113 private OnSettingsClickListener mOnSettingsClickListener;
Julia Reynolds3aedded2017-03-31 14:42:09 -0400114 private OnAppSettingsClickListener mAppSettingsClickListener;
Mady Mellor95d743c2017-01-10 12:05:27 -0800115 private NotificationGuts mGutsContainer;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700116
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700117 /** Whether this view is being shown as part of the blocking helper. */
Rohan Shah524cf7b2018-03-15 14:40:02 -0700118 private boolean mIsForBlockingHelper;
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500119 private boolean mNegativeUserSentiment;
Mady Mellor87d79452017-01-10 11:52:52 -0800120
Rohan Shahdd588c72018-05-09 20:32:15 -0700121 /**
122 * String that describes how the user exit or quit out of this view, also used as a counter tag.
123 */
124 private String mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700125
126 private OnClickListener mOnKeepShowing = v -> {
Rohan Shahdd588c72018-05-09 20:32:15 -0700127 mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700128 closeControls(v);
Eyal Posenera9cf9c72018-12-18 16:23:54 +0200129 mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
130 .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_STAY_SILENT));
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700131 };
Julia Reynolds437cdb12018-01-03 12:27:24 -0500132
Gus Prevas9abc5062018-10-31 16:11:04 -0400133 private OnClickListener mOnToggleSilent = v -> {
134 Runnable saveImportance = () -> {
Gus Prevas894d9152018-11-12 13:51:40 -0500135 swapContent(ACTION_TOGGLE_SILENT, true /* animate */);
Eyal Posenera9cf9c72018-12-18 16:23:54 +0200136 mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
137 .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_ALERT_ME));
Gus Prevas9abc5062018-10-31 16:11:04 -0400138 };
139 if (mCheckSaveListener != null) {
140 mCheckSaveListener.checkSave(saveImportance, mSbn);
141 } else {
142 saveImportance.run();
143 }
144 };
145
Rohan Shahca0447e2018-03-30 15:18:27 -0700146 private OnClickListener mOnStopOrMinimizeNotifications = v -> {
Gus Prevas533836a2018-09-24 17:15:32 -0400147 Runnable saveImportance = () -> {
Gus Prevas894d9152018-11-12 13:51:40 -0500148 swapContent(ACTION_BLOCK, true /* animate */);
Eyal Posenera9cf9c72018-12-18 16:23:54 +0200149 mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
150 .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_BLOCKED));
Gus Prevas533836a2018-09-24 17:15:32 -0400151 };
152 if (mCheckSaveListener != null) {
153 mCheckSaveListener.checkSave(saveImportance, mSbn);
154 } else {
155 saveImportance.run();
156 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500157 };
158
159 private OnClickListener mOnUndo = v -> {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700160 // Reset exit counter that we'll log and record an undo event separately (not an exit event)
Rohan Shahdd588c72018-05-09 20:32:15 -0700161 mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700162 logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
William Brockman75cf66a2018-12-21 13:25:01 -0500163 mMetricsLogger.write(importanceChangeLogMaker().setType(MetricsEvent.TYPE_DISMISS));
Gus Prevas894d9152018-11-12 13:51:40 -0500164 swapContent(ACTION_UNDO, true /* animate */);
Eyal Posenera9cf9c72018-12-18 16:23:54 +0200165 mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
166 .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_UNDO));
Julia Reynolds437cdb12018-01-03 12:27:24 -0500167 };
168
Mady Mellor87d79452017-01-10 11:52:52 -0800169 public NotificationInfo(Context context, AttributeSet attrs) {
170 super(context, attrs);
171 }
172
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400173 // Specify a CheckSaveListener to override when/if the user's changes are committed.
174 public interface CheckSaveListener {
175 // Invoked when importance has changed and the NotificationInfo wants to try to save it.
176 // Listener should run saveImportance unless the change should be canceled.
Eliot Courtney47098cb2017-10-18 17:30:30 +0900177 void checkSave(Runnable saveImportance, StatusBarNotification sbn);
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400178 }
179
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500180 public interface OnSettingsClickListener {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500181 void onClick(View v, NotificationChannel channel, int appUid);
Mady Mellor87d79452017-01-10 11:52:52 -0800182 }
183
Julia Reynolds3aedded2017-03-31 14:42:09 -0400184 public interface OnAppSettingsClickListener {
185 void onClick(View v, Intent intent);
186 }
187
Rohan Shah524cf7b2018-03-15 14:40:02 -0700188 @VisibleForTesting
189 void bindNotification(
190 final PackageManager pm,
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500191 final INotificationManager iNotificationManager,
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500192 final String pkg,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500193 final NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700194 final int numUniqueChannelsInRow,
Julia Reynolds3aedded2017-03-31 14:42:09 -0400195 final StatusBarNotification sbn,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500196 final CheckSaveListener checkSaveListener,
197 final OnSettingsClickListener onSettingsClick,
198 final OnAppSettingsClickListener onAppSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400199 boolean isDeviceProvisioned,
Gus Prevas9abc5062018-10-31 16:11:04 -0400200 boolean isNonblockable,
Gus Prevascaed15c2019-01-18 14:19:51 -0500201 int importance,
202 boolean wasShownHighPriority)
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500203 throws RemoteException {
Rohan Shahca0447e2018-03-30 15:18:27 -0700204 bindNotification(pm, iNotificationManager, pkg, notificationChannel,
205 numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400206 onAppSettingsClick, isDeviceProvisioned, isNonblockable,
Gus Prevas82ad0592018-11-28 14:27:40 -0500207 false /* isBlockingHelper */, false /* isUserSentimentNegative */,
Gus Prevascaed15c2019-01-18 14:19:51 -0500208 importance, wasShownHighPriority);
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500209 }
210
Rohan Shah524cf7b2018-03-15 14:40:02 -0700211 public void bindNotification(
212 PackageManager pm,
213 INotificationManager iNotificationManager,
214 String pkg,
215 NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700216 int numUniqueChannelsInRow,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700217 StatusBarNotification sbn,
218 CheckSaveListener checkSaveListener,
219 OnSettingsClickListener onSettingsClick,
220 OnAppSettingsClickListener onAppSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400221 boolean isDeviceProvisioned,
Rohan Shah63411fc2018-03-28 19:05:52 -0700222 boolean isNonblockable,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700223 boolean isForBlockingHelper,
Gus Prevas9abc5062018-10-31 16:11:04 -0400224 boolean isUserSentimentNegative,
Gus Prevascaed15c2019-01-18 14:19:51 -0500225 int importance,
226 boolean wasShownHighPriority)
Rohan Shah524cf7b2018-03-15 14:40:02 -0700227 throws RemoteException {
Mady Mellor87d79452017-01-10 11:52:52 -0800228 mINotificationManager = iNotificationManager;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700229 mMetricsLogger = Dependency.get(MetricsLogger.class);
Rohan Shahca0447e2018-03-30 15:18:27 -0700230 mPackageName = pkg;
231 mNumUniqueChannelsInRow = numUniqueChannelsInRow;
Julia Reynolds3aedded2017-03-31 14:42:09 -0400232 mSbn = sbn;
233 mPm = pm;
234 mAppSettingsClickListener = onAppSettingsClick;
Rohan Shahca0447e2018-03-30 15:18:27 -0700235 mAppName = mPackageName;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500236 mCheckSaveListener = checkSaveListener;
237 mOnSettingsClickListener = onSettingsClick;
238 mSingleNotificationChannel = notificationChannel;
Gus Prevascaed15c2019-01-18 14:19:51 -0500239 mStartingChannelImportance = mSingleNotificationChannel.getImportance();
240 mWasShownHighPriority = wasShownHighPriority;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700241 mNegativeUserSentiment = isUserSentimentNegative;
Rohan Shah63411fc2018-03-28 19:05:52 -0700242 mIsNonblockable = isNonblockable;
Julia Reynoldse0341482018-03-08 14:42:50 -0500243 mIsForeground =
244 (mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700245 mIsForBlockingHelper = isForBlockingHelper;
Rohan Shahdbd64e72018-03-28 14:46:50 -0700246 mAppUid = mSbn.getUid();
Julia Reynolds268647a2018-10-25 16:54:27 -0400247 mDelegatePkg = mSbn.getOpPkg();
Julia Reynolds35765d82018-08-17 11:39:19 -0400248 mIsDeviceProvisioned = isDeviceProvisioned;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500249
Julia Reynolds437cdb12018-01-03 12:27:24 -0500250 int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400251 pkg, mAppUid, false /* includeDeleted */);
Rohan Shahca0447e2018-03-30 15:18:27 -0700252 if (mNumUniqueChannelsInRow == 0) {
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400253 throw new IllegalArgumentException("bindNotification requires at least one channel");
254 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500255 // Special behavior for the Default channel if no other channels have been defined.
Rohan Shahca0447e2018-03-30 15:18:27 -0700256 mIsSingleDefaultChannel = mNumUniqueChannelsInRow == 1
Rohan Shahdbd64e72018-03-28 14:46:50 -0700257 && mSingleNotificationChannel.getId().equals(
258 NotificationChannel.DEFAULT_CHANNEL_ID)
259 && numTotalChannels == 1;
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400260 }
261
Julia Reynolds437cdb12018-01-03 12:27:24 -0500262 bindHeader();
263 bindPrompt();
264 bindButtons();
Eyal Posenera9cf9c72018-12-18 16:23:54 +0200265
266 mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
267 .setSubtype(MetricsEvent.BLOCKING_HELPER_DISPLAY));
Julia Reynolds437cdb12018-01-03 12:27:24 -0500268 }
269
270 private void bindHeader() throws RemoteException {
271 // Package name
272 Drawable pkgicon = null;
273 ApplicationInfo info;
274 try {
Rohan Shahca0447e2018-03-30 15:18:27 -0700275 info = mPm.getApplicationInfo(
276 mPackageName,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500277 PackageManager.MATCH_UNINSTALLED_PACKAGES
278 | PackageManager.MATCH_DISABLED_COMPONENTS
279 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
280 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
281 if (info != null) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500282 mAppName = String.valueOf(mPm.getApplicationLabel(info));
283 pkgicon = mPm.getApplicationIcon(info);
Geoffrey Pitschee8c81e2017-03-23 11:38:56 -0400284 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500285 } catch (PackageManager.NameNotFoundException e) {
286 // app is gone, just show package name and generic icon
287 pkgicon = mPm.getDefaultActivityIcon();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500288 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500289 ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400290 ((TextView) findViewById(R.id.pkgname)).setText(mAppName);
Mady Mellor87d79452017-01-10 11:52:52 -0800291
Julia Reynolds268647a2018-10-25 16:54:27 -0400292 // Delegate
293 bindDelegate();
Daniel Sandlerd0a52b62018-10-31 20:13:22 +0000294
295 // Settings button.
296 final View settingsButton = findViewById(R.id.info);
297 if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
298 settingsButton.setVisibility(View.VISIBLE);
299 final int appUidF = mAppUid;
300 settingsButton.setOnClickListener(
301 (View view) -> {
302 logBlockingHelperCounter(
303 NotificationCounters.BLOCKING_HELPER_NOTIF_SETTINGS);
304 mOnSettingsClickListener.onClick(view,
305 mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel,
306 appUidF);
307 });
308 } else {
309 settingsButton.setVisibility(View.GONE);
310 }
311 }
312
Julia Reynolds268647a2018-10-25 16:54:27 -0400313 private void bindPrompt() throws RemoteException {
Daniel Sandlerd0a52b62018-10-31 20:13:22 +0000314 final TextView blockPrompt = findViewById(R.id.block_prompt);
315 bindName();
Julia Reynolds268647a2018-10-25 16:54:27 -0400316 bindGroup();
Daniel Sandlerd0a52b62018-10-31 20:13:22 +0000317 if (mIsNonblockable) {
318 blockPrompt.setText(R.string.notification_unblockable_desc);
319 } else {
320 if (mNegativeUserSentiment) {
321 blockPrompt.setText(R.string.inline_blocking_helper);
322 } else if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
323 blockPrompt.setText(R.string.inline_keep_showing_app);
324 } else {
325 blockPrompt.setText(R.string.inline_keep_showing);
326 }
327 }
328 }
329
330 private void bindName() {
331 final TextView channelName = findViewById(R.id.channel_name);
332 if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
333 channelName.setVisibility(View.GONE);
334 } else {
335 channelName.setText(mSingleNotificationChannel.getName());
336 }
Julia Reynoldsac98aea2018-10-25 16:54:27 -0400337 }
338
Julia Reynolds268647a2018-10-25 16:54:27 -0400339 private void bindDelegate() {
340 TextView delegateView = findViewById(R.id.delegate_name);
341 TextView dividerView = findViewById(R.id.pkg_divider);
342
343 CharSequence delegatePkg = null;
344 if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
345 // this notification was posted by a delegate!
346 ApplicationInfo info;
347 try {
348 info = mPm.getApplicationInfo(
349 mDelegatePkg,
350 PackageManager.MATCH_UNINSTALLED_PACKAGES
351 | PackageManager.MATCH_DISABLED_COMPONENTS
352 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
353 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
354 if (info != null) {
355 delegatePkg = String.valueOf(mPm.getApplicationLabel(info));
356 }
357 } catch (PackageManager.NameNotFoundException e) { }
358 }
359 if (delegatePkg != null) {
360 delegateView.setText(mContext.getResources().getString(
361 R.string.notification_delegate_header, delegatePkg));
362 delegateView.setVisibility(View.VISIBLE);
363 dividerView.setVisibility(View.VISIBLE);
364 } else {
365 delegateView.setVisibility(View.GONE);
366 dividerView.setVisibility(View.GONE);
367 }
368 }
369
370 private void bindGroup() throws RemoteException {
371 // Set group information if this channel has an associated group.
372 CharSequence groupName = null;
373 if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
374 final NotificationChannelGroup notificationChannelGroup =
375 mINotificationManager.getNotificationChannelGroupForPackage(
376 mSingleNotificationChannel.getGroup(), mPackageName, mAppUid);
377 if (notificationChannelGroup != null) {
378 groupName = notificationChannelGroup.getName();
379 }
380 }
381 TextView groupNameView = findViewById(R.id.group_name);
382 TextView groupDividerView = findViewById(R.id.pkg_group_divider);
383 if (groupName != null) {
384 groupNameView.setText(groupName);
385 groupNameView.setVisibility(View.VISIBLE);
386 groupDividerView.setVisibility(View.VISIBLE);
387 } else {
388 groupNameView.setVisibility(View.GONE);
389 groupDividerView.setVisibility(View.GONE);
390 }
391 }
392
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700393 @VisibleForTesting
394 void logBlockingHelperCounter(String counterTag) {
395 if (mIsForBlockingHelper) {
396 mMetricsLogger.count(counterTag, 1);
397 }
398 }
399
William Brockman75cf66a2018-12-21 13:25:01 -0500400 /**
401 * Returns an initialized LogMaker for logging importance changes.
402 * The caller may override the type (to DISMISS) before passing it to mMetricsLogger.
403 * @return new LogMaker
404 */
405 private LogMaker importanceChangeLogMaker() {
Gus Prevascaed15c2019-01-18 14:19:51 -0500406 Integer chosenImportance =
407 mChosenImportance != null ? mChosenImportance : mStartingChannelImportance;
William Brockman75cf66a2018-12-21 13:25:01 -0500408 return new LogMaker(MetricsEvent.ACTION_SAVE_IMPORTANCE)
409 .setType(MetricsEvent.TYPE_ACTION)
Gus Prevascaed15c2019-01-18 14:19:51 -0500410 .setSubtype(chosenImportance - mStartingChannelImportance);
William Brockman75cf66a2018-12-21 13:25:01 -0500411 }
412
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400413 private boolean hasImportanceChanged() {
Gus Prevas9abc5062018-10-31 16:11:04 -0400414 return mSingleNotificationChannel != null
Gus Prevascaed15c2019-01-18 14:19:51 -0500415 && mChosenImportance != null
416 && (mStartingChannelImportance != mChosenImportance
417 || (mWasShownHighPriority && mChosenImportance < IMPORTANCE_DEFAULT)
418 || (!mWasShownHighPriority && mChosenImportance >= IMPORTANCE_DEFAULT));
Mady Mellor87d79452017-01-10 11:52:52 -0800419 }
420
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500421 private void saveImportance() {
Gus Prevas894d9152018-11-12 13:51:40 -0500422 if (!mIsNonblockable
423 || mExitReason != NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS) {
Gus Prevascaed15c2019-01-18 14:19:51 -0500424 if (mChosenImportance == null) {
425 mChosenImportance = mStartingChannelImportance;
426 }
Gus Prevas533836a2018-09-24 17:15:32 -0400427 updateImportance();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500428 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700429 }
430
431 /**
432 * Commits the updated importance values on the background thread.
433 */
434 private void updateImportance() {
Gus Prevascaed15c2019-01-18 14:19:51 -0500435 if (mChosenImportance != null) {
436 mMetricsLogger.write(importanceChangeLogMaker());
Rohan Shahca0447e2018-03-30 15:18:27 -0700437
Gus Prevascaed15c2019-01-18 14:19:51 -0500438 Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
439 bgHandler.post(
440 new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
441 mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
442 mStartingChannelImportance, mChosenImportance));
443 }
Mady Mellor87d79452017-01-10 11:52:52 -0800444 }
445
Julia Reynolds437cdb12018-01-03 12:27:24 -0500446 private void bindButtons() {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700447 findViewById(R.id.undo).setOnClickListener(mOnUndo);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500448
Gus Prevas82ad0592018-11-28 14:27:40 -0500449 boolean showInterruptivenessSettings =
450 !mIsNonblockable
451 && !mIsForeground
452 && !mIsForBlockingHelper
453 && NotificationUtils.useNewInterruptionModel(mContext);
454 if (showInterruptivenessSettings) {
455 findViewById(R.id.block_or_minimize).setVisibility(GONE);
456 findViewById(R.id.interruptiveness_settings).setVisibility(VISIBLE);
457 View block = findViewById(R.id.int_block);
458 TextView silent = findViewById(R.id.int_silent);
459 TextView alert = findViewById(R.id.int_alert);
460
Gus Prevas82ad0592018-11-28 14:27:40 -0500461 block.setOnClickListener(mOnStopOrMinimizeNotifications);
Gus Prevascaed15c2019-01-18 14:19:51 -0500462 if (mWasShownHighPriority) {
Gus Prevas82ad0592018-11-28 14:27:40 -0500463 silent.setOnClickListener(mOnToggleSilent);
464 silent.setText(R.string.inline_silent_button_silent);
465 alert.setOnClickListener(mOnKeepShowing);
466 alert.setText(R.string.inline_silent_button_keep_alerting);
467 } else {
468 silent.setOnClickListener(mOnKeepShowing);
469 silent.setText(R.string.inline_silent_button_stay_silent);
470 alert.setOnClickListener(mOnToggleSilent);
471 alert.setText(R.string.inline_silent_button_alert);
472 }
Mady Mellor87d79452017-01-10 11:52:52 -0800473 } else {
Gus Prevas82ad0592018-11-28 14:27:40 -0500474 findViewById(R.id.block_or_minimize).setVisibility(VISIBLE);
475 findViewById(R.id.interruptiveness_settings).setVisibility(GONE);
476 View block = findViewById(R.id.block);
477 TextView keep = findViewById(R.id.keep);
478 View minimize = findViewById(R.id.minimize);
479
480 block.setOnClickListener(mOnStopOrMinimizeNotifications);
481 keep.setOnClickListener(mOnKeepShowing);
482 minimize.setOnClickListener(mOnStopOrMinimizeNotifications);
483
484 if (mIsNonblockable) {
485 keep.setText(android.R.string.ok);
486 block.setVisibility(GONE);
487 minimize.setVisibility(GONE);
488 } else if (mIsForeground) {
489 block.setVisibility(GONE);
490 minimize.setVisibility(VISIBLE);
491 } else {
492 block.setVisibility(VISIBLE);
493 minimize.setVisibility(GONE);
494 }
495
496 // Set up app settings link (i.e. Customize)
497 TextView settingsLinkView = findViewById(R.id.app_settings);
498 Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName,
499 mSingleNotificationChannel,
500 mSbn.getId(), mSbn.getTag());
501 if (!mIsForBlockingHelper
502 && settingsIntent != null
503 && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
504 settingsLinkView.setVisibility(VISIBLE);
505 settingsLinkView.setText(mContext.getString(R.string.notification_app_settings));
506 settingsLinkView.setOnClickListener((View view) -> {
507 mAppSettingsClickListener.onClick(view, settingsIntent);
508 });
509 } else {
510 settingsLinkView.setVisibility(View.GONE);
511 }
Mady Mellor87d79452017-01-10 11:52:52 -0800512 }
513 }
514
Gus Prevas894d9152018-11-12 13:51:40 -0500515 private void swapContent(@NotificationInfoAction int action, boolean animate) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500516 if (mExpandAnimation != null) {
517 mExpandAnimation.cancel();
518 }
Mady Mellor87d79452017-01-10 11:52:52 -0800519
Julia Reynoldse0341482018-03-08 14:42:50 -0500520 View prompt = findViewById(R.id.prompt);
521 ViewGroup confirmation = findViewById(R.id.confirmation);
522 TextView confirmationText = findViewById(R.id.confirmation_text);
523 View header = findViewById(R.id.header);
524
Gus Prevas9abc5062018-10-31 16:11:04 -0400525 switch (action) {
Gus Prevas894d9152018-11-12 13:51:40 -0500526 case ACTION_UNDO:
Gus Prevas9abc5062018-10-31 16:11:04 -0400527 mChosenImportance = mStartingChannelImportance;
528 break;
Gus Prevas894d9152018-11-12 13:51:40 -0500529 case ACTION_TOGGLE_SILENT:
530 mExitReason = NotificationCounters.BLOCKING_HELPER_TOGGLE_SILENT;
Gus Prevascaed15c2019-01-18 14:19:51 -0500531 if (mWasShownHighPriority) {
Gus Prevas9abc5062018-10-31 16:11:04 -0400532 mChosenImportance = IMPORTANCE_LOW;
533 confirmationText.setText(R.string.notification_channel_silenced);
534 } else {
535 mChosenImportance = IMPORTANCE_HIGH;
536 confirmationText.setText(R.string.notification_channel_unsilenced);
537 }
538 break;
Gus Prevas894d9152018-11-12 13:51:40 -0500539 case ACTION_BLOCK:
540 mExitReason = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS;
Gus Prevas9abc5062018-10-31 16:11:04 -0400541 if (mIsForeground) {
542 mChosenImportance = IMPORTANCE_MIN;
543 confirmationText.setText(R.string.notification_channel_minimized);
544 } else {
545 mChosenImportance = IMPORTANCE_NONE;
546 confirmationText.setText(R.string.notification_channel_disabled);
547 }
548 break;
549 default:
550 throw new IllegalArgumentException();
Julia Reynolds437cdb12018-01-03 12:27:24 -0500551 }
552
Gus Prevas894d9152018-11-12 13:51:40 -0500553 boolean isUndo = action == ACTION_UNDO;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500554
Gus Prevas9abc5062018-10-31 16:11:04 -0400555 prompt.setVisibility(isUndo ? VISIBLE : GONE);
556 confirmation.setVisibility(isUndo ? GONE : VISIBLE);
557 header.setVisibility(isUndo ? VISIBLE : GONE);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500558
Gus Prevas894d9152018-11-12 13:51:40 -0500559 if (animate) {
560 ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA,
561 prompt.getAlpha(), isUndo ? 1f : 0f);
562 promptAnim.setInterpolator(isUndo ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
563 ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA,
564 confirmation.getAlpha(), isUndo ? 0f : 1f);
565 confirmAnim.setInterpolator(isUndo ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500566
Gus Prevas894d9152018-11-12 13:51:40 -0500567 mExpandAnimation = new AnimatorSet();
568 mExpandAnimation.playTogether(promptAnim, confirmAnim);
569 mExpandAnimation.setDuration(150);
570 mExpandAnimation.addListener(new AnimatorListenerAdapter() {
571 boolean mCancelled = false;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500572
Gus Prevas894d9152018-11-12 13:51:40 -0500573 @Override
574 public void onAnimationCancel(Animator animation) {
575 mCancelled = true;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500576 }
Gus Prevas894d9152018-11-12 13:51:40 -0500577
578 @Override
579 public void onAnimationEnd(Animator animation) {
580 if (!mCancelled) {
581 prompt.setVisibility(isUndo ? VISIBLE : GONE);
582 confirmation.setVisibility(isUndo ? GONE : VISIBLE);
583 }
584 }
585 });
586 mExpandAnimation.start();
587 }
Rohan Shah142e2da2018-06-14 13:14:18 -0700588
589 // Since we're swapping/update the content, reset the timeout so the UI can't close
590 // immediately after the update.
591 if (mGutsContainer != null) {
592 mGutsContainer.resetFalsingCheck();
593 }
Mady Mellor87d79452017-01-10 11:52:52 -0800594 }
595
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400596 @Override
Gus Prevas9abc5062018-10-31 16:11:04 -0400597 public void onFinishedClosing() {
Gus Prevascaed15c2019-01-18 14:19:51 -0500598 if (mChosenImportance != null) {
599 mStartingChannelImportance = mChosenImportance;
Gus Prevas9abc5062018-10-31 16:11:04 -0400600 }
601 mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
602
603 View prompt = findViewById(R.id.prompt);
604 ViewGroup confirmation = findViewById(R.id.confirmation);
605 View header = findViewById(R.id.header);
606 prompt.setVisibility(VISIBLE);
607 prompt.setAlpha(1f);
608 confirmation.setVisibility(GONE);
609 confirmation.setAlpha(1f);
610 header.setVisibility(VISIBLE);
611 header.setAlpha(1f);
Eyal Posenera9cf9c72018-12-18 16:23:54 +0200612 mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
613 .setSubtype(MetricsEvent.BLOCKING_HELPER_DISMISS));
Gus Prevas9abc5062018-10-31 16:11:04 -0400614 }
615
616 @Override
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400617 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
618 super.onInitializeAccessibilityEvent(event);
619 if (mGutsContainer != null &&
620 event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
621 if (mGutsContainer.isExposed()) {
622 event.getText().add(mContext.getString(
623 R.string.notification_channel_controls_opened_accessibility, mAppName));
624 } else {
625 event.getText().add(mContext.getString(
626 R.string.notification_channel_controls_closed_accessibility, mAppName));
627 }
628 }
629 }
630
Julia Reynolds3aedded2017-03-31 14:42:09 -0400631 private Intent getAppSettingsIntent(PackageManager pm, String packageName,
632 NotificationChannel channel, int id, String tag) {
633 Intent intent = new Intent(Intent.ACTION_MAIN)
634 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
635 .setPackage(packageName);
636 final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
637 intent,
638 PackageManager.MATCH_DEFAULT_ONLY
639 );
640 if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
641 return null;
642 }
643 final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
644 intent.setClassName(activityInfo.packageName, activityInfo.name);
645 if (channel != null) {
646 intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
647 }
648 intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
649 intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
650 return intent;
651 }
652
Rohan Shahca0447e2018-03-30 15:18:27 -0700653 /**
654 * Closes the controls and commits the updated importance values (indirectly). If this view is
655 * being used to show the blocking helper, this will immediately dismiss the blocking helper and
656 * commit the updated importance.
657 *
658 * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
659 * user does not have the ability to undo the action anymore. See {@link #swapContent(boolean)}
660 * for where undo is handled.
661 */
Rohan Shah524cf7b2018-03-15 14:40:02 -0700662 @VisibleForTesting
663 void closeControls(View v) {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700664 int[] parentLoc = new int[2];
665 int[] targetLoc = new int[2];
666 mGutsContainer.getLocationOnScreen(parentLoc);
667 v.getLocationOnScreen(targetLoc);
668 final int centerX = v.getWidth() / 2;
669 final int centerY = v.getHeight() / 2;
670 final int x = targetLoc[0] - parentLoc[0] + centerX;
671 final int y = targetLoc[1] - parentLoc[1] + centerY;
672 mGutsContainer.closeControls(x, y, true /* save */, false /* force */);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500673 }
674
Mady Mellor87d79452017-01-10 11:52:52 -0800675 @Override
Mady Mellor95d743c2017-01-10 12:05:27 -0800676 public void setGutsParent(NotificationGuts guts) {
677 mGutsContainer = guts;
Mady Mellor87d79452017-01-10 11:52:52 -0800678 }
679
680 @Override
Mady Mellor434180c2017-02-13 11:29:42 -0800681 public boolean willBeRemoved() {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500682 return hasImportanceChanged();
Mady Mellor434180c2017-02-13 11:29:42 -0800683 }
684
685 @Override
Lucas Dupin9b08c012018-05-16 19:53:32 -0700686 public boolean shouldBeSaved() {
687 return hasImportanceChanged();
688 }
689
690 @Override
Mady Mellor87d79452017-01-10 11:52:52 -0800691 public View getContentView() {
692 return this;
693 }
694
695 @Override
Mady Mellorc2dbe492017-03-30 13:22:03 -0700696 public boolean handleCloseControls(boolean save, boolean force) {
Julia Reynoldsc65656a2018-02-12 09:55:14 -0500697 // Save regardless of the importance so we can lock the importance field if the user wants
698 // to keep getting notifications
699 if (save) {
Rohan Shahca0447e2018-03-30 15:18:27 -0700700 saveImportance();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500701 }
Rohan Shahdd588c72018-05-09 20:32:15 -0700702 logBlockingHelperCounter(mExitReason);
Mady Mellor87d79452017-01-10 11:52:52 -0800703 return false;
704 }
Mady Mellore09fb702017-03-30 13:23:29 -0700705
706 @Override
707 public int getActualHeight() {
708 return getHeight();
709 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700710
Gus Prevas533836a2018-09-24 17:15:32 -0400711 @VisibleForTesting
712 public boolean isAnimating() {
713 return mExpandAnimation != null && mExpandAnimation.isRunning();
714 }
715
Rohan Shahca0447e2018-03-30 15:18:27 -0700716 /**
717 * Runnable to either update the given channel (with a new importance value) or, if no channel
718 * is provided, update notifications enabled state for the package.
719 */
720 private static class UpdateImportanceRunnable implements Runnable {
721 private final INotificationManager mINotificationManager;
722 private final String mPackageName;
723 private final int mAppUid;
724 private final @Nullable NotificationChannel mChannelToUpdate;
725 private final int mCurrentImportance;
726 private final int mNewImportance;
727
728
729 public UpdateImportanceRunnable(INotificationManager notificationManager,
730 String packageName, int appUid, @Nullable NotificationChannel channelToUpdate,
731 int currentImportance, int newImportance) {
732 mINotificationManager = notificationManager;
733 mPackageName = packageName;
734 mAppUid = appUid;
735 mChannelToUpdate = channelToUpdate;
736 mCurrentImportance = currentImportance;
737 mNewImportance = newImportance;
738 }
739
740 @Override
741 public void run() {
742 try {
743 if (mChannelToUpdate != null) {
744 mChannelToUpdate.setImportance(mNewImportance);
745 mChannelToUpdate.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
746 mINotificationManager.updateNotificationChannelForPackage(
747 mPackageName, mAppUid, mChannelToUpdate);
748 } else {
749 // For notifications with more than one channel, update notification enabled
750 // state. If the importance was lowered, we disable notifications.
Rohan Shah590e1b22018-04-10 23:48:47 -0400751 mINotificationManager.setNotificationsEnabledWithImportanceLockForPackage(
Rohan Shahca0447e2018-03-30 15:18:27 -0700752 mPackageName, mAppUid, mNewImportance >= mCurrentImportance);
753 }
754 } catch (RemoteException e) {
755 Log.e(TAG, "Unable to update notification importance", e);
756 }
757 }
758 }
Eyal Posenera9cf9c72018-12-18 16:23:54 +0200759
760 private LogMaker getLogMaker() {
761 return mSbn.getLogMaker().setCategory(MetricsEvent.NOTIFICATION_ITEM);
762 }
Mady Mellor87d79452017-01-10 11:52:52 -0800763}