blob: b1eab80693071bc4b7029aa552bb0dfbd08f9061 [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);
126 };
Julia Reynolds437cdb12018-01-03 12:27:24 -0500127
Gus Prevas9abc5062018-10-31 16:11:04 -0400128 private OnClickListener mOnToggleSilent = v -> {
129 Runnable saveImportance = () -> {
Gus Prevas894d9152018-11-12 13:51:40 -0500130 swapContent(ACTION_TOGGLE_SILENT, true /* animate */);
Gus Prevas9abc5062018-10-31 16:11:04 -0400131 };
132 if (mCheckSaveListener != null) {
133 mCheckSaveListener.checkSave(saveImportance, mSbn);
134 } else {
135 saveImportance.run();
136 }
137 };
138
Rohan Shahca0447e2018-03-30 15:18:27 -0700139 private OnClickListener mOnStopOrMinimizeNotifications = v -> {
Gus Prevas533836a2018-09-24 17:15:32 -0400140 Runnable saveImportance = () -> {
Gus Prevas894d9152018-11-12 13:51:40 -0500141 swapContent(ACTION_BLOCK, true /* animate */);
Gus Prevas533836a2018-09-24 17:15:32 -0400142 };
143 if (mCheckSaveListener != null) {
144 mCheckSaveListener.checkSave(saveImportance, mSbn);
145 } else {
146 saveImportance.run();
147 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500148 };
149
150 private OnClickListener mOnUndo = v -> {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700151 // Reset exit counter that we'll log and record an undo event separately (not an exit event)
Rohan Shahdd588c72018-05-09 20:32:15 -0700152 mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700153 logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
William Brockman75cf66a2018-12-21 13:25:01 -0500154 mMetricsLogger.write(importanceChangeLogMaker().setType(MetricsEvent.TYPE_DISMISS));
Gus Prevas894d9152018-11-12 13:51:40 -0500155 swapContent(ACTION_UNDO, true /* animate */);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500156 };
157
Mady Mellor87d79452017-01-10 11:52:52 -0800158 public NotificationInfo(Context context, AttributeSet attrs) {
159 super(context, attrs);
160 }
161
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400162 // Specify a CheckSaveListener to override when/if the user's changes are committed.
163 public interface CheckSaveListener {
164 // Invoked when importance has changed and the NotificationInfo wants to try to save it.
165 // Listener should run saveImportance unless the change should be canceled.
Eliot Courtney47098cb2017-10-18 17:30:30 +0900166 void checkSave(Runnable saveImportance, StatusBarNotification sbn);
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400167 }
168
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500169 public interface OnSettingsClickListener {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500170 void onClick(View v, NotificationChannel channel, int appUid);
Mady Mellor87d79452017-01-10 11:52:52 -0800171 }
172
Julia Reynolds3aedded2017-03-31 14:42:09 -0400173 public interface OnAppSettingsClickListener {
174 void onClick(View v, Intent intent);
175 }
176
Rohan Shah524cf7b2018-03-15 14:40:02 -0700177 @VisibleForTesting
178 void bindNotification(
179 final PackageManager pm,
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500180 final INotificationManager iNotificationManager,
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500181 final String pkg,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500182 final NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700183 final int numUniqueChannelsInRow,
Julia Reynolds3aedded2017-03-31 14:42:09 -0400184 final StatusBarNotification sbn,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500185 final CheckSaveListener checkSaveListener,
186 final OnSettingsClickListener onSettingsClick,
187 final OnAppSettingsClickListener onAppSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400188 boolean isDeviceProvisioned,
Gus Prevas9abc5062018-10-31 16:11:04 -0400189 boolean isNonblockable,
Gus Prevas5a70a4e2018-11-26 17:16:05 -0500190 int importance)
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 Prevas82ad0592018-11-28 14:27:40 -0500195 false /* isBlockingHelper */, false /* isUserSentimentNegative */,
Gus Prevas5a70a4e2018-11-26 17:16:05 -0500196 importance);
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,
Gus Prevas5a70a4e2018-11-26 17:16:05 -0500213 int importance)
Rohan Shah524cf7b2018-03-15 14:40:02 -0700214 throws RemoteException {
Mady Mellor87d79452017-01-10 11:52:52 -0800215 mINotificationManager = iNotificationManager;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700216 mMetricsLogger = Dependency.get(MetricsLogger.class);
Rohan Shahca0447e2018-03-30 15:18:27 -0700217 mPackageName = pkg;
218 mNumUniqueChannelsInRow = numUniqueChannelsInRow;
Julia Reynolds3aedded2017-03-31 14:42:09 -0400219 mSbn = sbn;
220 mPm = pm;
221 mAppSettingsClickListener = onAppSettingsClick;
Rohan Shahca0447e2018-03-30 15:18:27 -0700222 mAppName = mPackageName;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500223 mCheckSaveListener = checkSaveListener;
224 mOnSettingsClickListener = onSettingsClick;
225 mSingleNotificationChannel = notificationChannel;
Gus Prevas9abc5062018-10-31 16:11:04 -0400226 int channelImportance = mSingleNotificationChannel.getImportance();
227 mStartingChannelImportance = mChosenImportance = channelImportance;
228 mStartingChannelOrNotificationImportance =
229 channelImportance == IMPORTANCE_UNSPECIFIED ? importance : channelImportance;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700230 mNegativeUserSentiment = isUserSentimentNegative;
Rohan Shah63411fc2018-03-28 19:05:52 -0700231 mIsNonblockable = isNonblockable;
Julia Reynoldse0341482018-03-08 14:42:50 -0500232 mIsForeground =
233 (mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700234 mIsForBlockingHelper = isForBlockingHelper;
Rohan Shahdbd64e72018-03-28 14:46:50 -0700235 mAppUid = mSbn.getUid();
Julia Reynolds268647a2018-10-25 16:54:27 -0400236 mDelegatePkg = mSbn.getOpPkg();
Julia Reynolds35765d82018-08-17 11:39:19 -0400237 mIsDeviceProvisioned = isDeviceProvisioned;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500238
Julia Reynolds437cdb12018-01-03 12:27:24 -0500239 int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400240 pkg, mAppUid, false /* includeDeleted */);
Rohan Shahca0447e2018-03-30 15:18:27 -0700241 if (mNumUniqueChannelsInRow == 0) {
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400242 throw new IllegalArgumentException("bindNotification requires at least one channel");
243 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500244 // Special behavior for the Default channel if no other channels have been defined.
Rohan Shahca0447e2018-03-30 15:18:27 -0700245 mIsSingleDefaultChannel = mNumUniqueChannelsInRow == 1
Rohan Shahdbd64e72018-03-28 14:46:50 -0700246 && mSingleNotificationChannel.getId().equals(
247 NotificationChannel.DEFAULT_CHANNEL_ID)
248 && numTotalChannels == 1;
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400249 }
250
Julia Reynolds437cdb12018-01-03 12:27:24 -0500251 bindHeader();
252 bindPrompt();
253 bindButtons();
254 }
255
256 private void bindHeader() throws RemoteException {
257 // Package name
258 Drawable pkgicon = null;
259 ApplicationInfo info;
260 try {
Rohan Shahca0447e2018-03-30 15:18:27 -0700261 info = mPm.getApplicationInfo(
262 mPackageName,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500263 PackageManager.MATCH_UNINSTALLED_PACKAGES
264 | PackageManager.MATCH_DISABLED_COMPONENTS
265 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
266 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
267 if (info != null) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500268 mAppName = String.valueOf(mPm.getApplicationLabel(info));
269 pkgicon = mPm.getApplicationIcon(info);
Geoffrey Pitschee8c81e2017-03-23 11:38:56 -0400270 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500271 } catch (PackageManager.NameNotFoundException e) {
272 // app is gone, just show package name and generic icon
273 pkgicon = mPm.getDefaultActivityIcon();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500274 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500275 ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400276 ((TextView) findViewById(R.id.pkgname)).setText(mAppName);
Mady Mellor87d79452017-01-10 11:52:52 -0800277
Julia Reynolds268647a2018-10-25 16:54:27 -0400278 // Delegate
279 bindDelegate();
Daniel Sandlerd0a52b62018-10-31 20:13:22 +0000280
281 // Settings button.
282 final View settingsButton = findViewById(R.id.info);
283 if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
284 settingsButton.setVisibility(View.VISIBLE);
285 final int appUidF = mAppUid;
286 settingsButton.setOnClickListener(
287 (View view) -> {
288 logBlockingHelperCounter(
289 NotificationCounters.BLOCKING_HELPER_NOTIF_SETTINGS);
290 mOnSettingsClickListener.onClick(view,
291 mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel,
292 appUidF);
293 });
294 } else {
295 settingsButton.setVisibility(View.GONE);
296 }
297 }
298
Julia Reynolds268647a2018-10-25 16:54:27 -0400299 private void bindPrompt() throws RemoteException {
Daniel Sandlerd0a52b62018-10-31 20:13:22 +0000300 final TextView blockPrompt = findViewById(R.id.block_prompt);
301 bindName();
Julia Reynolds268647a2018-10-25 16:54:27 -0400302 bindGroup();
Daniel Sandlerd0a52b62018-10-31 20:13:22 +0000303 if (mIsNonblockable) {
304 blockPrompt.setText(R.string.notification_unblockable_desc);
305 } else {
306 if (mNegativeUserSentiment) {
307 blockPrompt.setText(R.string.inline_blocking_helper);
308 } else if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
309 blockPrompt.setText(R.string.inline_keep_showing_app);
310 } else {
311 blockPrompt.setText(R.string.inline_keep_showing);
312 }
313 }
314 }
315
316 private void bindName() {
317 final TextView channelName = findViewById(R.id.channel_name);
318 if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
319 channelName.setVisibility(View.GONE);
320 } else {
321 channelName.setText(mSingleNotificationChannel.getName());
322 }
Julia Reynoldsac98aea2018-10-25 16:54:27 -0400323 }
324
Julia Reynolds268647a2018-10-25 16:54:27 -0400325 private void bindDelegate() {
326 TextView delegateView = findViewById(R.id.delegate_name);
327 TextView dividerView = findViewById(R.id.pkg_divider);
328
329 CharSequence delegatePkg = null;
330 if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
331 // this notification was posted by a delegate!
332 ApplicationInfo info;
333 try {
334 info = mPm.getApplicationInfo(
335 mDelegatePkg,
336 PackageManager.MATCH_UNINSTALLED_PACKAGES
337 | PackageManager.MATCH_DISABLED_COMPONENTS
338 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
339 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
340 if (info != null) {
341 delegatePkg = String.valueOf(mPm.getApplicationLabel(info));
342 }
343 } catch (PackageManager.NameNotFoundException e) { }
344 }
345 if (delegatePkg != null) {
346 delegateView.setText(mContext.getResources().getString(
347 R.string.notification_delegate_header, delegatePkg));
348 delegateView.setVisibility(View.VISIBLE);
349 dividerView.setVisibility(View.VISIBLE);
350 } else {
351 delegateView.setVisibility(View.GONE);
352 dividerView.setVisibility(View.GONE);
353 }
354 }
355
356 private void bindGroup() throws RemoteException {
357 // Set group information if this channel has an associated group.
358 CharSequence groupName = null;
359 if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
360 final NotificationChannelGroup notificationChannelGroup =
361 mINotificationManager.getNotificationChannelGroupForPackage(
362 mSingleNotificationChannel.getGroup(), mPackageName, mAppUid);
363 if (notificationChannelGroup != null) {
364 groupName = notificationChannelGroup.getName();
365 }
366 }
367 TextView groupNameView = findViewById(R.id.group_name);
368 TextView groupDividerView = findViewById(R.id.pkg_group_divider);
369 if (groupName != null) {
370 groupNameView.setText(groupName);
371 groupNameView.setVisibility(View.VISIBLE);
372 groupDividerView.setVisibility(View.VISIBLE);
373 } else {
374 groupNameView.setVisibility(View.GONE);
375 groupDividerView.setVisibility(View.GONE);
376 }
377 }
378
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700379 @VisibleForTesting
380 void logBlockingHelperCounter(String counterTag) {
381 if (mIsForBlockingHelper) {
382 mMetricsLogger.count(counterTag, 1);
383 }
384 }
385
William Brockman75cf66a2018-12-21 13:25:01 -0500386 /**
387 * Returns an initialized LogMaker for logging importance changes.
388 * The caller may override the type (to DISMISS) before passing it to mMetricsLogger.
389 * @return new LogMaker
390 */
391 private LogMaker importanceChangeLogMaker() {
392 return new LogMaker(MetricsEvent.ACTION_SAVE_IMPORTANCE)
393 .setType(MetricsEvent.TYPE_ACTION)
394 .setSubtype(mChosenImportance - mStartingChannelImportance);
395 }
396
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400397 private boolean hasImportanceChanged() {
Gus Prevas9abc5062018-10-31 16:11:04 -0400398 return mSingleNotificationChannel != null
399 && mStartingChannelImportance != mChosenImportance;
Mady Mellor87d79452017-01-10 11:52:52 -0800400 }
401
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500402 private void saveImportance() {
Gus Prevas894d9152018-11-12 13:51:40 -0500403 if (!mIsNonblockable
404 || mExitReason != NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS) {
Gus Prevas533836a2018-09-24 17:15:32 -0400405 updateImportance();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500406 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700407 }
408
409 /**
410 * Commits the updated importance values on the background thread.
411 */
412 private void updateImportance() {
William Brockman75cf66a2018-12-21 13:25:01 -0500413 mMetricsLogger.write(importanceChangeLogMaker());
Rohan Shahca0447e2018-03-30 15:18:27 -0700414
415 Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
416 bgHandler.post(new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
417 mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
Gus Prevas9abc5062018-10-31 16:11:04 -0400418 mStartingChannelImportance, mChosenImportance));
Mady Mellor87d79452017-01-10 11:52:52 -0800419 }
420
Julia Reynolds437cdb12018-01-03 12:27:24 -0500421 private void bindButtons() {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700422 findViewById(R.id.undo).setOnClickListener(mOnUndo);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500423
Gus Prevas82ad0592018-11-28 14:27:40 -0500424 boolean showInterruptivenessSettings =
425 !mIsNonblockable
426 && !mIsForeground
427 && !mIsForBlockingHelper
428 && NotificationUtils.useNewInterruptionModel(mContext);
429 if (showInterruptivenessSettings) {
430 findViewById(R.id.block_or_minimize).setVisibility(GONE);
431 findViewById(R.id.interruptiveness_settings).setVisibility(VISIBLE);
432 View block = findViewById(R.id.int_block);
433 TextView silent = findViewById(R.id.int_silent);
434 TextView alert = findViewById(R.id.int_alert);
435
Gus Prevas9abc5062018-10-31 16:11:04 -0400436 boolean isCurrentlyAlerting =
437 mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500438
Gus Prevas82ad0592018-11-28 14:27:40 -0500439 block.setOnClickListener(mOnStopOrMinimizeNotifications);
440 if (isCurrentlyAlerting) {
441 silent.setOnClickListener(mOnToggleSilent);
442 silent.setText(R.string.inline_silent_button_silent);
443 alert.setOnClickListener(mOnKeepShowing);
444 alert.setText(R.string.inline_silent_button_keep_alerting);
445 } else {
446 silent.setOnClickListener(mOnKeepShowing);
447 silent.setText(R.string.inline_silent_button_stay_silent);
448 alert.setOnClickListener(mOnToggleSilent);
449 alert.setText(R.string.inline_silent_button_alert);
450 }
Mady Mellor87d79452017-01-10 11:52:52 -0800451 } else {
Gus Prevas82ad0592018-11-28 14:27:40 -0500452 findViewById(R.id.block_or_minimize).setVisibility(VISIBLE);
453 findViewById(R.id.interruptiveness_settings).setVisibility(GONE);
454 View block = findViewById(R.id.block);
455 TextView keep = findViewById(R.id.keep);
456 View minimize = findViewById(R.id.minimize);
457
458 block.setOnClickListener(mOnStopOrMinimizeNotifications);
459 keep.setOnClickListener(mOnKeepShowing);
460 minimize.setOnClickListener(mOnStopOrMinimizeNotifications);
461
462 if (mIsNonblockable) {
463 keep.setText(android.R.string.ok);
464 block.setVisibility(GONE);
465 minimize.setVisibility(GONE);
466 } else if (mIsForeground) {
467 block.setVisibility(GONE);
468 minimize.setVisibility(VISIBLE);
469 } else {
470 block.setVisibility(VISIBLE);
471 minimize.setVisibility(GONE);
472 }
473
474 // Set up app settings link (i.e. Customize)
475 TextView settingsLinkView = findViewById(R.id.app_settings);
476 Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName,
477 mSingleNotificationChannel,
478 mSbn.getId(), mSbn.getTag());
479 if (!mIsForBlockingHelper
480 && settingsIntent != null
481 && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
482 settingsLinkView.setVisibility(VISIBLE);
483 settingsLinkView.setText(mContext.getString(R.string.notification_app_settings));
484 settingsLinkView.setOnClickListener((View view) -> {
485 mAppSettingsClickListener.onClick(view, settingsIntent);
486 });
487 } else {
488 settingsLinkView.setVisibility(View.GONE);
489 }
Mady Mellor87d79452017-01-10 11:52:52 -0800490 }
491 }
492
Gus Prevas894d9152018-11-12 13:51:40 -0500493 private void swapContent(@NotificationInfoAction int action, boolean animate) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500494 if (mExpandAnimation != null) {
495 mExpandAnimation.cancel();
496 }
Mady Mellor87d79452017-01-10 11:52:52 -0800497
Julia Reynoldse0341482018-03-08 14:42:50 -0500498 View prompt = findViewById(R.id.prompt);
499 ViewGroup confirmation = findViewById(R.id.confirmation);
500 TextView confirmationText = findViewById(R.id.confirmation_text);
501 View header = findViewById(R.id.header);
502
Gus Prevas9abc5062018-10-31 16:11:04 -0400503 switch (action) {
Gus Prevas894d9152018-11-12 13:51:40 -0500504 case ACTION_UNDO:
Gus Prevas9abc5062018-10-31 16:11:04 -0400505 mChosenImportance = mStartingChannelImportance;
506 break;
Gus Prevas894d9152018-11-12 13:51:40 -0500507 case ACTION_TOGGLE_SILENT:
508 mExitReason = NotificationCounters.BLOCKING_HELPER_TOGGLE_SILENT;
Gus Prevas9abc5062018-10-31 16:11:04 -0400509 if (mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT) {
510 mChosenImportance = IMPORTANCE_LOW;
511 confirmationText.setText(R.string.notification_channel_silenced);
512 } else {
513 mChosenImportance = IMPORTANCE_HIGH;
514 confirmationText.setText(R.string.notification_channel_unsilenced);
515 }
516 break;
Gus Prevas894d9152018-11-12 13:51:40 -0500517 case ACTION_BLOCK:
518 mExitReason = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS;
Gus Prevas9abc5062018-10-31 16:11:04 -0400519 if (mIsForeground) {
520 mChosenImportance = IMPORTANCE_MIN;
521 confirmationText.setText(R.string.notification_channel_minimized);
522 } else {
523 mChosenImportance = IMPORTANCE_NONE;
524 confirmationText.setText(R.string.notification_channel_disabled);
525 }
526 break;
527 default:
528 throw new IllegalArgumentException();
Julia Reynolds437cdb12018-01-03 12:27:24 -0500529 }
530
Gus Prevas894d9152018-11-12 13:51:40 -0500531 boolean isUndo = action == ACTION_UNDO;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500532
Gus Prevas9abc5062018-10-31 16:11:04 -0400533 prompt.setVisibility(isUndo ? VISIBLE : GONE);
534 confirmation.setVisibility(isUndo ? GONE : VISIBLE);
535 header.setVisibility(isUndo ? VISIBLE : GONE);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500536
Gus Prevas894d9152018-11-12 13:51:40 -0500537 if (animate) {
538 ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA,
539 prompt.getAlpha(), isUndo ? 1f : 0f);
540 promptAnim.setInterpolator(isUndo ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
541 ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA,
542 confirmation.getAlpha(), isUndo ? 0f : 1f);
543 confirmAnim.setInterpolator(isUndo ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500544
Gus Prevas894d9152018-11-12 13:51:40 -0500545 mExpandAnimation = new AnimatorSet();
546 mExpandAnimation.playTogether(promptAnim, confirmAnim);
547 mExpandAnimation.setDuration(150);
548 mExpandAnimation.addListener(new AnimatorListenerAdapter() {
549 boolean mCancelled = false;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500550
Gus Prevas894d9152018-11-12 13:51:40 -0500551 @Override
552 public void onAnimationCancel(Animator animation) {
553 mCancelled = true;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500554 }
Gus Prevas894d9152018-11-12 13:51:40 -0500555
556 @Override
557 public void onAnimationEnd(Animator animation) {
558 if (!mCancelled) {
559 prompt.setVisibility(isUndo ? VISIBLE : GONE);
560 confirmation.setVisibility(isUndo ? GONE : VISIBLE);
561 }
562 }
563 });
564 mExpandAnimation.start();
565 }
Rohan Shah142e2da2018-06-14 13:14:18 -0700566
567 // Since we're swapping/update the content, reset the timeout so the UI can't close
568 // immediately after the update.
569 if (mGutsContainer != null) {
570 mGutsContainer.resetFalsingCheck();
571 }
Mady Mellor87d79452017-01-10 11:52:52 -0800572 }
573
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400574 @Override
Gus Prevas9abc5062018-10-31 16:11:04 -0400575 public void onFinishedClosing() {
576 mStartingChannelImportance = mChosenImportance;
577 if (mChosenImportance != IMPORTANCE_UNSPECIFIED) {
578 mStartingChannelOrNotificationImportance = mChosenImportance;
579 }
580 mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
581
582 View prompt = findViewById(R.id.prompt);
583 ViewGroup confirmation = findViewById(R.id.confirmation);
584 View header = findViewById(R.id.header);
585 prompt.setVisibility(VISIBLE);
586 prompt.setAlpha(1f);
587 confirmation.setVisibility(GONE);
588 confirmation.setAlpha(1f);
589 header.setVisibility(VISIBLE);
590 header.setAlpha(1f);
591 }
592
593 @Override
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400594 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
595 super.onInitializeAccessibilityEvent(event);
596 if (mGutsContainer != null &&
597 event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
598 if (mGutsContainer.isExposed()) {
599 event.getText().add(mContext.getString(
600 R.string.notification_channel_controls_opened_accessibility, mAppName));
601 } else {
602 event.getText().add(mContext.getString(
603 R.string.notification_channel_controls_closed_accessibility, mAppName));
604 }
605 }
606 }
607
Julia Reynolds3aedded2017-03-31 14:42:09 -0400608 private Intent getAppSettingsIntent(PackageManager pm, String packageName,
609 NotificationChannel channel, int id, String tag) {
610 Intent intent = new Intent(Intent.ACTION_MAIN)
611 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
612 .setPackage(packageName);
613 final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
614 intent,
615 PackageManager.MATCH_DEFAULT_ONLY
616 );
617 if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
618 return null;
619 }
620 final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
621 intent.setClassName(activityInfo.packageName, activityInfo.name);
622 if (channel != null) {
623 intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
624 }
625 intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
626 intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
627 return intent;
628 }
629
Rohan Shahca0447e2018-03-30 15:18:27 -0700630 /**
631 * Closes the controls and commits the updated importance values (indirectly). If this view is
632 * being used to show the blocking helper, this will immediately dismiss the blocking helper and
633 * commit the updated importance.
634 *
635 * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
636 * user does not have the ability to undo the action anymore. See {@link #swapContent(boolean)}
637 * for where undo is handled.
638 */
Rohan Shah524cf7b2018-03-15 14:40:02 -0700639 @VisibleForTesting
640 void closeControls(View v) {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700641 int[] parentLoc = new int[2];
642 int[] targetLoc = new int[2];
643 mGutsContainer.getLocationOnScreen(parentLoc);
644 v.getLocationOnScreen(targetLoc);
645 final int centerX = v.getWidth() / 2;
646 final int centerY = v.getHeight() / 2;
647 final int x = targetLoc[0] - parentLoc[0] + centerX;
648 final int y = targetLoc[1] - parentLoc[1] + centerY;
649 mGutsContainer.closeControls(x, y, true /* save */, false /* force */);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500650 }
651
Mady Mellor87d79452017-01-10 11:52:52 -0800652 @Override
Mady Mellor95d743c2017-01-10 12:05:27 -0800653 public void setGutsParent(NotificationGuts guts) {
654 mGutsContainer = guts;
Mady Mellor87d79452017-01-10 11:52:52 -0800655 }
656
657 @Override
Mady Mellor434180c2017-02-13 11:29:42 -0800658 public boolean willBeRemoved() {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500659 return hasImportanceChanged();
Mady Mellor434180c2017-02-13 11:29:42 -0800660 }
661
662 @Override
Lucas Dupin9b08c012018-05-16 19:53:32 -0700663 public boolean shouldBeSaved() {
664 return hasImportanceChanged();
665 }
666
667 @Override
Mady Mellor87d79452017-01-10 11:52:52 -0800668 public View getContentView() {
669 return this;
670 }
671
672 @Override
Mady Mellorc2dbe492017-03-30 13:22:03 -0700673 public boolean handleCloseControls(boolean save, boolean force) {
Julia Reynoldsc65656a2018-02-12 09:55:14 -0500674 // Save regardless of the importance so we can lock the importance field if the user wants
675 // to keep getting notifications
676 if (save) {
Rohan Shahca0447e2018-03-30 15:18:27 -0700677 saveImportance();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500678 }
Rohan Shahdd588c72018-05-09 20:32:15 -0700679 logBlockingHelperCounter(mExitReason);
Mady Mellor87d79452017-01-10 11:52:52 -0800680 return false;
681 }
Mady Mellore09fb702017-03-30 13:23:29 -0700682
683 @Override
684 public int getActualHeight() {
685 return getHeight();
686 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700687
Gus Prevas533836a2018-09-24 17:15:32 -0400688 @VisibleForTesting
689 public boolean isAnimating() {
690 return mExpandAnimation != null && mExpandAnimation.isRunning();
691 }
692
Rohan Shahca0447e2018-03-30 15:18:27 -0700693 /**
694 * Runnable to either update the given channel (with a new importance value) or, if no channel
695 * is provided, update notifications enabled state for the package.
696 */
697 private static class UpdateImportanceRunnable implements Runnable {
698 private final INotificationManager mINotificationManager;
699 private final String mPackageName;
700 private final int mAppUid;
701 private final @Nullable NotificationChannel mChannelToUpdate;
702 private final int mCurrentImportance;
703 private final int mNewImportance;
704
705
706 public UpdateImportanceRunnable(INotificationManager notificationManager,
707 String packageName, int appUid, @Nullable NotificationChannel channelToUpdate,
708 int currentImportance, int newImportance) {
709 mINotificationManager = notificationManager;
710 mPackageName = packageName;
711 mAppUid = appUid;
712 mChannelToUpdate = channelToUpdate;
713 mCurrentImportance = currentImportance;
714 mNewImportance = newImportance;
715 }
716
717 @Override
718 public void run() {
719 try {
720 if (mChannelToUpdate != null) {
721 mChannelToUpdate.setImportance(mNewImportance);
722 mChannelToUpdate.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
723 mINotificationManager.updateNotificationChannelForPackage(
724 mPackageName, mAppUid, mChannelToUpdate);
725 } else {
726 // For notifications with more than one channel, update notification enabled
727 // state. If the importance was lowered, we disable notifications.
Rohan Shah590e1b22018-04-10 23:48:47 -0400728 mINotificationManager.setNotificationsEnabledWithImportanceLockForPackage(
Rohan Shahca0447e2018-03-30 15:18:27 -0700729 mPackageName, mAppUid, mNewImportance >= mCurrentImportance);
730 }
731 } catch (RemoteException e) {
732 Log.e(TAG, "Unable to update notification importance", e);
733 }
734 }
735 }
Mady Mellor87d79452017-01-10 11:52:52 -0800736}