blob: 522da4d51470575f6d37d8f99261a0a32da4d2e5 [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 Prevas9abc5062018-10-31 16:11:04 -040074 @IntDef(prefix = { "SWAP_CONTENT_" }, value = {
75 SWAP_CONTENT_UNDO,
76 SWAP_CONTENT_TOGGLE_SILENT,
77 SWAP_CONTENT_BLOCK,
78 })
79 @interface SwapContentAction {}
80
81 private static final int SWAP_CONTENT_UNDO = 0;
82 private static final int SWAP_CONTENT_TOGGLE_SILENT = 1;
83 private static final int SWAP_CONTENT_BLOCK = 2;
84
Mady Mellor87d79452017-01-10 11:52:52 -080085 private INotificationManager mINotificationManager;
Julia Reynolds437cdb12018-01-03 12:27:24 -050086 private PackageManager mPm;
Rohan Shahda5dcdd2018-04-27 17:21:50 -070087 private MetricsLogger mMetricsLogger;
Julia Reynolds437cdb12018-01-03 12:27:24 -050088
Rohan Shahca0447e2018-03-30 15:18:27 -070089 private String mPackageName;
Geoffrey Pitschd94e7882017-04-06 09:52:11 -040090 private String mAppName;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050091 private int mAppUid;
Rohan Shahca0447e2018-03-30 15:18:27 -070092 private int mNumUniqueChannelsInRow;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050093 private NotificationChannel mSingleNotificationChannel;
Gus Prevas9abc5062018-10-31 16:11:04 -040094 private int mStartingChannelImportance;
95 private int mStartingChannelOrNotificationImportance;
Julia Reynolds437cdb12018-01-03 12:27:24 -050096 private int mChosenImportance;
97 private boolean mIsSingleDefaultChannel;
Rohan Shah63411fc2018-03-28 19:05:52 -070098 private boolean mIsNonblockable;
Julia Reynolds437cdb12018-01-03 12:27:24 -050099 private StatusBarNotification mSbn;
100 private AnimatorSet mExpandAnimation;
Julia Reynoldse0341482018-03-08 14:42:50 -0500101 private boolean mIsForeground;
Julia Reynolds35765d82018-08-17 11:39:19 -0400102 private boolean mIsDeviceProvisioned;
Gus Prevas9abc5062018-10-31 16:11:04 -0400103 private boolean mIsNoisy;
Mady Mellor87d79452017-01-10 11:52:52 -0800104
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400105 private CheckSaveListener mCheckSaveListener;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500106 private OnSettingsClickListener mOnSettingsClickListener;
Julia Reynolds3aedded2017-03-31 14:42:09 -0400107 private OnAppSettingsClickListener mAppSettingsClickListener;
Mady Mellor95d743c2017-01-10 12:05:27 -0800108 private NotificationGuts mGutsContainer;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700109
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700110 /** Whether this view is being shown as part of the blocking helper. */
Rohan Shah524cf7b2018-03-15 14:40:02 -0700111 private boolean mIsForBlockingHelper;
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500112 private boolean mNegativeUserSentiment;
Mady Mellor87d79452017-01-10 11:52:52 -0800113
Rohan Shahdd588c72018-05-09 20:32:15 -0700114 /**
115 * String that describes how the user exit or quit out of this view, also used as a counter tag.
116 */
117 private String mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700118
119 private OnClickListener mOnKeepShowing = v -> {
Rohan Shahdd588c72018-05-09 20:32:15 -0700120 mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700121 closeControls(v);
122 };
Julia Reynolds437cdb12018-01-03 12:27:24 -0500123
Gus Prevas9abc5062018-10-31 16:11:04 -0400124 private OnClickListener mOnToggleSilent = v -> {
125 Runnable saveImportance = () -> {
126 mExitReason = NotificationCounters.BLOCKING_HELPER_TOGGLE_SILENT;
127 swapContent(SWAP_CONTENT_TOGGLE_SILENT);
128 };
129 if (mCheckSaveListener != null) {
130 mCheckSaveListener.checkSave(saveImportance, mSbn);
131 } else {
132 saveImportance.run();
133 }
134 };
135
Rohan Shahca0447e2018-03-30 15:18:27 -0700136 private OnClickListener mOnStopOrMinimizeNotifications = v -> {
Gus Prevas533836a2018-09-24 17:15:32 -0400137 Runnable saveImportance = () -> {
138 mExitReason = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS;
Gus Prevas9abc5062018-10-31 16:11:04 -0400139 swapContent(SWAP_CONTENT_BLOCK);
Gus Prevas533836a2018-09-24 17:15:32 -0400140 };
141 if (mCheckSaveListener != null) {
142 mCheckSaveListener.checkSave(saveImportance, mSbn);
143 } else {
144 saveImportance.run();
145 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500146 };
147
148 private OnClickListener mOnUndo = v -> {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700149 // Reset exit counter that we'll log and record an undo event separately (not an exit event)
Rohan Shahdd588c72018-05-09 20:32:15 -0700150 mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700151 logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
Gus Prevas9abc5062018-10-31 16:11:04 -0400152 swapContent(SWAP_CONTENT_UNDO);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500153 };
154
Mady Mellor87d79452017-01-10 11:52:52 -0800155 public NotificationInfo(Context context, AttributeSet attrs) {
156 super(context, attrs);
157 }
158
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400159 // Specify a CheckSaveListener to override when/if the user's changes are committed.
160 public interface CheckSaveListener {
161 // Invoked when importance has changed and the NotificationInfo wants to try to save it.
162 // Listener should run saveImportance unless the change should be canceled.
Eliot Courtney47098cb2017-10-18 17:30:30 +0900163 void checkSave(Runnable saveImportance, StatusBarNotification sbn);
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400164 }
165
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500166 public interface OnSettingsClickListener {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500167 void onClick(View v, NotificationChannel channel, int appUid);
Mady Mellor87d79452017-01-10 11:52:52 -0800168 }
169
Julia Reynolds3aedded2017-03-31 14:42:09 -0400170 public interface OnAppSettingsClickListener {
171 void onClick(View v, Intent intent);
172 }
173
Rohan Shah524cf7b2018-03-15 14:40:02 -0700174 @VisibleForTesting
175 void bindNotification(
176 final PackageManager pm,
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500177 final INotificationManager iNotificationManager,
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500178 final String pkg,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500179 final NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700180 final int numUniqueChannelsInRow,
Julia Reynolds3aedded2017-03-31 14:42:09 -0400181 final StatusBarNotification sbn,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500182 final CheckSaveListener checkSaveListener,
183 final OnSettingsClickListener onSettingsClick,
184 final OnAppSettingsClickListener onAppSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400185 boolean isDeviceProvisioned,
Gus Prevas9abc5062018-10-31 16:11:04 -0400186 boolean isNonblockable,
187 boolean isNoisy,
188 int importance)
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500189 throws RemoteException {
Rohan Shahca0447e2018-03-30 15:18:27 -0700190 bindNotification(pm, iNotificationManager, pkg, notificationChannel,
191 numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400192 onAppSettingsClick, isDeviceProvisioned, isNonblockable,
Gus Prevas9abc5062018-10-31 16:11:04 -0400193 false /* isBlockingHelper */, false /* isUserSentimentNegative */, isNoisy,
194 importance);
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500195 }
196
Rohan Shah524cf7b2018-03-15 14:40:02 -0700197 public void bindNotification(
198 PackageManager pm,
199 INotificationManager iNotificationManager,
200 String pkg,
201 NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700202 int numUniqueChannelsInRow,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700203 StatusBarNotification sbn,
204 CheckSaveListener checkSaveListener,
205 OnSettingsClickListener onSettingsClick,
206 OnAppSettingsClickListener onAppSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400207 boolean isDeviceProvisioned,
Rohan Shah63411fc2018-03-28 19:05:52 -0700208 boolean isNonblockable,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700209 boolean isForBlockingHelper,
Gus Prevas9abc5062018-10-31 16:11:04 -0400210 boolean isUserSentimentNegative,
211 boolean isNoisy,
212 int importance)
Rohan Shah524cf7b2018-03-15 14:40:02 -0700213 throws RemoteException {
Mady Mellor87d79452017-01-10 11:52:52 -0800214 mINotificationManager = iNotificationManager;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700215 mMetricsLogger = Dependency.get(MetricsLogger.class);
Rohan Shahca0447e2018-03-30 15:18:27 -0700216 mPackageName = pkg;
217 mNumUniqueChannelsInRow = numUniqueChannelsInRow;
Julia Reynolds3aedded2017-03-31 14:42:09 -0400218 mSbn = sbn;
219 mPm = pm;
220 mAppSettingsClickListener = onAppSettingsClick;
Rohan Shahca0447e2018-03-30 15:18:27 -0700221 mAppName = mPackageName;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500222 mCheckSaveListener = checkSaveListener;
223 mOnSettingsClickListener = onSettingsClick;
224 mSingleNotificationChannel = notificationChannel;
Gus Prevas9abc5062018-10-31 16:11:04 -0400225 int channelImportance = mSingleNotificationChannel.getImportance();
226 mStartingChannelImportance = mChosenImportance = channelImportance;
227 mStartingChannelOrNotificationImportance =
228 channelImportance == IMPORTANCE_UNSPECIFIED ? importance : channelImportance;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700229 mNegativeUserSentiment = isUserSentimentNegative;
Rohan Shah63411fc2018-03-28 19:05:52 -0700230 mIsNonblockable = isNonblockable;
Julia Reynoldse0341482018-03-08 14:42:50 -0500231 mIsForeground =
232 (mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700233 mIsForBlockingHelper = isForBlockingHelper;
Rohan Shahdbd64e72018-03-28 14:46:50 -0700234 mAppUid = mSbn.getUid();
Julia Reynolds35765d82018-08-17 11:39:19 -0400235 mIsDeviceProvisioned = isDeviceProvisioned;
Gus Prevas9abc5062018-10-31 16:11:04 -0400236 mIsNoisy = isNoisy;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500237
Julia Reynolds437cdb12018-01-03 12:27:24 -0500238 int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400239 pkg, mAppUid, false /* includeDeleted */);
Rohan Shahca0447e2018-03-30 15:18:27 -0700240 if (mNumUniqueChannelsInRow == 0) {
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400241 throw new IllegalArgumentException("bindNotification requires at least one channel");
242 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500243 // Special behavior for the Default channel if no other channels have been defined.
Rohan Shahca0447e2018-03-30 15:18:27 -0700244 mIsSingleDefaultChannel = mNumUniqueChannelsInRow == 1
Rohan Shahdbd64e72018-03-28 14:46:50 -0700245 && mSingleNotificationChannel.getId().equals(
246 NotificationChannel.DEFAULT_CHANNEL_ID)
247 && numTotalChannels == 1;
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400248 }
249
Julia Reynolds437cdb12018-01-03 12:27:24 -0500250 bindHeader();
251 bindPrompt();
252 bindButtons();
253 }
254
255 private void bindHeader() throws RemoteException {
256 // Package name
257 Drawable pkgicon = null;
258 ApplicationInfo info;
259 try {
Rohan Shahca0447e2018-03-30 15:18:27 -0700260 info = mPm.getApplicationInfo(
261 mPackageName,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500262 PackageManager.MATCH_UNINSTALLED_PACKAGES
263 | PackageManager.MATCH_DISABLED_COMPONENTS
264 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
265 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
266 if (info != null) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500267 mAppName = String.valueOf(mPm.getApplicationLabel(info));
268 pkgicon = mPm.getApplicationIcon(info);
Geoffrey Pitschee8c81e2017-03-23 11:38:56 -0400269 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500270 } catch (PackageManager.NameNotFoundException e) {
271 // app is gone, just show package name and generic icon
272 pkgicon = mPm.getDefaultActivityIcon();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500273 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500274 ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400275 ((TextView) findViewById(R.id.pkgname)).setText(mAppName);
Mady Mellor87d79452017-01-10 11:52:52 -0800276
Julia Reynoldsac98aea2018-10-25 16:54:27 -0400277 // Set group information if this channel has an associated group.
278 CharSequence groupName = null;
279 if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
280 final NotificationChannelGroup notificationChannelGroup =
281 mINotificationManager.getNotificationChannelGroupForPackage(
282 mSingleNotificationChannel.getGroup(), mPackageName, mAppUid);
283 if (notificationChannelGroup != null) {
284 groupName = notificationChannelGroup.getName();
285 }
286 }
287 TextView groupNameView = findViewById(R.id.group_name);
288 TextView groupDividerView = findViewById(R.id.pkg_group_divider);
289 if (groupName != null) {
290 groupNameView.setText(groupName);
291 groupNameView.setVisibility(View.VISIBLE);
292 groupDividerView.setVisibility(View.VISIBLE);
293 } else {
294 groupNameView.setVisibility(View.GONE);
295 groupDividerView.setVisibility(View.GONE);
296 }
Daniel Sandlerd0a52b62018-10-31 20:13:22 +0000297
298 // Settings button.
299 final View settingsButton = findViewById(R.id.info);
300 if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
301 settingsButton.setVisibility(View.VISIBLE);
302 final int appUidF = mAppUid;
303 settingsButton.setOnClickListener(
304 (View view) -> {
305 logBlockingHelperCounter(
306 NotificationCounters.BLOCKING_HELPER_NOTIF_SETTINGS);
307 mOnSettingsClickListener.onClick(view,
308 mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel,
309 appUidF);
310 });
311 } else {
312 settingsButton.setVisibility(View.GONE);
313 }
314 }
315
316 private void bindPrompt() {
317 final TextView blockPrompt = findViewById(R.id.block_prompt);
318 bindName();
319 if (mIsNonblockable) {
320 blockPrompt.setText(R.string.notification_unblockable_desc);
321 } else {
322 if (mNegativeUserSentiment) {
323 blockPrompt.setText(R.string.inline_blocking_helper);
324 } else if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
325 blockPrompt.setText(R.string.inline_keep_showing_app);
326 } else {
327 blockPrompt.setText(R.string.inline_keep_showing);
328 }
329 }
330 }
331
332 private void bindName() {
333 final TextView channelName = findViewById(R.id.channel_name);
334 if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
335 channelName.setVisibility(View.GONE);
336 } else {
337 channelName.setText(mSingleNotificationChannel.getName());
338 }
Julia Reynoldsac98aea2018-10-25 16:54:27 -0400339 }
340
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700341 @VisibleForTesting
342 void logBlockingHelperCounter(String counterTag) {
343 if (mIsForBlockingHelper) {
344 mMetricsLogger.count(counterTag, 1);
345 }
346 }
347
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400348 private boolean hasImportanceChanged() {
Gus Prevas9abc5062018-10-31 16:11:04 -0400349 return mSingleNotificationChannel != null
350 && mStartingChannelImportance != mChosenImportance;
Mady Mellor87d79452017-01-10 11:52:52 -0800351 }
352
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500353 private void saveImportance() {
Rohan Shahca0447e2018-03-30 15:18:27 -0700354 if (!mIsNonblockable) {
Gus Prevas533836a2018-09-24 17:15:32 -0400355 updateImportance();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500356 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700357 }
358
359 /**
360 * Commits the updated importance values on the background thread.
361 */
362 private void updateImportance() {
Mady Mellor87d79452017-01-10 11:52:52 -0800363 MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
Gus Prevas9abc5062018-10-31 16:11:04 -0400364 mChosenImportance - mStartingChannelImportance);
Rohan Shahca0447e2018-03-30 15:18:27 -0700365
366 Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
367 bgHandler.post(new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
368 mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
Gus Prevas9abc5062018-10-31 16:11:04 -0400369 mStartingChannelImportance, mChosenImportance));
Mady Mellor87d79452017-01-10 11:52:52 -0800370 }
371
Julia Reynolds437cdb12018-01-03 12:27:24 -0500372 private void bindButtons() {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700373 // Set up stay-in-notification actions
Julia Reynolds437cdb12018-01-03 12:27:24 -0500374 View block = findViewById(R.id.block);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500375 TextView keep = findViewById(R.id.keep);
Gus Prevas9abc5062018-10-31 16:11:04 -0400376 TextView silent = findViewById(R.id.toggle_silent);
Julia Reynoldse0341482018-03-08 14:42:50 -0500377 View minimize = findViewById(R.id.minimize);
Rohan Shah524cf7b2018-03-15 14:40:02 -0700378
379 findViewById(R.id.undo).setOnClickListener(mOnUndo);
Rohan Shahca0447e2018-03-30 15:18:27 -0700380 block.setOnClickListener(mOnStopOrMinimizeNotifications);
Rohan Shah524cf7b2018-03-15 14:40:02 -0700381 keep.setOnClickListener(mOnKeepShowing);
Gus Prevas9abc5062018-10-31 16:11:04 -0400382 silent.setOnClickListener(mOnToggleSilent);
Rohan Shahca0447e2018-03-30 15:18:27 -0700383 minimize.setOnClickListener(mOnStopOrMinimizeNotifications);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500384
Rohan Shah63411fc2018-03-28 19:05:52 -0700385 if (mIsNonblockable) {
Rohan Shah53529aa2018-04-12 00:14:50 -0400386 keep.setText(android.R.string.ok);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500387 block.setVisibility(GONE);
Gus Prevas9abc5062018-10-31 16:11:04 -0400388 silent.setVisibility(GONE);
Julia Reynoldse0341482018-03-08 14:42:50 -0500389 minimize.setVisibility(GONE);
390 } else if (mIsForeground) {
391 block.setVisibility(GONE);
Gus Prevas9abc5062018-10-31 16:11:04 -0400392 silent.setVisibility(GONE);
Julia Reynoldse0341482018-03-08 14:42:50 -0500393 minimize.setVisibility(VISIBLE);
Gus Prevas9abc5062018-10-31 16:11:04 -0400394 } else {
Julia Reynoldse0341482018-03-08 14:42:50 -0500395 block.setVisibility(VISIBLE);
Gus Prevas9abc5062018-10-31 16:11:04 -0400396 boolean showToggleSilent = mIsNoisy
397 && NotificationUtils.useNewInterruptionModel(mContext);
398 silent.setVisibility(showToggleSilent ? VISIBLE : GONE);
399 boolean isCurrentlyAlerting =
400 mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT;
401 silent.setText(isCurrentlyAlerting
402 ? R.string.inline_silent_button_silent
403 : R.string.inline_silent_button_alert);
Julia Reynoldse0341482018-03-08 14:42:50 -0500404 minimize.setVisibility(GONE);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500405 }
406
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000407 // Set up app settings link (i.e. Customize)
Julia Reynolds437cdb12018-01-03 12:27:24 -0500408 TextView settingsLinkView = findViewById(R.id.app_settings);
Rohan Shahca0447e2018-03-30 15:18:27 -0700409 Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName, mSingleNotificationChannel,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500410 mSbn.getId(), mSbn.getTag());
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000411 if (!mIsForBlockingHelper
412 && settingsIntent != null
Julia Reynolds437cdb12018-01-03 12:27:24 -0500413 && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
Julia Reynoldse0341482018-03-08 14:42:50 -0500414 settingsLinkView.setVisibility(VISIBLE);
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000415 settingsLinkView.setText(mContext.getString(R.string.notification_app_settings));
Julia Reynolds437cdb12018-01-03 12:27:24 -0500416 settingsLinkView.setOnClickListener((View view) -> {
417 mAppSettingsClickListener.onClick(view, settingsIntent);
418 });
Mady Mellor87d79452017-01-10 11:52:52 -0800419 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500420 settingsLinkView.setVisibility(View.GONE);
Mady Mellor87d79452017-01-10 11:52:52 -0800421 }
422 }
423
Gus Prevas9abc5062018-10-31 16:11:04 -0400424 private void swapContent(@SwapContentAction int action) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500425 if (mExpandAnimation != null) {
426 mExpandAnimation.cancel();
427 }
Mady Mellor87d79452017-01-10 11:52:52 -0800428
Julia Reynoldse0341482018-03-08 14:42:50 -0500429 View prompt = findViewById(R.id.prompt);
430 ViewGroup confirmation = findViewById(R.id.confirmation);
431 TextView confirmationText = findViewById(R.id.confirmation_text);
432 View header = findViewById(R.id.header);
433
Gus Prevas9abc5062018-10-31 16:11:04 -0400434 switch (action) {
435 case SWAP_CONTENT_UNDO:
436 mChosenImportance = mStartingChannelImportance;
437 break;
438 case SWAP_CONTENT_TOGGLE_SILENT:
439 if (mStartingChannelOrNotificationImportance >= IMPORTANCE_DEFAULT) {
440 mChosenImportance = IMPORTANCE_LOW;
441 confirmationText.setText(R.string.notification_channel_silenced);
442 } else {
443 mChosenImportance = IMPORTANCE_HIGH;
444 confirmationText.setText(R.string.notification_channel_unsilenced);
445 }
446 break;
447 case SWAP_CONTENT_BLOCK:
448 if (mIsForeground) {
449 mChosenImportance = IMPORTANCE_MIN;
450 confirmationText.setText(R.string.notification_channel_minimized);
451 } else {
452 mChosenImportance = IMPORTANCE_NONE;
453 confirmationText.setText(R.string.notification_channel_disabled);
454 }
455 break;
456 default:
457 throw new IllegalArgumentException();
Julia Reynolds437cdb12018-01-03 12:27:24 -0500458 }
459
Gus Prevas9abc5062018-10-31 16:11:04 -0400460 boolean isUndo = action == SWAP_CONTENT_UNDO;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500461 ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA,
Gus Prevas9abc5062018-10-31 16:11:04 -0400462 prompt.getAlpha(), isUndo ? 1f : 0f);
463 promptAnim.setInterpolator(isUndo ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500464 ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA,
Gus Prevas9abc5062018-10-31 16:11:04 -0400465 confirmation.getAlpha(), isUndo ? 0f : 1f);
466 confirmAnim.setInterpolator(isUndo ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500467
Gus Prevas9abc5062018-10-31 16:11:04 -0400468 prompt.setVisibility(isUndo ? VISIBLE : GONE);
469 confirmation.setVisibility(isUndo ? GONE : VISIBLE);
470 header.setVisibility(isUndo ? VISIBLE : GONE);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500471
472 mExpandAnimation = new AnimatorSet();
473 mExpandAnimation.playTogether(promptAnim, confirmAnim);
474 mExpandAnimation.setDuration(150);
475 mExpandAnimation.addListener(new AnimatorListenerAdapter() {
476 boolean cancelled = false;
477
478 @Override
479 public void onAnimationCancel(Animator animation) {
480 cancelled = true;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500481 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500482
483 @Override
484 public void onAnimationEnd(Animator animation) {
485 if (!cancelled) {
Gus Prevas9abc5062018-10-31 16:11:04 -0400486 prompt.setVisibility(isUndo ? VISIBLE : GONE);
487 confirmation.setVisibility(isUndo ? GONE : VISIBLE);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500488 }
489 }
Mady Mellor87d79452017-01-10 11:52:52 -0800490 });
Julia Reynolds437cdb12018-01-03 12:27:24 -0500491 mExpandAnimation.start();
Rohan Shah142e2da2018-06-14 13:14:18 -0700492
493 // Since we're swapping/update the content, reset the timeout so the UI can't close
494 // immediately after the update.
495 if (mGutsContainer != null) {
496 mGutsContainer.resetFalsingCheck();
497 }
Mady Mellor87d79452017-01-10 11:52:52 -0800498 }
499
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400500 @Override
Gus Prevas9abc5062018-10-31 16:11:04 -0400501 public void onFinishedClosing() {
502 mStartingChannelImportance = mChosenImportance;
503 if (mChosenImportance != IMPORTANCE_UNSPECIFIED) {
504 mStartingChannelOrNotificationImportance = mChosenImportance;
505 }
506 mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
507
508 View prompt = findViewById(R.id.prompt);
509 ViewGroup confirmation = findViewById(R.id.confirmation);
510 View header = findViewById(R.id.header);
511 prompt.setVisibility(VISIBLE);
512 prompt.setAlpha(1f);
513 confirmation.setVisibility(GONE);
514 confirmation.setAlpha(1f);
515 header.setVisibility(VISIBLE);
516 header.setAlpha(1f);
517 }
518
519 @Override
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400520 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
521 super.onInitializeAccessibilityEvent(event);
522 if (mGutsContainer != null &&
523 event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
524 if (mGutsContainer.isExposed()) {
525 event.getText().add(mContext.getString(
526 R.string.notification_channel_controls_opened_accessibility, mAppName));
527 } else {
528 event.getText().add(mContext.getString(
529 R.string.notification_channel_controls_closed_accessibility, mAppName));
530 }
531 }
532 }
533
Julia Reynolds3aedded2017-03-31 14:42:09 -0400534 private Intent getAppSettingsIntent(PackageManager pm, String packageName,
535 NotificationChannel channel, int id, String tag) {
536 Intent intent = new Intent(Intent.ACTION_MAIN)
537 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
538 .setPackage(packageName);
539 final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
540 intent,
541 PackageManager.MATCH_DEFAULT_ONLY
542 );
543 if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
544 return null;
545 }
546 final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
547 intent.setClassName(activityInfo.packageName, activityInfo.name);
548 if (channel != null) {
549 intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
550 }
551 intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
552 intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
553 return intent;
554 }
555
Rohan Shahca0447e2018-03-30 15:18:27 -0700556 /**
557 * Closes the controls and commits the updated importance values (indirectly). If this view is
558 * being used to show the blocking helper, this will immediately dismiss the blocking helper and
559 * commit the updated importance.
560 *
561 * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
562 * user does not have the ability to undo the action anymore. See {@link #swapContent(boolean)}
563 * for where undo is handled.
564 */
Rohan Shah524cf7b2018-03-15 14:40:02 -0700565 @VisibleForTesting
566 void closeControls(View v) {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700567 int[] parentLoc = new int[2];
568 int[] targetLoc = new int[2];
569 mGutsContainer.getLocationOnScreen(parentLoc);
570 v.getLocationOnScreen(targetLoc);
571 final int centerX = v.getWidth() / 2;
572 final int centerY = v.getHeight() / 2;
573 final int x = targetLoc[0] - parentLoc[0] + centerX;
574 final int y = targetLoc[1] - parentLoc[1] + centerY;
575 mGutsContainer.closeControls(x, y, true /* save */, false /* force */);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500576 }
577
Mady Mellor87d79452017-01-10 11:52:52 -0800578 @Override
Mady Mellor95d743c2017-01-10 12:05:27 -0800579 public void setGutsParent(NotificationGuts guts) {
580 mGutsContainer = guts;
Mady Mellor87d79452017-01-10 11:52:52 -0800581 }
582
583 @Override
Mady Mellor434180c2017-02-13 11:29:42 -0800584 public boolean willBeRemoved() {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500585 return hasImportanceChanged();
Mady Mellor434180c2017-02-13 11:29:42 -0800586 }
587
588 @Override
Lucas Dupin9b08c012018-05-16 19:53:32 -0700589 public boolean shouldBeSaved() {
590 return hasImportanceChanged();
591 }
592
593 @Override
Mady Mellor87d79452017-01-10 11:52:52 -0800594 public View getContentView() {
595 return this;
596 }
597
598 @Override
Mady Mellorc2dbe492017-03-30 13:22:03 -0700599 public boolean handleCloseControls(boolean save, boolean force) {
Julia Reynoldsc65656a2018-02-12 09:55:14 -0500600 // Save regardless of the importance so we can lock the importance field if the user wants
601 // to keep getting notifications
602 if (save) {
Rohan Shahca0447e2018-03-30 15:18:27 -0700603 saveImportance();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500604 }
Rohan Shahdd588c72018-05-09 20:32:15 -0700605 logBlockingHelperCounter(mExitReason);
Mady Mellor87d79452017-01-10 11:52:52 -0800606 return false;
607 }
Mady Mellore09fb702017-03-30 13:23:29 -0700608
609 @Override
610 public int getActualHeight() {
611 return getHeight();
612 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700613
Gus Prevas533836a2018-09-24 17:15:32 -0400614 @VisibleForTesting
615 public boolean isAnimating() {
616 return mExpandAnimation != null && mExpandAnimation.isRunning();
617 }
618
Rohan Shahca0447e2018-03-30 15:18:27 -0700619 /**
620 * Runnable to either update the given channel (with a new importance value) or, if no channel
621 * is provided, update notifications enabled state for the package.
622 */
623 private static class UpdateImportanceRunnable implements Runnable {
624 private final INotificationManager mINotificationManager;
625 private final String mPackageName;
626 private final int mAppUid;
627 private final @Nullable NotificationChannel mChannelToUpdate;
628 private final int mCurrentImportance;
629 private final int mNewImportance;
630
631
632 public UpdateImportanceRunnable(INotificationManager notificationManager,
633 String packageName, int appUid, @Nullable NotificationChannel channelToUpdate,
634 int currentImportance, int newImportance) {
635 mINotificationManager = notificationManager;
636 mPackageName = packageName;
637 mAppUid = appUid;
638 mChannelToUpdate = channelToUpdate;
639 mCurrentImportance = currentImportance;
640 mNewImportance = newImportance;
641 }
642
643 @Override
644 public void run() {
645 try {
646 if (mChannelToUpdate != null) {
647 mChannelToUpdate.setImportance(mNewImportance);
648 mChannelToUpdate.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
649 mINotificationManager.updateNotificationChannelForPackage(
650 mPackageName, mAppUid, mChannelToUpdate);
651 } else {
652 // For notifications with more than one channel, update notification enabled
653 // state. If the importance was lowered, we disable notifications.
Rohan Shah590e1b22018-04-10 23:48:47 -0400654 mINotificationManager.setNotificationsEnabledWithImportanceLockForPackage(
Rohan Shahca0447e2018-03-30 15:18:27 -0700655 mPackageName, mAppUid, mNewImportance >= mCurrentImportance);
656 }
657 } catch (RemoteException e) {
658 Log.e(TAG, "Unable to update notification importance", e);
659 }
660 }
661 }
Mady Mellor87d79452017-01-10 11:52:52 -0800662}