blob: 3a7091bb843ac2ad9eeba82e63eab0257481805b [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;
Gus Prevas9abc5062018-10-31 16:11:04 -040024import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
Julia Reynolds3aedded2017-03-31 14:42:09 -040025
Julia Reynolds437cdb12018-01-03 12:27:24 -050026import android.animation.Animator;
27import android.animation.AnimatorListenerAdapter;
28import android.animation.AnimatorSet;
29import android.animation.ObjectAnimator;
Gus Prevas9abc5062018-10-31 16:11:04 -040030import android.annotation.IntDef;
Rohan Shahca0447e2018-03-30 15:18:27 -070031import android.annotation.Nullable;
Mady Mellor87d79452017-01-10 11:52:52 -080032import android.app.INotificationManager;
Julia Reynolds3aedded2017-03-31 14:42:09 -040033import android.app.Notification;
Mady Mellor87d79452017-01-10 11:52:52 -080034import android.app.NotificationChannel;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -050035import android.app.NotificationChannelGroup;
Mady Mellor87d79452017-01-10 11:52:52 -080036import android.content.Context;
Julia Reynolds3aedded2017-03-31 14:42:09 -040037import android.content.Intent;
38import android.content.pm.ActivityInfo;
Mady Mellor87d79452017-01-10 11:52:52 -080039import android.content.pm.ApplicationInfo;
Mady Mellor87d79452017-01-10 11:52:52 -080040import android.content.pm.PackageManager;
Julia Reynolds3aedded2017-03-31 14:42:09 -040041import android.content.pm.ResolveInfo;
Mady Mellor87d79452017-01-10 11:52:52 -080042import android.graphics.drawable.Drawable;
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;
Rohan Shahca0447e2018-03-30 15:18:27 -070095 private int mNumUniqueChannelsInRow;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050096 private NotificationChannel mSingleNotificationChannel;
Gus Prevas9abc5062018-10-31 16:11:04 -040097 private int mStartingChannelImportance;
98 private int mStartingChannelOrNotificationImportance;
Julia Reynolds437cdb12018-01-03 12:27:24 -050099 private int mChosenImportance;
100 private boolean mIsSingleDefaultChannel;
Rohan Shah63411fc2018-03-28 19:05:52 -0700101 private boolean mIsNonblockable;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500102 private StatusBarNotification mSbn;
103 private AnimatorSet mExpandAnimation;
Julia Reynoldse0341482018-03-08 14:42:50 -0500104 private boolean mIsForeground;
Julia Reynolds35765d82018-08-17 11:39:19 -0400105 private boolean mIsDeviceProvisioned;
Gus Prevas9abc5062018-10-31 16:11:04 -0400106 private boolean mIsNoisy;
Mady Mellor87d79452017-01-10 11:52:52 -0800107
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400108 private CheckSaveListener mCheckSaveListener;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500109 private OnSettingsClickListener mOnSettingsClickListener;
Julia Reynolds3aedded2017-03-31 14:42:09 -0400110 private OnAppSettingsClickListener mAppSettingsClickListener;
Mady Mellor95d743c2017-01-10 12:05:27 -0800111 private NotificationGuts mGutsContainer;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700112
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700113 /** Whether this view is being shown as part of the blocking helper. */
Rohan Shah524cf7b2018-03-15 14:40:02 -0700114 private boolean mIsForBlockingHelper;
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500115 private boolean mNegativeUserSentiment;
Mady Mellor87d79452017-01-10 11:52:52 -0800116
Rohan Shahdd588c72018-05-09 20:32:15 -0700117 /**
118 * String that describes how the user exit or quit out of this view, also used as a counter tag.
119 */
120 private String mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700121
122 private OnClickListener mOnKeepShowing = v -> {
Rohan Shahdd588c72018-05-09 20:32:15 -0700123 mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700124 closeControls(v);
125 };
Julia Reynolds437cdb12018-01-03 12:27:24 -0500126
Gus Prevas9abc5062018-10-31 16:11:04 -0400127 private OnClickListener mOnToggleSilent = v -> {
128 Runnable saveImportance = () -> {
Gus Prevas894d9152018-11-12 13:51:40 -0500129 swapContent(ACTION_TOGGLE_SILENT, true /* animate */);
Gus Prevas9abc5062018-10-31 16:11:04 -0400130 };
131 if (mCheckSaveListener != null) {
132 mCheckSaveListener.checkSave(saveImportance, mSbn);
133 } else {
134 saveImportance.run();
135 }
136 };
137
Rohan Shahca0447e2018-03-30 15:18:27 -0700138 private OnClickListener mOnStopOrMinimizeNotifications = v -> {
Gus Prevas533836a2018-09-24 17:15:32 -0400139 Runnable saveImportance = () -> {
Gus Prevas894d9152018-11-12 13:51:40 -0500140 swapContent(ACTION_BLOCK, true /* animate */);
Gus Prevas533836a2018-09-24 17:15:32 -0400141 };
142 if (mCheckSaveListener != null) {
143 mCheckSaveListener.checkSave(saveImportance, mSbn);
144 } else {
145 saveImportance.run();
146 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500147 };
148
149 private OnClickListener mOnUndo = v -> {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700150 // Reset exit counter that we'll log and record an undo event separately (not an exit event)
Rohan Shahdd588c72018-05-09 20:32:15 -0700151 mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700152 logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
Gus Prevas894d9152018-11-12 13:51:40 -0500153 swapContent(ACTION_UNDO, true /* animate */);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500154 };
155
Mady Mellor87d79452017-01-10 11:52:52 -0800156 public NotificationInfo(Context context, AttributeSet attrs) {
157 super(context, attrs);
158 }
159
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400160 // Specify a CheckSaveListener to override when/if the user's changes are committed.
161 public interface CheckSaveListener {
162 // Invoked when importance has changed and the NotificationInfo wants to try to save it.
163 // Listener should run saveImportance unless the change should be canceled.
Eliot Courtney47098cb2017-10-18 17:30:30 +0900164 void checkSave(Runnable saveImportance, StatusBarNotification sbn);
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400165 }
166
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500167 public interface OnSettingsClickListener {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500168 void onClick(View v, NotificationChannel channel, int appUid);
Mady Mellor87d79452017-01-10 11:52:52 -0800169 }
170
Julia Reynolds3aedded2017-03-31 14:42:09 -0400171 public interface OnAppSettingsClickListener {
172 void onClick(View v, Intent intent);
173 }
174
Rohan Shah524cf7b2018-03-15 14:40:02 -0700175 @VisibleForTesting
176 void bindNotification(
177 final PackageManager pm,
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500178 final INotificationManager iNotificationManager,
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500179 final String pkg,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500180 final NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700181 final int numUniqueChannelsInRow,
Julia Reynolds3aedded2017-03-31 14:42:09 -0400182 final StatusBarNotification sbn,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500183 final CheckSaveListener checkSaveListener,
184 final OnSettingsClickListener onSettingsClick,
185 final OnAppSettingsClickListener onAppSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400186 boolean isDeviceProvisioned,
Gus Prevas9abc5062018-10-31 16:11:04 -0400187 boolean isNonblockable,
188 boolean isNoisy,
Gus Prevas894d9152018-11-12 13:51:40 -0500189 int importance,
190 @NotificationInfoAction int action)
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500191 throws RemoteException {
Rohan Shahca0447e2018-03-30 15:18:27 -0700192 bindNotification(pm, iNotificationManager, pkg, notificationChannel,
193 numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400194 onAppSettingsClick, isDeviceProvisioned, isNonblockable,
Gus Prevas9abc5062018-10-31 16:11:04 -0400195 false /* isBlockingHelper */, false /* isUserSentimentNegative */, isNoisy,
Gus Prevas894d9152018-11-12 13:51:40 -0500196 importance, action);
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500197 }
198
Rohan Shah524cf7b2018-03-15 14:40:02 -0700199 public void bindNotification(
200 PackageManager pm,
201 INotificationManager iNotificationManager,
202 String pkg,
203 NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700204 int numUniqueChannelsInRow,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700205 StatusBarNotification sbn,
206 CheckSaveListener checkSaveListener,
207 OnSettingsClickListener onSettingsClick,
208 OnAppSettingsClickListener onAppSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400209 boolean isDeviceProvisioned,
Rohan Shah63411fc2018-03-28 19:05:52 -0700210 boolean isNonblockable,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700211 boolean isForBlockingHelper,
Gus Prevas9abc5062018-10-31 16:11:04 -0400212 boolean isUserSentimentNegative,
213 boolean isNoisy,
Gus Prevas894d9152018-11-12 13:51:40 -0500214 int importance,
215 @NotificationInfoAction int action)
Rohan Shah524cf7b2018-03-15 14:40:02 -0700216 throws RemoteException {
Mady Mellor87d79452017-01-10 11:52:52 -0800217 mINotificationManager = iNotificationManager;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700218 mMetricsLogger = Dependency.get(MetricsLogger.class);
Rohan Shahca0447e2018-03-30 15:18:27 -0700219 mPackageName = pkg;
220 mNumUniqueChannelsInRow = numUniqueChannelsInRow;
Julia Reynolds3aedded2017-03-31 14:42:09 -0400221 mSbn = sbn;
222 mPm = pm;
223 mAppSettingsClickListener = onAppSettingsClick;
Rohan Shahca0447e2018-03-30 15:18:27 -0700224 mAppName = mPackageName;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500225 mCheckSaveListener = checkSaveListener;
226 mOnSettingsClickListener = onSettingsClick;
227 mSingleNotificationChannel = notificationChannel;
Gus Prevas9abc5062018-10-31 16:11:04 -0400228 int channelImportance = mSingleNotificationChannel.getImportance();
229 mStartingChannelImportance = mChosenImportance = channelImportance;
230 mStartingChannelOrNotificationImportance =
231 channelImportance == IMPORTANCE_UNSPECIFIED ? importance : channelImportance;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700232 mNegativeUserSentiment = isUserSentimentNegative;
Rohan Shah63411fc2018-03-28 19:05:52 -0700233 mIsNonblockable = isNonblockable;
Julia Reynoldse0341482018-03-08 14:42:50 -0500234 mIsForeground =
235 (mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700236 mIsForBlockingHelper = isForBlockingHelper;
Rohan Shahdbd64e72018-03-28 14:46:50 -0700237 mAppUid = mSbn.getUid();
Julia Reynolds35765d82018-08-17 11:39:19 -0400238 mIsDeviceProvisioned = isDeviceProvisioned;
Gus Prevas9abc5062018-10-31 16:11:04 -0400239 mIsNoisy = isNoisy;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500240
Julia Reynolds437cdb12018-01-03 12:27:24 -0500241 int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400242 pkg, mAppUid, false /* includeDeleted */);
Rohan Shahca0447e2018-03-30 15:18:27 -0700243 if (mNumUniqueChannelsInRow == 0) {
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400244 throw new IllegalArgumentException("bindNotification requires at least one channel");
245 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500246 // Special behavior for the Default channel if no other channels have been defined.
Rohan Shahca0447e2018-03-30 15:18:27 -0700247 mIsSingleDefaultChannel = mNumUniqueChannelsInRow == 1
Rohan Shahdbd64e72018-03-28 14:46:50 -0700248 && mSingleNotificationChannel.getId().equals(
249 NotificationChannel.DEFAULT_CHANNEL_ID)
250 && numTotalChannels == 1;
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400251 }
252
Julia Reynolds437cdb12018-01-03 12:27:24 -0500253 bindHeader();
254 bindPrompt();
255 bindButtons();
Gus Prevas894d9152018-11-12 13:51:40 -0500256
257 if (action != ACTION_NONE) {
258 swapContent(action, false /* don't animate */);
259 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500260 }
261
262 private void bindHeader() throws RemoteException {
263 // Package name
264 Drawable pkgicon = null;
265 ApplicationInfo info;
266 try {
Rohan Shahca0447e2018-03-30 15:18:27 -0700267 info = mPm.getApplicationInfo(
268 mPackageName,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500269 PackageManager.MATCH_UNINSTALLED_PACKAGES
270 | PackageManager.MATCH_DISABLED_COMPONENTS
271 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
272 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
273 if (info != null) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500274 mAppName = String.valueOf(mPm.getApplicationLabel(info));
275 pkgicon = mPm.getApplicationIcon(info);
Geoffrey Pitschee8c81e2017-03-23 11:38:56 -0400276 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500277 } catch (PackageManager.NameNotFoundException e) {
278 // app is gone, just show package name and generic icon
279 pkgicon = mPm.getDefaultActivityIcon();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500280 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500281 ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400282 ((TextView) findViewById(R.id.pkgname)).setText(mAppName);
Mady Mellor87d79452017-01-10 11:52:52 -0800283
Julia Reynoldsac98aea2018-10-25 16:54:27 -0400284 // Set group information if this channel has an associated group.
285 CharSequence groupName = null;
286 if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
287 final NotificationChannelGroup notificationChannelGroup =
288 mINotificationManager.getNotificationChannelGroupForPackage(
289 mSingleNotificationChannel.getGroup(), mPackageName, mAppUid);
290 if (notificationChannelGroup != null) {
291 groupName = notificationChannelGroup.getName();
292 }
293 }
294 TextView groupNameView = findViewById(R.id.group_name);
295 TextView groupDividerView = findViewById(R.id.pkg_group_divider);
296 if (groupName != null) {
297 groupNameView.setText(groupName);
298 groupNameView.setVisibility(View.VISIBLE);
299 groupDividerView.setVisibility(View.VISIBLE);
300 } else {
301 groupNameView.setVisibility(View.GONE);
302 groupDividerView.setVisibility(View.GONE);
303 }
Daniel Sandlerd0a52b62018-10-31 20:13:22 +0000304
305 // Settings button.
306 final View settingsButton = findViewById(R.id.info);
307 if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
308 settingsButton.setVisibility(View.VISIBLE);
309 final int appUidF = mAppUid;
310 settingsButton.setOnClickListener(
311 (View view) -> {
312 logBlockingHelperCounter(
313 NotificationCounters.BLOCKING_HELPER_NOTIF_SETTINGS);
314 mOnSettingsClickListener.onClick(view,
315 mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel,
316 appUidF);
317 });
318 } else {
319 settingsButton.setVisibility(View.GONE);
320 }
321 }
322
323 private void bindPrompt() {
324 final TextView blockPrompt = findViewById(R.id.block_prompt);
325 bindName();
326 if (mIsNonblockable) {
327 blockPrompt.setText(R.string.notification_unblockable_desc);
328 } else {
329 if (mNegativeUserSentiment) {
330 blockPrompt.setText(R.string.inline_blocking_helper);
331 } else if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
332 blockPrompt.setText(R.string.inline_keep_showing_app);
333 } else {
334 blockPrompt.setText(R.string.inline_keep_showing);
335 }
336 }
337 }
338
339 private void bindName() {
340 final TextView channelName = findViewById(R.id.channel_name);
341 if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
342 channelName.setVisibility(View.GONE);
343 } else {
344 channelName.setText(mSingleNotificationChannel.getName());
345 }
Julia Reynoldsac98aea2018-10-25 16:54:27 -0400346 }
347
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700348 @VisibleForTesting
349 void logBlockingHelperCounter(String counterTag) {
350 if (mIsForBlockingHelper) {
351 mMetricsLogger.count(counterTag, 1);
352 }
353 }
354
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400355 private boolean hasImportanceChanged() {
Gus Prevas9abc5062018-10-31 16:11:04 -0400356 return mSingleNotificationChannel != null
357 && mStartingChannelImportance != mChosenImportance;
Mady Mellor87d79452017-01-10 11:52:52 -0800358 }
359
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500360 private void saveImportance() {
Gus Prevas894d9152018-11-12 13:51:40 -0500361 if (!mIsNonblockable
362 || mExitReason != NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS) {
Gus Prevas533836a2018-09-24 17:15:32 -0400363 updateImportance();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500364 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700365 }
366
367 /**
368 * Commits the updated importance values on the background thread.
369 */
370 private void updateImportance() {
Mady Mellor87d79452017-01-10 11:52:52 -0800371 MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
Gus Prevas9abc5062018-10-31 16:11:04 -0400372 mChosenImportance - mStartingChannelImportance);
Rohan Shahca0447e2018-03-30 15:18:27 -0700373
374 Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
375 bgHandler.post(new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
376 mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
Gus Prevas9abc5062018-10-31 16:11:04 -0400377 mStartingChannelImportance, mChosenImportance));
Mady Mellor87d79452017-01-10 11:52:52 -0800378 }
379
Julia Reynolds437cdb12018-01-03 12:27:24 -0500380 private void bindButtons() {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700381 // Set up stay-in-notification actions
Julia Reynolds437cdb12018-01-03 12:27:24 -0500382 View block = findViewById(R.id.block);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500383 TextView keep = findViewById(R.id.keep);
Gus Prevas9abc5062018-10-31 16:11:04 -0400384 TextView silent = findViewById(R.id.toggle_silent);
Julia Reynoldse0341482018-03-08 14:42:50 -0500385 View minimize = findViewById(R.id.minimize);
Rohan Shah524cf7b2018-03-15 14:40:02 -0700386
387 findViewById(R.id.undo).setOnClickListener(mOnUndo);
Rohan Shahca0447e2018-03-30 15:18:27 -0700388 block.setOnClickListener(mOnStopOrMinimizeNotifications);
Rohan Shah524cf7b2018-03-15 14:40:02 -0700389 keep.setOnClickListener(mOnKeepShowing);
Gus Prevas9abc5062018-10-31 16:11:04 -0400390 silent.setOnClickListener(mOnToggleSilent);
Rohan Shahca0447e2018-03-30 15:18:27 -0700391 minimize.setOnClickListener(mOnStopOrMinimizeNotifications);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500392
Rohan Shah63411fc2018-03-28 19:05:52 -0700393 if (mIsNonblockable) {
Rohan Shah53529aa2018-04-12 00:14:50 -0400394 keep.setText(android.R.string.ok);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500395 block.setVisibility(GONE);
Gus Prevas9abc5062018-10-31 16:11:04 -0400396 silent.setVisibility(GONE);
Julia Reynoldse0341482018-03-08 14:42:50 -0500397 minimize.setVisibility(GONE);
398 } else if (mIsForeground) {
399 block.setVisibility(GONE);
Gus Prevas9abc5062018-10-31 16:11:04 -0400400 silent.setVisibility(GONE);
Julia Reynoldse0341482018-03-08 14:42:50 -0500401 minimize.setVisibility(VISIBLE);
Gus Prevas9abc5062018-10-31 16:11:04 -0400402 } else {
Julia Reynoldse0341482018-03-08 14:42:50 -0500403 block.setVisibility(VISIBLE);
Gus Prevas9abc5062018-10-31 16:11:04 -0400404 boolean showToggleSilent = mIsNoisy
405 && NotificationUtils.useNewInterruptionModel(mContext);
406 silent.setVisibility(showToggleSilent ? VISIBLE : GONE);
407 boolean isCurrentlyAlerting =
408 mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT;
409 silent.setText(isCurrentlyAlerting
410 ? R.string.inline_silent_button_silent
411 : R.string.inline_silent_button_alert);
Julia Reynoldse0341482018-03-08 14:42:50 -0500412 minimize.setVisibility(GONE);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500413 }
414
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000415 // Set up app settings link (i.e. Customize)
Julia Reynolds437cdb12018-01-03 12:27:24 -0500416 TextView settingsLinkView = findViewById(R.id.app_settings);
Rohan Shahca0447e2018-03-30 15:18:27 -0700417 Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName, mSingleNotificationChannel,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500418 mSbn.getId(), mSbn.getTag());
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000419 if (!mIsForBlockingHelper
420 && settingsIntent != null
Julia Reynolds437cdb12018-01-03 12:27:24 -0500421 && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
Julia Reynoldse0341482018-03-08 14:42:50 -0500422 settingsLinkView.setVisibility(VISIBLE);
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000423 settingsLinkView.setText(mContext.getString(R.string.notification_app_settings));
Julia Reynolds437cdb12018-01-03 12:27:24 -0500424 settingsLinkView.setOnClickListener((View view) -> {
425 mAppSettingsClickListener.onClick(view, settingsIntent);
426 });
Mady Mellor87d79452017-01-10 11:52:52 -0800427 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500428 settingsLinkView.setVisibility(View.GONE);
Mady Mellor87d79452017-01-10 11:52:52 -0800429 }
430 }
431
Gus Prevas894d9152018-11-12 13:51:40 -0500432 private void swapContent(@NotificationInfoAction int action, boolean animate) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500433 if (mExpandAnimation != null) {
434 mExpandAnimation.cancel();
435 }
Mady Mellor87d79452017-01-10 11:52:52 -0800436
Julia Reynoldse0341482018-03-08 14:42:50 -0500437 View prompt = findViewById(R.id.prompt);
438 ViewGroup confirmation = findViewById(R.id.confirmation);
439 TextView confirmationText = findViewById(R.id.confirmation_text);
440 View header = findViewById(R.id.header);
441
Gus Prevas9abc5062018-10-31 16:11:04 -0400442 switch (action) {
Gus Prevas894d9152018-11-12 13:51:40 -0500443 case ACTION_UNDO:
Gus Prevas9abc5062018-10-31 16:11:04 -0400444 mChosenImportance = mStartingChannelImportance;
445 break;
Gus Prevas894d9152018-11-12 13:51:40 -0500446 case ACTION_TOGGLE_SILENT:
447 mExitReason = NotificationCounters.BLOCKING_HELPER_TOGGLE_SILENT;
Gus Prevas9abc5062018-10-31 16:11:04 -0400448 if (mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT) {
449 mChosenImportance = IMPORTANCE_LOW;
450 confirmationText.setText(R.string.notification_channel_silenced);
451 } else {
452 mChosenImportance = IMPORTANCE_HIGH;
453 confirmationText.setText(R.string.notification_channel_unsilenced);
454 }
455 break;
Gus Prevas894d9152018-11-12 13:51:40 -0500456 case ACTION_BLOCK:
457 mExitReason = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS;
Gus Prevas9abc5062018-10-31 16:11:04 -0400458 if (mIsForeground) {
459 mChosenImportance = IMPORTANCE_MIN;
460 confirmationText.setText(R.string.notification_channel_minimized);
461 } else {
462 mChosenImportance = IMPORTANCE_NONE;
463 confirmationText.setText(R.string.notification_channel_disabled);
464 }
465 break;
466 default:
467 throw new IllegalArgumentException();
Julia Reynolds437cdb12018-01-03 12:27:24 -0500468 }
469
Gus Prevas894d9152018-11-12 13:51:40 -0500470 boolean isUndo = action == ACTION_UNDO;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500471
Gus Prevas9abc5062018-10-31 16:11:04 -0400472 prompt.setVisibility(isUndo ? VISIBLE : GONE);
473 confirmation.setVisibility(isUndo ? GONE : VISIBLE);
474 header.setVisibility(isUndo ? VISIBLE : GONE);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500475
Gus Prevas894d9152018-11-12 13:51:40 -0500476 if (animate) {
477 ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA,
478 prompt.getAlpha(), isUndo ? 1f : 0f);
479 promptAnim.setInterpolator(isUndo ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
480 ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA,
481 confirmation.getAlpha(), isUndo ? 0f : 1f);
482 confirmAnim.setInterpolator(isUndo ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500483
Gus Prevas894d9152018-11-12 13:51:40 -0500484 mExpandAnimation = new AnimatorSet();
485 mExpandAnimation.playTogether(promptAnim, confirmAnim);
486 mExpandAnimation.setDuration(150);
487 mExpandAnimation.addListener(new AnimatorListenerAdapter() {
488 boolean mCancelled = false;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500489
Gus Prevas894d9152018-11-12 13:51:40 -0500490 @Override
491 public void onAnimationCancel(Animator animation) {
492 mCancelled = true;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500493 }
Gus Prevas894d9152018-11-12 13:51:40 -0500494
495 @Override
496 public void onAnimationEnd(Animator animation) {
497 if (!mCancelled) {
498 prompt.setVisibility(isUndo ? VISIBLE : GONE);
499 confirmation.setVisibility(isUndo ? GONE : VISIBLE);
500 }
501 }
502 });
503 mExpandAnimation.start();
504 }
Rohan Shah142e2da2018-06-14 13:14:18 -0700505
506 // Since we're swapping/update the content, reset the timeout so the UI can't close
507 // immediately after the update.
508 if (mGutsContainer != null) {
509 mGutsContainer.resetFalsingCheck();
510 }
Mady Mellor87d79452017-01-10 11:52:52 -0800511 }
512
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400513 @Override
Gus Prevas9abc5062018-10-31 16:11:04 -0400514 public void onFinishedClosing() {
515 mStartingChannelImportance = mChosenImportance;
516 if (mChosenImportance != IMPORTANCE_UNSPECIFIED) {
517 mStartingChannelOrNotificationImportance = mChosenImportance;
518 }
519 mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
520
521 View prompt = findViewById(R.id.prompt);
522 ViewGroup confirmation = findViewById(R.id.confirmation);
523 View header = findViewById(R.id.header);
524 prompt.setVisibility(VISIBLE);
525 prompt.setAlpha(1f);
526 confirmation.setVisibility(GONE);
527 confirmation.setAlpha(1f);
528 header.setVisibility(VISIBLE);
529 header.setAlpha(1f);
530 }
531
532 @Override
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400533 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
534 super.onInitializeAccessibilityEvent(event);
535 if (mGutsContainer != null &&
536 event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
537 if (mGutsContainer.isExposed()) {
538 event.getText().add(mContext.getString(
539 R.string.notification_channel_controls_opened_accessibility, mAppName));
540 } else {
541 event.getText().add(mContext.getString(
542 R.string.notification_channel_controls_closed_accessibility, mAppName));
543 }
544 }
545 }
546
Julia Reynolds3aedded2017-03-31 14:42:09 -0400547 private Intent getAppSettingsIntent(PackageManager pm, String packageName,
548 NotificationChannel channel, int id, String tag) {
549 Intent intent = new Intent(Intent.ACTION_MAIN)
550 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
551 .setPackage(packageName);
552 final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
553 intent,
554 PackageManager.MATCH_DEFAULT_ONLY
555 );
556 if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
557 return null;
558 }
559 final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
560 intent.setClassName(activityInfo.packageName, activityInfo.name);
561 if (channel != null) {
562 intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
563 }
564 intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
565 intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
566 return intent;
567 }
568
Rohan Shahca0447e2018-03-30 15:18:27 -0700569 /**
570 * Closes the controls and commits the updated importance values (indirectly). If this view is
571 * being used to show the blocking helper, this will immediately dismiss the blocking helper and
572 * commit the updated importance.
573 *
574 * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
575 * user does not have the ability to undo the action anymore. See {@link #swapContent(boolean)}
576 * for where undo is handled.
577 */
Rohan Shah524cf7b2018-03-15 14:40:02 -0700578 @VisibleForTesting
579 void closeControls(View v) {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700580 int[] parentLoc = new int[2];
581 int[] targetLoc = new int[2];
582 mGutsContainer.getLocationOnScreen(parentLoc);
583 v.getLocationOnScreen(targetLoc);
584 final int centerX = v.getWidth() / 2;
585 final int centerY = v.getHeight() / 2;
586 final int x = targetLoc[0] - parentLoc[0] + centerX;
587 final int y = targetLoc[1] - parentLoc[1] + centerY;
588 mGutsContainer.closeControls(x, y, true /* save */, false /* force */);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500589 }
590
Mady Mellor87d79452017-01-10 11:52:52 -0800591 @Override
Mady Mellor95d743c2017-01-10 12:05:27 -0800592 public void setGutsParent(NotificationGuts guts) {
593 mGutsContainer = guts;
Mady Mellor87d79452017-01-10 11:52:52 -0800594 }
595
596 @Override
Mady Mellor434180c2017-02-13 11:29:42 -0800597 public boolean willBeRemoved() {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500598 return hasImportanceChanged();
Mady Mellor434180c2017-02-13 11:29:42 -0800599 }
600
601 @Override
Lucas Dupin9b08c012018-05-16 19:53:32 -0700602 public boolean shouldBeSaved() {
603 return hasImportanceChanged();
604 }
605
606 @Override
Mady Mellor87d79452017-01-10 11:52:52 -0800607 public View getContentView() {
608 return this;
609 }
610
611 @Override
Mady Mellorc2dbe492017-03-30 13:22:03 -0700612 public boolean handleCloseControls(boolean save, boolean force) {
Julia Reynoldsc65656a2018-02-12 09:55:14 -0500613 // Save regardless of the importance so we can lock the importance field if the user wants
614 // to keep getting notifications
615 if (save) {
Rohan Shahca0447e2018-03-30 15:18:27 -0700616 saveImportance();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500617 }
Rohan Shahdd588c72018-05-09 20:32:15 -0700618 logBlockingHelperCounter(mExitReason);
Mady Mellor87d79452017-01-10 11:52:52 -0800619 return false;
620 }
Mady Mellore09fb702017-03-30 13:23:29 -0700621
622 @Override
623 public int getActualHeight() {
624 return getHeight();
625 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700626
Gus Prevas533836a2018-09-24 17:15:32 -0400627 @VisibleForTesting
628 public boolean isAnimating() {
629 return mExpandAnimation != null && mExpandAnimation.isRunning();
630 }
631
Rohan Shahca0447e2018-03-30 15:18:27 -0700632 /**
633 * Runnable to either update the given channel (with a new importance value) or, if no channel
634 * is provided, update notifications enabled state for the package.
635 */
636 private static class UpdateImportanceRunnable implements Runnable {
637 private final INotificationManager mINotificationManager;
638 private final String mPackageName;
639 private final int mAppUid;
640 private final @Nullable NotificationChannel mChannelToUpdate;
641 private final int mCurrentImportance;
642 private final int mNewImportance;
643
644
645 public UpdateImportanceRunnable(INotificationManager notificationManager,
646 String packageName, int appUid, @Nullable NotificationChannel channelToUpdate,
647 int currentImportance, int newImportance) {
648 mINotificationManager = notificationManager;
649 mPackageName = packageName;
650 mAppUid = appUid;
651 mChannelToUpdate = channelToUpdate;
652 mCurrentImportance = currentImportance;
653 mNewImportance = newImportance;
654 }
655
656 @Override
657 public void run() {
658 try {
659 if (mChannelToUpdate != null) {
660 mChannelToUpdate.setImportance(mNewImportance);
661 mChannelToUpdate.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
662 mINotificationManager.updateNotificationChannelForPackage(
663 mPackageName, mAppUid, mChannelToUpdate);
664 } else {
665 // For notifications with more than one channel, update notification enabled
666 // state. If the importance was lowered, we disable notifications.
Rohan Shah590e1b22018-04-10 23:48:47 -0400667 mINotificationManager.setNotificationsEnabledWithImportanceLockForPackage(
Rohan Shahca0447e2018-03-30 15:18:27 -0700668 mPackageName, mAppUid, mNewImportance >= mCurrentImportance);
669 }
670 } catch (RemoteException e) {
671 Log.e(TAG, "Unable to update notification importance", e);
672 }
673 }
674 }
Mady Mellor87d79452017-01-10 11:52:52 -0800675}