blob: 5253e38fd675833b5c763349fb6436f00a3ba705 [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;
William Brockman75cf66a2018-12-21 13:25:01 -050043import android.metrics.LogMaker;
Rohan Shahca0447e2018-03-30 15:18:27 -070044import android.os.Handler;
Mady Mellor87d79452017-01-10 11:52:52 -080045import android.os.RemoteException;
Julia Reynolds3aedded2017-03-31 14:42:09 -040046import android.service.notification.StatusBarNotification;
47import android.text.TextUtils;
Mady Mellor87d79452017-01-10 11:52:52 -080048import android.util.AttributeSet;
Rohan Shahca0447e2018-03-30 15:18:27 -070049import android.util.Log;
Mady Mellor87d79452017-01-10 11:52:52 -080050import android.view.View;
Julia Reynoldse0341482018-03-08 14:42:50 -050051import android.view.ViewGroup;
Geoffrey Pitschd94e7882017-04-06 09:52:11 -040052import android.view.accessibility.AccessibilityEvent;
Mady Mellor87d79452017-01-10 11:52:52 -080053import android.widget.ImageView;
54import android.widget.LinearLayout;
Mady Mellor87d79452017-01-10 11:52:52 -080055import android.widget.TextView;
56
Rohan Shah524cf7b2018-03-15 14:40:02 -070057import com.android.internal.annotations.VisibleForTesting;
Mady Mellor87d79452017-01-10 11:52:52 -080058import com.android.internal.logging.MetricsLogger;
59import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Rohan Shah524cf7b2018-03-15 14:40:02 -070060import com.android.systemui.Dependency;
Julia Reynolds437cdb12018-01-03 12:27:24 -050061import com.android.systemui.Interpolators;
Mady Mellor87d79452017-01-10 11:52:52 -080062import com.android.systemui.R;
Gus Prevas9abc5062018-10-31 16:11:04 -040063import com.android.systemui.statusbar.notification.NotificationUtils;
Rohan Shah20790b82018-07-02 17:21:04 -070064import com.android.systemui.statusbar.notification.logging.NotificationCounters;
Mady Mellor87d79452017-01-10 11:52:52 -080065
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050066import java.util.List;
Mady Mellor87d79452017-01-10 11:52:52 -080067
68/**
Rohan Shahda5dcdd2018-04-27 17:21:50 -070069 * The guts of a notification revealed when performing a long press. This also houses the blocking
70 * helper affordance that allows a user to keep/stop notifications after swiping one away.
Mady Mellor87d79452017-01-10 11:52:52 -080071 */
Mady Mellor95d743c2017-01-10 12:05:27 -080072public class NotificationInfo extends LinearLayout implements NotificationGuts.GutsContent {
Mady Mellor87d79452017-01-10 11:52:52 -080073 private static final String TAG = "InfoGuts";
74
Gus Prevas894d9152018-11-12 13:51:40 -050075 @IntDef(prefix = { "ACTION_" }, value = {
76 ACTION_NONE,
77 ACTION_UNDO,
78 ACTION_TOGGLE_SILENT,
79 ACTION_BLOCK,
Gus Prevas9abc5062018-10-31 16:11:04 -040080 })
Gus Prevas894d9152018-11-12 13:51:40 -050081 public @interface NotificationInfoAction {
82 }
Gus Prevas9abc5062018-10-31 16:11:04 -040083
Gus Prevas894d9152018-11-12 13:51:40 -050084 public static final int ACTION_NONE = 0;
85 public static final int ACTION_UNDO = 1;
86 public static final int ACTION_TOGGLE_SILENT = 2;
87 public static final int ACTION_BLOCK = 3;
Gus Prevas9abc5062018-10-31 16:11:04 -040088
Mady Mellor87d79452017-01-10 11:52:52 -080089 private INotificationManager mINotificationManager;
Julia Reynolds437cdb12018-01-03 12:27:24 -050090 private PackageManager mPm;
Rohan Shahda5dcdd2018-04-27 17:21:50 -070091 private MetricsLogger mMetricsLogger;
Julia Reynolds437cdb12018-01-03 12:27:24 -050092
Rohan Shahca0447e2018-03-30 15:18:27 -070093 private String mPackageName;
Geoffrey Pitschd94e7882017-04-06 09:52:11 -040094 private String mAppName;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050095 private int mAppUid;
Julia Reynolds268647a2018-10-25 16:54:27 -040096 private String mDelegatePkg;
Rohan Shahca0447e2018-03-30 15:18:27 -070097 private int mNumUniqueChannelsInRow;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050098 private NotificationChannel mSingleNotificationChannel;
Gus Prevas9abc5062018-10-31 16:11:04 -040099 private int mStartingChannelImportance;
100 private int mStartingChannelOrNotificationImportance;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500101 private int mChosenImportance;
102 private boolean mIsSingleDefaultChannel;
Rohan Shah63411fc2018-03-28 19:05:52 -0700103 private boolean mIsNonblockable;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500104 private StatusBarNotification mSbn;
105 private AnimatorSet mExpandAnimation;
Julia Reynoldse0341482018-03-08 14:42:50 -0500106 private boolean mIsForeground;
Julia Reynolds35765d82018-08-17 11:39:19 -0400107 private boolean mIsDeviceProvisioned;
Mady Mellor87d79452017-01-10 11:52:52 -0800108
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400109 private CheckSaveListener mCheckSaveListener;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500110 private OnSettingsClickListener mOnSettingsClickListener;
Julia Reynolds3aedded2017-03-31 14:42:09 -0400111 private OnAppSettingsClickListener mAppSettingsClickListener;
Mady Mellor95d743c2017-01-10 12:05:27 -0800112 private NotificationGuts mGutsContainer;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700113
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700114 /** Whether this view is being shown as part of the blocking helper. */
Rohan Shah524cf7b2018-03-15 14:40:02 -0700115 private boolean mIsForBlockingHelper;
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500116 private boolean mNegativeUserSentiment;
Mady Mellor87d79452017-01-10 11:52:52 -0800117
Rohan Shahdd588c72018-05-09 20:32:15 -0700118 /**
119 * String that describes how the user exit or quit out of this view, also used as a counter tag.
120 */
121 private String mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700122
123 private OnClickListener mOnKeepShowing = v -> {
Rohan Shahdd588c72018-05-09 20:32:15 -0700124 mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700125 closeControls(v);
Eyal Posenera9cf9c72018-12-18 16:23:54 +0200126 mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
127 .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_STAY_SILENT));
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700128 };
Julia Reynolds437cdb12018-01-03 12:27:24 -0500129
Gus Prevas9abc5062018-10-31 16:11:04 -0400130 private OnClickListener mOnToggleSilent = v -> {
131 Runnable saveImportance = () -> {
Gus Prevas894d9152018-11-12 13:51:40 -0500132 swapContent(ACTION_TOGGLE_SILENT, true /* animate */);
Eyal Posenera9cf9c72018-12-18 16:23:54 +0200133 mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
134 .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_ALERT_ME));
Gus Prevas9abc5062018-10-31 16:11:04 -0400135 };
136 if (mCheckSaveListener != null) {
137 mCheckSaveListener.checkSave(saveImportance, mSbn);
138 } else {
139 saveImportance.run();
140 }
141 };
142
Rohan Shahca0447e2018-03-30 15:18:27 -0700143 private OnClickListener mOnStopOrMinimizeNotifications = v -> {
Gus Prevas533836a2018-09-24 17:15:32 -0400144 Runnable saveImportance = () -> {
Gus Prevas894d9152018-11-12 13:51:40 -0500145 swapContent(ACTION_BLOCK, true /* animate */);
Eyal Posenera9cf9c72018-12-18 16:23:54 +0200146 mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
147 .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_BLOCKED));
Gus Prevas533836a2018-09-24 17:15:32 -0400148 };
149 if (mCheckSaveListener != null) {
150 mCheckSaveListener.checkSave(saveImportance, mSbn);
151 } else {
152 saveImportance.run();
153 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500154 };
155
156 private OnClickListener mOnUndo = v -> {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700157 // Reset exit counter that we'll log and record an undo event separately (not an exit event)
Rohan Shahdd588c72018-05-09 20:32:15 -0700158 mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700159 logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
William Brockman75cf66a2018-12-21 13:25:01 -0500160 mMetricsLogger.write(importanceChangeLogMaker().setType(MetricsEvent.TYPE_DISMISS));
Gus Prevas894d9152018-11-12 13:51:40 -0500161 swapContent(ACTION_UNDO, true /* animate */);
Eyal Posenera9cf9c72018-12-18 16:23:54 +0200162 mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
163 .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_UNDO));
Julia Reynolds437cdb12018-01-03 12:27:24 -0500164 };
165
Mady Mellor87d79452017-01-10 11:52:52 -0800166 public NotificationInfo(Context context, AttributeSet attrs) {
167 super(context, attrs);
168 }
169
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400170 // Specify a CheckSaveListener to override when/if the user's changes are committed.
171 public interface CheckSaveListener {
172 // Invoked when importance has changed and the NotificationInfo wants to try to save it.
173 // Listener should run saveImportance unless the change should be canceled.
Eliot Courtney47098cb2017-10-18 17:30:30 +0900174 void checkSave(Runnable saveImportance, StatusBarNotification sbn);
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400175 }
176
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500177 public interface OnSettingsClickListener {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500178 void onClick(View v, NotificationChannel channel, int appUid);
Mady Mellor87d79452017-01-10 11:52:52 -0800179 }
180
Julia Reynolds3aedded2017-03-31 14:42:09 -0400181 public interface OnAppSettingsClickListener {
182 void onClick(View v, Intent intent);
183 }
184
Rohan Shah524cf7b2018-03-15 14:40:02 -0700185 @VisibleForTesting
186 void bindNotification(
187 final PackageManager pm,
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500188 final INotificationManager iNotificationManager,
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500189 final String pkg,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500190 final NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700191 final int numUniqueChannelsInRow,
Julia Reynolds3aedded2017-03-31 14:42:09 -0400192 final StatusBarNotification sbn,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500193 final CheckSaveListener checkSaveListener,
194 final OnSettingsClickListener onSettingsClick,
195 final OnAppSettingsClickListener onAppSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400196 boolean isDeviceProvisioned,
Gus Prevas9abc5062018-10-31 16:11:04 -0400197 boolean isNonblockable,
Gus Prevas5a70a4e2018-11-26 17:16:05 -0500198 int importance)
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500199 throws RemoteException {
Rohan Shahca0447e2018-03-30 15:18:27 -0700200 bindNotification(pm, iNotificationManager, pkg, notificationChannel,
201 numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400202 onAppSettingsClick, isDeviceProvisioned, isNonblockable,
Gus Prevas82ad0592018-11-28 14:27:40 -0500203 false /* isBlockingHelper */, false /* isUserSentimentNegative */,
Gus Prevas5a70a4e2018-11-26 17:16:05 -0500204 importance);
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500205 }
206
Rohan Shah524cf7b2018-03-15 14:40:02 -0700207 public void bindNotification(
208 PackageManager pm,
209 INotificationManager iNotificationManager,
210 String pkg,
211 NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700212 int numUniqueChannelsInRow,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700213 StatusBarNotification sbn,
214 CheckSaveListener checkSaveListener,
215 OnSettingsClickListener onSettingsClick,
216 OnAppSettingsClickListener onAppSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400217 boolean isDeviceProvisioned,
Rohan Shah63411fc2018-03-28 19:05:52 -0700218 boolean isNonblockable,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700219 boolean isForBlockingHelper,
Gus Prevas9abc5062018-10-31 16:11:04 -0400220 boolean isUserSentimentNegative,
Gus Prevas5a70a4e2018-11-26 17:16:05 -0500221 int importance)
Rohan Shah524cf7b2018-03-15 14:40:02 -0700222 throws RemoteException {
Mady Mellor87d79452017-01-10 11:52:52 -0800223 mINotificationManager = iNotificationManager;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700224 mMetricsLogger = Dependency.get(MetricsLogger.class);
Rohan Shahca0447e2018-03-30 15:18:27 -0700225 mPackageName = pkg;
226 mNumUniqueChannelsInRow = numUniqueChannelsInRow;
Julia Reynolds3aedded2017-03-31 14:42:09 -0400227 mSbn = sbn;
228 mPm = pm;
229 mAppSettingsClickListener = onAppSettingsClick;
Rohan Shahca0447e2018-03-30 15:18:27 -0700230 mAppName = mPackageName;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500231 mCheckSaveListener = checkSaveListener;
232 mOnSettingsClickListener = onSettingsClick;
233 mSingleNotificationChannel = notificationChannel;
Gus Prevas9abc5062018-10-31 16:11:04 -0400234 int channelImportance = mSingleNotificationChannel.getImportance();
235 mStartingChannelImportance = mChosenImportance = channelImportance;
236 mStartingChannelOrNotificationImportance =
237 channelImportance == IMPORTANCE_UNSPECIFIED ? importance : channelImportance;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700238 mNegativeUserSentiment = isUserSentimentNegative;
Rohan Shah63411fc2018-03-28 19:05:52 -0700239 mIsNonblockable = isNonblockable;
Julia Reynoldse0341482018-03-08 14:42:50 -0500240 mIsForeground =
241 (mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700242 mIsForBlockingHelper = isForBlockingHelper;
Rohan Shahdbd64e72018-03-28 14:46:50 -0700243 mAppUid = mSbn.getUid();
Julia Reynolds268647a2018-10-25 16:54:27 -0400244 mDelegatePkg = mSbn.getOpPkg();
Julia Reynolds35765d82018-08-17 11:39:19 -0400245 mIsDeviceProvisioned = isDeviceProvisioned;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500246
Julia Reynolds437cdb12018-01-03 12:27:24 -0500247 int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400248 pkg, mAppUid, false /* includeDeleted */);
Rohan Shahca0447e2018-03-30 15:18:27 -0700249 if (mNumUniqueChannelsInRow == 0) {
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400250 throw new IllegalArgumentException("bindNotification requires at least one channel");
251 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500252 // Special behavior for the Default channel if no other channels have been defined.
Rohan Shahca0447e2018-03-30 15:18:27 -0700253 mIsSingleDefaultChannel = mNumUniqueChannelsInRow == 1
Rohan Shahdbd64e72018-03-28 14:46:50 -0700254 && mSingleNotificationChannel.getId().equals(
255 NotificationChannel.DEFAULT_CHANNEL_ID)
256 && numTotalChannels == 1;
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400257 }
258
Julia Reynolds437cdb12018-01-03 12:27:24 -0500259 bindHeader();
260 bindPrompt();
261 bindButtons();
Eyal Posenera9cf9c72018-12-18 16:23:54 +0200262
263 mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
264 .setSubtype(MetricsEvent.BLOCKING_HELPER_DISPLAY));
Julia Reynolds437cdb12018-01-03 12:27:24 -0500265 }
266
267 private void bindHeader() throws RemoteException {
268 // Package name
269 Drawable pkgicon = null;
270 ApplicationInfo info;
271 try {
Rohan Shahca0447e2018-03-30 15:18:27 -0700272 info = mPm.getApplicationInfo(
273 mPackageName,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500274 PackageManager.MATCH_UNINSTALLED_PACKAGES
275 | PackageManager.MATCH_DISABLED_COMPONENTS
276 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
277 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
278 if (info != null) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500279 mAppName = String.valueOf(mPm.getApplicationLabel(info));
280 pkgicon = mPm.getApplicationIcon(info);
Geoffrey Pitschee8c81e2017-03-23 11:38:56 -0400281 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500282 } catch (PackageManager.NameNotFoundException e) {
283 // app is gone, just show package name and generic icon
284 pkgicon = mPm.getDefaultActivityIcon();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500285 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500286 ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400287 ((TextView) findViewById(R.id.pkgname)).setText(mAppName);
Mady Mellor87d79452017-01-10 11:52:52 -0800288
Julia Reynolds268647a2018-10-25 16:54:27 -0400289 // Delegate
290 bindDelegate();
Daniel Sandlerd0a52b62018-10-31 20:13:22 +0000291
292 // Settings button.
293 final View settingsButton = findViewById(R.id.info);
294 if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
295 settingsButton.setVisibility(View.VISIBLE);
296 final int appUidF = mAppUid;
297 settingsButton.setOnClickListener(
298 (View view) -> {
299 logBlockingHelperCounter(
300 NotificationCounters.BLOCKING_HELPER_NOTIF_SETTINGS);
301 mOnSettingsClickListener.onClick(view,
302 mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel,
303 appUidF);
304 });
305 } else {
306 settingsButton.setVisibility(View.GONE);
307 }
308 }
309
Julia Reynolds268647a2018-10-25 16:54:27 -0400310 private void bindPrompt() throws RemoteException {
Daniel Sandlerd0a52b62018-10-31 20:13:22 +0000311 final TextView blockPrompt = findViewById(R.id.block_prompt);
312 bindName();
Julia Reynolds268647a2018-10-25 16:54:27 -0400313 bindGroup();
Daniel Sandlerd0a52b62018-10-31 20:13:22 +0000314 if (mIsNonblockable) {
315 blockPrompt.setText(R.string.notification_unblockable_desc);
316 } else {
317 if (mNegativeUserSentiment) {
318 blockPrompt.setText(R.string.inline_blocking_helper);
319 } else if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
320 blockPrompt.setText(R.string.inline_keep_showing_app);
321 } else {
322 blockPrompt.setText(R.string.inline_keep_showing);
323 }
324 }
325 }
326
327 private void bindName() {
328 final TextView channelName = findViewById(R.id.channel_name);
329 if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
330 channelName.setVisibility(View.GONE);
331 } else {
332 channelName.setText(mSingleNotificationChannel.getName());
333 }
Julia Reynoldsac98aea2018-10-25 16:54:27 -0400334 }
335
Julia Reynolds268647a2018-10-25 16:54:27 -0400336 private void bindDelegate() {
337 TextView delegateView = findViewById(R.id.delegate_name);
338 TextView dividerView = findViewById(R.id.pkg_divider);
339
340 CharSequence delegatePkg = null;
341 if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
342 // this notification was posted by a delegate!
343 ApplicationInfo info;
344 try {
345 info = mPm.getApplicationInfo(
346 mDelegatePkg,
347 PackageManager.MATCH_UNINSTALLED_PACKAGES
348 | PackageManager.MATCH_DISABLED_COMPONENTS
349 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
350 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
351 if (info != null) {
352 delegatePkg = String.valueOf(mPm.getApplicationLabel(info));
353 }
354 } catch (PackageManager.NameNotFoundException e) { }
355 }
356 if (delegatePkg != null) {
357 delegateView.setText(mContext.getResources().getString(
358 R.string.notification_delegate_header, delegatePkg));
359 delegateView.setVisibility(View.VISIBLE);
360 dividerView.setVisibility(View.VISIBLE);
361 } else {
362 delegateView.setVisibility(View.GONE);
363 dividerView.setVisibility(View.GONE);
364 }
365 }
366
367 private void bindGroup() throws RemoteException {
368 // Set group information if this channel has an associated group.
369 CharSequence groupName = null;
370 if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
371 final NotificationChannelGroup notificationChannelGroup =
372 mINotificationManager.getNotificationChannelGroupForPackage(
373 mSingleNotificationChannel.getGroup(), mPackageName, mAppUid);
374 if (notificationChannelGroup != null) {
375 groupName = notificationChannelGroup.getName();
376 }
377 }
378 TextView groupNameView = findViewById(R.id.group_name);
379 TextView groupDividerView = findViewById(R.id.pkg_group_divider);
380 if (groupName != null) {
381 groupNameView.setText(groupName);
382 groupNameView.setVisibility(View.VISIBLE);
383 groupDividerView.setVisibility(View.VISIBLE);
384 } else {
385 groupNameView.setVisibility(View.GONE);
386 groupDividerView.setVisibility(View.GONE);
387 }
388 }
389
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700390 @VisibleForTesting
391 void logBlockingHelperCounter(String counterTag) {
392 if (mIsForBlockingHelper) {
393 mMetricsLogger.count(counterTag, 1);
394 }
395 }
396
William Brockman75cf66a2018-12-21 13:25:01 -0500397 /**
398 * Returns an initialized LogMaker for logging importance changes.
399 * The caller may override the type (to DISMISS) before passing it to mMetricsLogger.
400 * @return new LogMaker
401 */
402 private LogMaker importanceChangeLogMaker() {
403 return new LogMaker(MetricsEvent.ACTION_SAVE_IMPORTANCE)
404 .setType(MetricsEvent.TYPE_ACTION)
405 .setSubtype(mChosenImportance - mStartingChannelImportance);
406 }
407
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400408 private boolean hasImportanceChanged() {
Gus Prevas9abc5062018-10-31 16:11:04 -0400409 return mSingleNotificationChannel != null
410 && mStartingChannelImportance != mChosenImportance;
Mady Mellor87d79452017-01-10 11:52:52 -0800411 }
412
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500413 private void saveImportance() {
Gus Prevas894d9152018-11-12 13:51:40 -0500414 if (!mIsNonblockable
415 || mExitReason != NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS) {
Gus Prevas533836a2018-09-24 17:15:32 -0400416 updateImportance();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500417 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700418 }
419
420 /**
421 * Commits the updated importance values on the background thread.
422 */
423 private void updateImportance() {
William Brockman75cf66a2018-12-21 13:25:01 -0500424 mMetricsLogger.write(importanceChangeLogMaker());
Rohan Shahca0447e2018-03-30 15:18:27 -0700425
426 Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
427 bgHandler.post(new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
428 mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
Gus Prevas9abc5062018-10-31 16:11:04 -0400429 mStartingChannelImportance, mChosenImportance));
Mady Mellor87d79452017-01-10 11:52:52 -0800430 }
431
Julia Reynolds437cdb12018-01-03 12:27:24 -0500432 private void bindButtons() {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700433 findViewById(R.id.undo).setOnClickListener(mOnUndo);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500434
Gus Prevas82ad0592018-11-28 14:27:40 -0500435 boolean showInterruptivenessSettings =
436 !mIsNonblockable
437 && !mIsForeground
438 && !mIsForBlockingHelper
439 && NotificationUtils.useNewInterruptionModel(mContext);
440 if (showInterruptivenessSettings) {
441 findViewById(R.id.block_or_minimize).setVisibility(GONE);
442 findViewById(R.id.interruptiveness_settings).setVisibility(VISIBLE);
443 View block = findViewById(R.id.int_block);
444 TextView silent = findViewById(R.id.int_silent);
445 TextView alert = findViewById(R.id.int_alert);
446
Gus Prevas9abc5062018-10-31 16:11:04 -0400447 boolean isCurrentlyAlerting =
448 mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500449
Gus Prevas82ad0592018-11-28 14:27:40 -0500450 block.setOnClickListener(mOnStopOrMinimizeNotifications);
451 if (isCurrentlyAlerting) {
452 silent.setOnClickListener(mOnToggleSilent);
453 silent.setText(R.string.inline_silent_button_silent);
454 alert.setOnClickListener(mOnKeepShowing);
455 alert.setText(R.string.inline_silent_button_keep_alerting);
456 } else {
457 silent.setOnClickListener(mOnKeepShowing);
458 silent.setText(R.string.inline_silent_button_stay_silent);
459 alert.setOnClickListener(mOnToggleSilent);
460 alert.setText(R.string.inline_silent_button_alert);
461 }
Mady Mellor87d79452017-01-10 11:52:52 -0800462 } else {
Gus Prevas82ad0592018-11-28 14:27:40 -0500463 findViewById(R.id.block_or_minimize).setVisibility(VISIBLE);
464 findViewById(R.id.interruptiveness_settings).setVisibility(GONE);
465 View block = findViewById(R.id.block);
466 TextView keep = findViewById(R.id.keep);
467 View minimize = findViewById(R.id.minimize);
468
469 block.setOnClickListener(mOnStopOrMinimizeNotifications);
470 keep.setOnClickListener(mOnKeepShowing);
471 minimize.setOnClickListener(mOnStopOrMinimizeNotifications);
472
473 if (mIsNonblockable) {
474 keep.setText(android.R.string.ok);
475 block.setVisibility(GONE);
476 minimize.setVisibility(GONE);
477 } else if (mIsForeground) {
478 block.setVisibility(GONE);
479 minimize.setVisibility(VISIBLE);
480 } else {
481 block.setVisibility(VISIBLE);
482 minimize.setVisibility(GONE);
483 }
484
485 // Set up app settings link (i.e. Customize)
486 TextView settingsLinkView = findViewById(R.id.app_settings);
487 Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName,
488 mSingleNotificationChannel,
489 mSbn.getId(), mSbn.getTag());
490 if (!mIsForBlockingHelper
491 && settingsIntent != null
492 && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
493 settingsLinkView.setVisibility(VISIBLE);
494 settingsLinkView.setText(mContext.getString(R.string.notification_app_settings));
495 settingsLinkView.setOnClickListener((View view) -> {
496 mAppSettingsClickListener.onClick(view, settingsIntent);
497 });
498 } else {
499 settingsLinkView.setVisibility(View.GONE);
500 }
Mady Mellor87d79452017-01-10 11:52:52 -0800501 }
502 }
503
Gus Prevas894d9152018-11-12 13:51:40 -0500504 private void swapContent(@NotificationInfoAction int action, boolean animate) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500505 if (mExpandAnimation != null) {
506 mExpandAnimation.cancel();
507 }
Mady Mellor87d79452017-01-10 11:52:52 -0800508
Julia Reynoldse0341482018-03-08 14:42:50 -0500509 View prompt = findViewById(R.id.prompt);
510 ViewGroup confirmation = findViewById(R.id.confirmation);
511 TextView confirmationText = findViewById(R.id.confirmation_text);
512 View header = findViewById(R.id.header);
513
Gus Prevas9abc5062018-10-31 16:11:04 -0400514 switch (action) {
Gus Prevas894d9152018-11-12 13:51:40 -0500515 case ACTION_UNDO:
Gus Prevas9abc5062018-10-31 16:11:04 -0400516 mChosenImportance = mStartingChannelImportance;
517 break;
Gus Prevas894d9152018-11-12 13:51:40 -0500518 case ACTION_TOGGLE_SILENT:
519 mExitReason = NotificationCounters.BLOCKING_HELPER_TOGGLE_SILENT;
Gus Prevas9abc5062018-10-31 16:11:04 -0400520 if (mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT) {
521 mChosenImportance = IMPORTANCE_LOW;
522 confirmationText.setText(R.string.notification_channel_silenced);
523 } else {
524 mChosenImportance = IMPORTANCE_HIGH;
525 confirmationText.setText(R.string.notification_channel_unsilenced);
526 }
527 break;
Gus Prevas894d9152018-11-12 13:51:40 -0500528 case ACTION_BLOCK:
529 mExitReason = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS;
Gus Prevas9abc5062018-10-31 16:11:04 -0400530 if (mIsForeground) {
531 mChosenImportance = IMPORTANCE_MIN;
532 confirmationText.setText(R.string.notification_channel_minimized);
533 } else {
534 mChosenImportance = IMPORTANCE_NONE;
535 confirmationText.setText(R.string.notification_channel_disabled);
536 }
537 break;
538 default:
539 throw new IllegalArgumentException();
Julia Reynolds437cdb12018-01-03 12:27:24 -0500540 }
541
Gus Prevas894d9152018-11-12 13:51:40 -0500542 boolean isUndo = action == ACTION_UNDO;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500543
Gus Prevas9abc5062018-10-31 16:11:04 -0400544 prompt.setVisibility(isUndo ? VISIBLE : GONE);
545 confirmation.setVisibility(isUndo ? GONE : VISIBLE);
546 header.setVisibility(isUndo ? VISIBLE : GONE);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500547
Gus Prevas894d9152018-11-12 13:51:40 -0500548 if (animate) {
549 ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA,
550 prompt.getAlpha(), isUndo ? 1f : 0f);
551 promptAnim.setInterpolator(isUndo ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
552 ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA,
553 confirmation.getAlpha(), isUndo ? 0f : 1f);
554 confirmAnim.setInterpolator(isUndo ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500555
Gus Prevas894d9152018-11-12 13:51:40 -0500556 mExpandAnimation = new AnimatorSet();
557 mExpandAnimation.playTogether(promptAnim, confirmAnim);
558 mExpandAnimation.setDuration(150);
559 mExpandAnimation.addListener(new AnimatorListenerAdapter() {
560 boolean mCancelled = false;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500561
Gus Prevas894d9152018-11-12 13:51:40 -0500562 @Override
563 public void onAnimationCancel(Animator animation) {
564 mCancelled = true;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500565 }
Gus Prevas894d9152018-11-12 13:51:40 -0500566
567 @Override
568 public void onAnimationEnd(Animator animation) {
569 if (!mCancelled) {
570 prompt.setVisibility(isUndo ? VISIBLE : GONE);
571 confirmation.setVisibility(isUndo ? GONE : VISIBLE);
572 }
573 }
574 });
575 mExpandAnimation.start();
576 }
Rohan Shah142e2da2018-06-14 13:14:18 -0700577
578 // Since we're swapping/update the content, reset the timeout so the UI can't close
579 // immediately after the update.
580 if (mGutsContainer != null) {
581 mGutsContainer.resetFalsingCheck();
582 }
Mady Mellor87d79452017-01-10 11:52:52 -0800583 }
584
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400585 @Override
Gus Prevas9abc5062018-10-31 16:11:04 -0400586 public void onFinishedClosing() {
587 mStartingChannelImportance = mChosenImportance;
588 if (mChosenImportance != IMPORTANCE_UNSPECIFIED) {
589 mStartingChannelOrNotificationImportance = mChosenImportance;
590 }
591 mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
592
593 View prompt = findViewById(R.id.prompt);
594 ViewGroup confirmation = findViewById(R.id.confirmation);
595 View header = findViewById(R.id.header);
596 prompt.setVisibility(VISIBLE);
597 prompt.setAlpha(1f);
598 confirmation.setVisibility(GONE);
599 confirmation.setAlpha(1f);
600 header.setVisibility(VISIBLE);
601 header.setAlpha(1f);
Eyal Posenera9cf9c72018-12-18 16:23:54 +0200602 mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
603 .setSubtype(MetricsEvent.BLOCKING_HELPER_DISMISS));
Gus Prevas9abc5062018-10-31 16:11:04 -0400604 }
605
606 @Override
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400607 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
608 super.onInitializeAccessibilityEvent(event);
609 if (mGutsContainer != null &&
610 event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
611 if (mGutsContainer.isExposed()) {
612 event.getText().add(mContext.getString(
613 R.string.notification_channel_controls_opened_accessibility, mAppName));
614 } else {
615 event.getText().add(mContext.getString(
616 R.string.notification_channel_controls_closed_accessibility, mAppName));
617 }
618 }
619 }
620
Julia Reynolds3aedded2017-03-31 14:42:09 -0400621 private Intent getAppSettingsIntent(PackageManager pm, String packageName,
622 NotificationChannel channel, int id, String tag) {
623 Intent intent = new Intent(Intent.ACTION_MAIN)
624 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
625 .setPackage(packageName);
626 final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
627 intent,
628 PackageManager.MATCH_DEFAULT_ONLY
629 );
630 if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
631 return null;
632 }
633 final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
634 intent.setClassName(activityInfo.packageName, activityInfo.name);
635 if (channel != null) {
636 intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
637 }
638 intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
639 intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
640 return intent;
641 }
642
Rohan Shahca0447e2018-03-30 15:18:27 -0700643 /**
644 * Closes the controls and commits the updated importance values (indirectly). If this view is
645 * being used to show the blocking helper, this will immediately dismiss the blocking helper and
646 * commit the updated importance.
647 *
648 * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
649 * user does not have the ability to undo the action anymore. See {@link #swapContent(boolean)}
650 * for where undo is handled.
651 */
Rohan Shah524cf7b2018-03-15 14:40:02 -0700652 @VisibleForTesting
653 void closeControls(View v) {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700654 int[] parentLoc = new int[2];
655 int[] targetLoc = new int[2];
656 mGutsContainer.getLocationOnScreen(parentLoc);
657 v.getLocationOnScreen(targetLoc);
658 final int centerX = v.getWidth() / 2;
659 final int centerY = v.getHeight() / 2;
660 final int x = targetLoc[0] - parentLoc[0] + centerX;
661 final int y = targetLoc[1] - parentLoc[1] + centerY;
662 mGutsContainer.closeControls(x, y, true /* save */, false /* force */);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500663 }
664
Mady Mellor87d79452017-01-10 11:52:52 -0800665 @Override
Mady Mellor95d743c2017-01-10 12:05:27 -0800666 public void setGutsParent(NotificationGuts guts) {
667 mGutsContainer = guts;
Mady Mellor87d79452017-01-10 11:52:52 -0800668 }
669
670 @Override
Mady Mellor434180c2017-02-13 11:29:42 -0800671 public boolean willBeRemoved() {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500672 return hasImportanceChanged();
Mady Mellor434180c2017-02-13 11:29:42 -0800673 }
674
675 @Override
Lucas Dupin9b08c012018-05-16 19:53:32 -0700676 public boolean shouldBeSaved() {
677 return hasImportanceChanged();
678 }
679
680 @Override
Mady Mellor87d79452017-01-10 11:52:52 -0800681 public View getContentView() {
682 return this;
683 }
684
685 @Override
Mady Mellorc2dbe492017-03-30 13:22:03 -0700686 public boolean handleCloseControls(boolean save, boolean force) {
Julia Reynoldsc65656a2018-02-12 09:55:14 -0500687 // Save regardless of the importance so we can lock the importance field if the user wants
688 // to keep getting notifications
689 if (save) {
Rohan Shahca0447e2018-03-30 15:18:27 -0700690 saveImportance();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500691 }
Rohan Shahdd588c72018-05-09 20:32:15 -0700692 logBlockingHelperCounter(mExitReason);
Mady Mellor87d79452017-01-10 11:52:52 -0800693 return false;
694 }
Mady Mellore09fb702017-03-30 13:23:29 -0700695
696 @Override
697 public int getActualHeight() {
698 return getHeight();
699 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700700
Gus Prevas533836a2018-09-24 17:15:32 -0400701 @VisibleForTesting
702 public boolean isAnimating() {
703 return mExpandAnimation != null && mExpandAnimation.isRunning();
704 }
705
Rohan Shahca0447e2018-03-30 15:18:27 -0700706 /**
707 * Runnable to either update the given channel (with a new importance value) or, if no channel
708 * is provided, update notifications enabled state for the package.
709 */
710 private static class UpdateImportanceRunnable implements Runnable {
711 private final INotificationManager mINotificationManager;
712 private final String mPackageName;
713 private final int mAppUid;
714 private final @Nullable NotificationChannel mChannelToUpdate;
715 private final int mCurrentImportance;
716 private final int mNewImportance;
717
718
719 public UpdateImportanceRunnable(INotificationManager notificationManager,
720 String packageName, int appUid, @Nullable NotificationChannel channelToUpdate,
721 int currentImportance, int newImportance) {
722 mINotificationManager = notificationManager;
723 mPackageName = packageName;
724 mAppUid = appUid;
725 mChannelToUpdate = channelToUpdate;
726 mCurrentImportance = currentImportance;
727 mNewImportance = newImportance;
728 }
729
730 @Override
731 public void run() {
732 try {
733 if (mChannelToUpdate != null) {
734 mChannelToUpdate.setImportance(mNewImportance);
735 mChannelToUpdate.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
736 mINotificationManager.updateNotificationChannelForPackage(
737 mPackageName, mAppUid, mChannelToUpdate);
738 } else {
739 // For notifications with more than one channel, update notification enabled
740 // state. If the importance was lowered, we disable notifications.
Rohan Shah590e1b22018-04-10 23:48:47 -0400741 mINotificationManager.setNotificationsEnabledWithImportanceLockForPackage(
Rohan Shahca0447e2018-03-30 15:18:27 -0700742 mPackageName, mAppUid, mNewImportance >= mCurrentImportance);
743 }
744 } catch (RemoteException e) {
745 Log.e(TAG, "Unable to update notification importance", e);
746 }
747 }
748 }
Eyal Posenera9cf9c72018-12-18 16:23:54 +0200749
750 private LogMaker getLogMaker() {
751 return mSbn.getLogMaker().setCategory(MetricsEvent.NOTIFICATION_ITEM);
752 }
Mady Mellor87d79452017-01-10 11:52:52 -0800753}