blob: 912a2f7e598d7753cb690197f2dfb8c8fe0ad043 [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
Julia Reynoldse0341482018-03-08 14:42:50 -050019import static android.app.NotificationManager.IMPORTANCE_MIN;
Julia Reynolds3aedded2017-03-31 14:42:09 -040020import static android.app.NotificationManager.IMPORTANCE_NONE;
21
Julia Reynolds437cdb12018-01-03 12:27:24 -050022import android.animation.Animator;
23import android.animation.AnimatorListenerAdapter;
24import android.animation.AnimatorSet;
25import android.animation.ObjectAnimator;
Rohan Shahca0447e2018-03-30 15:18:27 -070026import android.annotation.Nullable;
Mady Mellor87d79452017-01-10 11:52:52 -080027import android.app.INotificationManager;
Julia Reynolds3aedded2017-03-31 14:42:09 -040028import android.app.Notification;
Mady Mellor87d79452017-01-10 11:52:52 -080029import android.app.NotificationChannel;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -050030import android.app.NotificationChannelGroup;
Mady Mellor87d79452017-01-10 11:52:52 -080031import android.content.Context;
Julia Reynolds3aedded2017-03-31 14:42:09 -040032import android.content.Intent;
33import android.content.pm.ActivityInfo;
Mady Mellor87d79452017-01-10 11:52:52 -080034import android.content.pm.ApplicationInfo;
Mady Mellor87d79452017-01-10 11:52:52 -080035import android.content.pm.PackageManager;
Julia Reynolds3aedded2017-03-31 14:42:09 -040036import android.content.pm.ResolveInfo;
Mady Mellor87d79452017-01-10 11:52:52 -080037import android.graphics.drawable.Drawable;
Rohan Shahca0447e2018-03-30 15:18:27 -070038import android.os.Handler;
Mady Mellor87d79452017-01-10 11:52:52 -080039import android.os.RemoteException;
Julia Reynolds3aedded2017-03-31 14:42:09 -040040import android.service.notification.StatusBarNotification;
41import android.text.TextUtils;
Mady Mellor87d79452017-01-10 11:52:52 -080042import android.util.AttributeSet;
Rohan Shahca0447e2018-03-30 15:18:27 -070043import android.util.Log;
Mady Mellor87d79452017-01-10 11:52:52 -080044import android.view.View;
Julia Reynoldse0341482018-03-08 14:42:50 -050045import android.view.ViewGroup;
Geoffrey Pitschd94e7882017-04-06 09:52:11 -040046import android.view.accessibility.AccessibilityEvent;
Mady Mellor87d79452017-01-10 11:52:52 -080047import android.widget.ImageView;
48import android.widget.LinearLayout;
Mady Mellor87d79452017-01-10 11:52:52 -080049import android.widget.TextView;
50
Rohan Shah524cf7b2018-03-15 14:40:02 -070051import com.android.internal.annotations.VisibleForTesting;
Mady Mellor87d79452017-01-10 11:52:52 -080052import com.android.internal.logging.MetricsLogger;
53import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Rohan Shah524cf7b2018-03-15 14:40:02 -070054import com.android.systemui.Dependency;
Julia Reynolds437cdb12018-01-03 12:27:24 -050055import com.android.systemui.Interpolators;
Mady Mellor87d79452017-01-10 11:52:52 -080056import com.android.systemui.R;
Rohan Shah20790b82018-07-02 17:21:04 -070057import com.android.systemui.statusbar.notification.logging.NotificationCounters;
Mady Mellor87d79452017-01-10 11:52:52 -080058
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050059import java.util.List;
Mady Mellor87d79452017-01-10 11:52:52 -080060
61/**
Rohan Shahda5dcdd2018-04-27 17:21:50 -070062 * The guts of a notification revealed when performing a long press. This also houses the blocking
63 * helper affordance that allows a user to keep/stop notifications after swiping one away.
Mady Mellor87d79452017-01-10 11:52:52 -080064 */
Mady Mellor95d743c2017-01-10 12:05:27 -080065public class NotificationInfo extends LinearLayout implements NotificationGuts.GutsContent {
Mady Mellor87d79452017-01-10 11:52:52 -080066 private static final String TAG = "InfoGuts";
67
68 private INotificationManager mINotificationManager;
Julia Reynolds437cdb12018-01-03 12:27:24 -050069 private PackageManager mPm;
Rohan Shahda5dcdd2018-04-27 17:21:50 -070070 private MetricsLogger mMetricsLogger;
Julia Reynolds437cdb12018-01-03 12:27:24 -050071
Rohan Shahca0447e2018-03-30 15:18:27 -070072 private String mPackageName;
Geoffrey Pitschd94e7882017-04-06 09:52:11 -040073 private String mAppName;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050074 private int mAppUid;
Julia Reynoldsac98aea2018-10-25 16:54:27 -040075 private String mDelegatePkg;
Rohan Shahca0447e2018-03-30 15:18:27 -070076 private int mNumUniqueChannelsInRow;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050077 private NotificationChannel mSingleNotificationChannel;
Mady Mellor87d79452017-01-10 11:52:52 -080078 private int mStartingUserImportance;
Julia Reynolds437cdb12018-01-03 12:27:24 -050079 private int mChosenImportance;
80 private boolean mIsSingleDefaultChannel;
Rohan Shah63411fc2018-03-28 19:05:52 -070081 private boolean mIsNonblockable;
Julia Reynolds437cdb12018-01-03 12:27:24 -050082 private StatusBarNotification mSbn;
83 private AnimatorSet mExpandAnimation;
Julia Reynoldse0341482018-03-08 14:42:50 -050084 private boolean mIsForeground;
Julia Reynolds35765d82018-08-17 11:39:19 -040085 private boolean mIsDeviceProvisioned;
Mady Mellor87d79452017-01-10 11:52:52 -080086
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -040087 private CheckSaveListener mCheckSaveListener;
Julia Reynolds437cdb12018-01-03 12:27:24 -050088 private OnSettingsClickListener mOnSettingsClickListener;
Julia Reynolds3aedded2017-03-31 14:42:09 -040089 private OnAppSettingsClickListener mAppSettingsClickListener;
Mady Mellor95d743c2017-01-10 12:05:27 -080090 private NotificationGuts mGutsContainer;
Rohan Shah524cf7b2018-03-15 14:40:02 -070091
Rohan Shahda5dcdd2018-04-27 17:21:50 -070092 /** Whether this view is being shown as part of the blocking helper. */
Rohan Shah524cf7b2018-03-15 14:40:02 -070093 private boolean mIsForBlockingHelper;
Julia Reynolds0ef7d842018-01-24 17:50:31 -050094 private boolean mNegativeUserSentiment;
Mady Mellor87d79452017-01-10 11:52:52 -080095
Rohan Shahdd588c72018-05-09 20:32:15 -070096 /**
97 * String that describes how the user exit or quit out of this view, also used as a counter tag.
98 */
99 private String mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700100
101 private OnClickListener mOnKeepShowing = v -> {
Rohan Shahdd588c72018-05-09 20:32:15 -0700102 mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700103 closeControls(v);
104 };
Julia Reynolds437cdb12018-01-03 12:27:24 -0500105
Rohan Shahca0447e2018-03-30 15:18:27 -0700106 private OnClickListener mOnStopOrMinimizeNotifications = v -> {
Gus Prevas533836a2018-09-24 17:15:32 -0400107 Runnable saveImportance = () -> {
108 mExitReason = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS;
109 swapContent(false);
110 };
111 if (mCheckSaveListener != null) {
112 mCheckSaveListener.checkSave(saveImportance, mSbn);
113 } else {
114 saveImportance.run();
115 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500116 };
117
118 private OnClickListener mOnUndo = v -> {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700119 // Reset exit counter that we'll log and record an undo event separately (not an exit event)
Rohan Shahdd588c72018-05-09 20:32:15 -0700120 mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700121 logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500122 swapContent(true);
123 };
124
Mady Mellor87d79452017-01-10 11:52:52 -0800125 public NotificationInfo(Context context, AttributeSet attrs) {
126 super(context, attrs);
127 }
128
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400129 // Specify a CheckSaveListener to override when/if the user's changes are committed.
130 public interface CheckSaveListener {
131 // Invoked when importance has changed and the NotificationInfo wants to try to save it.
132 // Listener should run saveImportance unless the change should be canceled.
Eliot Courtney47098cb2017-10-18 17:30:30 +0900133 void checkSave(Runnable saveImportance, StatusBarNotification sbn);
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400134 }
135
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500136 public interface OnSettingsClickListener {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500137 void onClick(View v, NotificationChannel channel, int appUid);
Mady Mellor87d79452017-01-10 11:52:52 -0800138 }
139
Julia Reynolds3aedded2017-03-31 14:42:09 -0400140 public interface OnAppSettingsClickListener {
141 void onClick(View v, Intent intent);
142 }
143
Rohan Shah524cf7b2018-03-15 14:40:02 -0700144 @VisibleForTesting
145 void bindNotification(
146 final PackageManager pm,
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500147 final INotificationManager iNotificationManager,
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500148 final String pkg,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500149 final NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700150 final int numUniqueChannelsInRow,
Julia Reynolds3aedded2017-03-31 14:42:09 -0400151 final StatusBarNotification sbn,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500152 final CheckSaveListener checkSaveListener,
153 final OnSettingsClickListener onSettingsClick,
154 final OnAppSettingsClickListener onAppSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400155 boolean isDeviceProvisioned,
Rohan Shah63411fc2018-03-28 19:05:52 -0700156 boolean isNonblockable)
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500157 throws RemoteException {
Rohan Shahca0447e2018-03-30 15:18:27 -0700158 bindNotification(pm, iNotificationManager, pkg, notificationChannel,
159 numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400160 onAppSettingsClick, isDeviceProvisioned, isNonblockable,
161 false /* isBlockingHelper */, false /* isUserSentimentNegative */);
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500162 }
163
Rohan Shah524cf7b2018-03-15 14:40:02 -0700164 public void bindNotification(
165 PackageManager pm,
166 INotificationManager iNotificationManager,
167 String pkg,
168 NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700169 int numUniqueChannelsInRow,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700170 StatusBarNotification sbn,
171 CheckSaveListener checkSaveListener,
172 OnSettingsClickListener onSettingsClick,
173 OnAppSettingsClickListener onAppSettingsClick,
Julia Reynolds35765d82018-08-17 11:39:19 -0400174 boolean isDeviceProvisioned,
Rohan Shah63411fc2018-03-28 19:05:52 -0700175 boolean isNonblockable,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700176 boolean isForBlockingHelper,
177 boolean isUserSentimentNegative)
178 throws RemoteException {
Mady Mellor87d79452017-01-10 11:52:52 -0800179 mINotificationManager = iNotificationManager;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700180 mMetricsLogger = Dependency.get(MetricsLogger.class);
Rohan Shahca0447e2018-03-30 15:18:27 -0700181 mPackageName = pkg;
182 mNumUniqueChannelsInRow = numUniqueChannelsInRow;
Julia Reynolds3aedded2017-03-31 14:42:09 -0400183 mSbn = sbn;
184 mPm = pm;
185 mAppSettingsClickListener = onAppSettingsClick;
Rohan Shahca0447e2018-03-30 15:18:27 -0700186 mAppName = mPackageName;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500187 mCheckSaveListener = checkSaveListener;
188 mOnSettingsClickListener = onSettingsClick;
189 mSingleNotificationChannel = notificationChannel;
190 mStartingUserImportance = mChosenImportance = mSingleNotificationChannel.getImportance();
Rohan Shah524cf7b2018-03-15 14:40:02 -0700191 mNegativeUserSentiment = isUserSentimentNegative;
Rohan Shah63411fc2018-03-28 19:05:52 -0700192 mIsNonblockable = isNonblockable;
Julia Reynoldse0341482018-03-08 14:42:50 -0500193 mIsForeground =
194 (mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700195 mIsForBlockingHelper = isForBlockingHelper;
Rohan Shahdbd64e72018-03-28 14:46:50 -0700196 mAppUid = mSbn.getUid();
Julia Reynoldsac98aea2018-10-25 16:54:27 -0400197 mDelegatePkg = mSbn.getOpPkg();
Julia Reynolds35765d82018-08-17 11:39:19 -0400198 mIsDeviceProvisioned = isDeviceProvisioned;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500199
Julia Reynolds437cdb12018-01-03 12:27:24 -0500200 int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400201 pkg, mAppUid, false /* includeDeleted */);
Rohan Shahca0447e2018-03-30 15:18:27 -0700202 if (mNumUniqueChannelsInRow == 0) {
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400203 throw new IllegalArgumentException("bindNotification requires at least one channel");
204 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500205 // Special behavior for the Default channel if no other channels have been defined.
Rohan Shahca0447e2018-03-30 15:18:27 -0700206 mIsSingleDefaultChannel = mNumUniqueChannelsInRow == 1
Rohan Shahdbd64e72018-03-28 14:46:50 -0700207 && mSingleNotificationChannel.getId().equals(
208 NotificationChannel.DEFAULT_CHANNEL_ID)
209 && numTotalChannels == 1;
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400210 }
211
Julia Reynolds437cdb12018-01-03 12:27:24 -0500212 bindHeader();
213 bindPrompt();
214 bindButtons();
215 }
216
217 private void bindHeader() throws RemoteException {
218 // Package name
219 Drawable pkgicon = null;
220 ApplicationInfo info;
221 try {
Rohan Shahca0447e2018-03-30 15:18:27 -0700222 info = mPm.getApplicationInfo(
223 mPackageName,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500224 PackageManager.MATCH_UNINSTALLED_PACKAGES
225 | PackageManager.MATCH_DISABLED_COMPONENTS
226 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
227 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
228 if (info != null) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500229 mAppName = String.valueOf(mPm.getApplicationLabel(info));
230 pkgicon = mPm.getApplicationIcon(info);
Geoffrey Pitschee8c81e2017-03-23 11:38:56 -0400231 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500232 } catch (PackageManager.NameNotFoundException e) {
233 // app is gone, just show package name and generic icon
234 pkgicon = mPm.getDefaultActivityIcon();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500235 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500236 ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400237 ((TextView) findViewById(R.id.pkgname)).setText(mAppName);
Mady Mellor87d79452017-01-10 11:52:52 -0800238
Julia Reynoldsac98aea2018-10-25 16:54:27 -0400239 // Delegate
240 bindDelegate();
Mady Mellor87d79452017-01-10 11:52:52 -0800241
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500242 // Settings button.
Julia Reynolds437cdb12018-01-03 12:27:24 -0500243 final View settingsButton = findViewById(R.id.info);
Julia Reynolds35765d82018-08-17 11:39:19 -0400244 if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
Geoffrey Pitsch029a3fa2017-03-30 13:01:57 -0400245 settingsButton.setVisibility(View.VISIBLE);
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500246 final int appUidF = mAppUid;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500247 settingsButton.setOnClickListener(
248 (View view) -> {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700249 logBlockingHelperCounter(
250 NotificationCounters.BLOCKING_HELPER_NOTIF_SETTINGS);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500251 mOnSettingsClickListener.onClick(view,
Rohan Shahca0447e2018-03-30 15:18:27 -0700252 mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500253 appUidF);
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500254 });
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500255 } else {
256 settingsButton.setVisibility(View.GONE);
257 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500258 }
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500259
Julia Reynoldsac98aea2018-10-25 16:54:27 -0400260 private void bindPrompt() throws RemoteException {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500261 final TextView blockPrompt = findViewById(R.id.block_prompt);
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500262 bindName();
Julia Reynoldsac98aea2018-10-25 16:54:27 -0400263 bindGroup();
Rohan Shah63411fc2018-03-28 19:05:52 -0700264 if (mIsNonblockable) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500265 blockPrompt.setText(R.string.notification_unblockable_desc);
266 } else {
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500267 if (mNegativeUserSentiment) {
268 blockPrompt.setText(R.string.inline_blocking_helper);
Rohan Shahca0447e2018-03-30 15:18:27 -0700269 } else if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500270 blockPrompt.setText(R.string.inline_keep_showing_app);
271 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500272 blockPrompt.setText(R.string.inline_keep_showing);
273 }
274 }
Mady Mellor87d79452017-01-10 11:52:52 -0800275 }
276
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500277 private void bindName() {
278 final TextView channelName = findViewById(R.id.channel_name);
Rohan Shahca0447e2018-03-30 15:18:27 -0700279 if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500280 channelName.setVisibility(View.GONE);
281 } else {
282 channelName.setText(mSingleNotificationChannel.getName());
283 }
284 }
285
Julia Reynoldsac98aea2018-10-25 16:54:27 -0400286 private void bindDelegate() {
287 TextView delegateView = findViewById(R.id.delegate_name);
288 TextView dividerView = findViewById(R.id.pkg_divider);
289
290 CharSequence delegatePkg = null;
291 if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
292 // this notification was posted by a delegate!
293 ApplicationInfo info;
294 try {
295 info = mPm.getApplicationInfo(
296 mDelegatePkg,
297 PackageManager.MATCH_UNINSTALLED_PACKAGES
298 | PackageManager.MATCH_DISABLED_COMPONENTS
299 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
300 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
301 if (info != null) {
302 delegatePkg = String.valueOf(mPm.getApplicationLabel(info));
303 }
304 } catch (PackageManager.NameNotFoundException e) {}
305 }
306 if (delegatePkg != null) {
307 delegateView.setText(mContext.getResources().getString(
308 R.string.notification_delegate_header, delegatePkg));
309 delegateView.setVisibility(View.VISIBLE);
310 dividerView.setVisibility(View.VISIBLE);
311 } else {
312 delegateView.setVisibility(View.GONE);
313 dividerView.setVisibility(View.GONE);
314 }
315 }
316
317 private void bindGroup() throws RemoteException {
318 // Set group information if this channel has an associated group.
319 CharSequence groupName = null;
320 if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
321 final NotificationChannelGroup notificationChannelGroup =
322 mINotificationManager.getNotificationChannelGroupForPackage(
323 mSingleNotificationChannel.getGroup(), mPackageName, mAppUid);
324 if (notificationChannelGroup != null) {
325 groupName = notificationChannelGroup.getName();
326 }
327 }
328 TextView groupNameView = findViewById(R.id.group_name);
329 TextView groupDividerView = findViewById(R.id.pkg_group_divider);
330 if (groupName != null) {
331 groupNameView.setText(groupName);
332 groupNameView.setVisibility(View.VISIBLE);
333 groupDividerView.setVisibility(View.VISIBLE);
334 } else {
335 groupNameView.setVisibility(View.GONE);
336 groupDividerView.setVisibility(View.GONE);
337 }
338 }
339
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700340 @VisibleForTesting
341 void logBlockingHelperCounter(String counterTag) {
342 if (mIsForBlockingHelper) {
343 mMetricsLogger.count(counterTag, 1);
344 }
345 }
346
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400347 private boolean hasImportanceChanged() {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500348 return mSingleNotificationChannel != null && mStartingUserImportance != mChosenImportance;
Mady Mellor87d79452017-01-10 11:52:52 -0800349 }
350
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500351 private void saveImportance() {
Rohan Shahca0447e2018-03-30 15:18:27 -0700352 if (!mIsNonblockable) {
Gus Prevas533836a2018-09-24 17:15:32 -0400353 updateImportance();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500354 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700355 }
356
357 /**
358 * Commits the updated importance values on the background thread.
359 */
360 private void updateImportance() {
Mady Mellor87d79452017-01-10 11:52:52 -0800361 MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500362 mChosenImportance - mStartingUserImportance);
Rohan Shahca0447e2018-03-30 15:18:27 -0700363
364 Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
365 bgHandler.post(new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
366 mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
367 mStartingUserImportance, mChosenImportance));
Mady Mellor87d79452017-01-10 11:52:52 -0800368 }
369
Julia Reynolds437cdb12018-01-03 12:27:24 -0500370 private void bindButtons() {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700371 // Set up stay-in-notification actions
Julia Reynolds437cdb12018-01-03 12:27:24 -0500372 View block = findViewById(R.id.block);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500373 TextView keep = findViewById(R.id.keep);
Julia Reynoldse0341482018-03-08 14:42:50 -0500374 View minimize = findViewById(R.id.minimize);
Rohan Shah524cf7b2018-03-15 14:40:02 -0700375
376 findViewById(R.id.undo).setOnClickListener(mOnUndo);
Rohan Shahca0447e2018-03-30 15:18:27 -0700377 block.setOnClickListener(mOnStopOrMinimizeNotifications);
Rohan Shah524cf7b2018-03-15 14:40:02 -0700378 keep.setOnClickListener(mOnKeepShowing);
Rohan Shahca0447e2018-03-30 15:18:27 -0700379 minimize.setOnClickListener(mOnStopOrMinimizeNotifications);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500380
Rohan Shah63411fc2018-03-28 19:05:52 -0700381 if (mIsNonblockable) {
Rohan Shah53529aa2018-04-12 00:14:50 -0400382 keep.setText(android.R.string.ok);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500383 block.setVisibility(GONE);
Julia Reynoldse0341482018-03-08 14:42:50 -0500384 minimize.setVisibility(GONE);
385 } else if (mIsForeground) {
386 block.setVisibility(GONE);
387 minimize.setVisibility(VISIBLE);
388 } else if (!mIsForeground) {
389 block.setVisibility(VISIBLE);
390 minimize.setVisibility(GONE);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500391 }
392
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000393 // Set up app settings link (i.e. Customize)
Julia Reynolds437cdb12018-01-03 12:27:24 -0500394 TextView settingsLinkView = findViewById(R.id.app_settings);
Rohan Shahca0447e2018-03-30 15:18:27 -0700395 Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName, mSingleNotificationChannel,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500396 mSbn.getId(), mSbn.getTag());
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000397 if (!mIsForBlockingHelper
398 && settingsIntent != null
Julia Reynolds437cdb12018-01-03 12:27:24 -0500399 && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
Julia Reynoldse0341482018-03-08 14:42:50 -0500400 settingsLinkView.setVisibility(VISIBLE);
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000401 settingsLinkView.setText(mContext.getString(R.string.notification_app_settings));
Julia Reynolds437cdb12018-01-03 12:27:24 -0500402 settingsLinkView.setOnClickListener((View view) -> {
403 mAppSettingsClickListener.onClick(view, settingsIntent);
404 });
Mady Mellor87d79452017-01-10 11:52:52 -0800405 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500406 settingsLinkView.setVisibility(View.GONE);
Mady Mellor87d79452017-01-10 11:52:52 -0800407 }
408 }
409
Julia Reynolds437cdb12018-01-03 12:27:24 -0500410 private void swapContent(boolean showPrompt) {
411 if (mExpandAnimation != null) {
412 mExpandAnimation.cancel();
413 }
Mady Mellor87d79452017-01-10 11:52:52 -0800414
Julia Reynoldse0341482018-03-08 14:42:50 -0500415 View prompt = findViewById(R.id.prompt);
416 ViewGroup confirmation = findViewById(R.id.confirmation);
417 TextView confirmationText = findViewById(R.id.confirmation_text);
418 View header = findViewById(R.id.header);
419
Julia Reynolds437cdb12018-01-03 12:27:24 -0500420 if (showPrompt) {
421 mChosenImportance = mStartingUserImportance;
Julia Reynoldse0341482018-03-08 14:42:50 -0500422 } else if (mIsForeground) {
423 mChosenImportance = IMPORTANCE_MIN;
424 confirmationText.setText(R.string.notification_channel_minimized);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500425 } else {
426 mChosenImportance = IMPORTANCE_NONE;
Julia Reynoldse0341482018-03-08 14:42:50 -0500427 confirmationText.setText(R.string.notification_channel_disabled);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500428 }
429
Julia Reynolds437cdb12018-01-03 12:27:24 -0500430 ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA,
431 prompt.getAlpha(), showPrompt ? 1f : 0f);
432 promptAnim.setInterpolator(showPrompt ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
433 ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA,
434 confirmation.getAlpha(), showPrompt ? 0f : 1f);
435 confirmAnim.setInterpolator(showPrompt ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN);
436
437 prompt.setVisibility(showPrompt ? VISIBLE : GONE);
438 confirmation.setVisibility(showPrompt ? GONE : VISIBLE);
Julia Reynoldse0341482018-03-08 14:42:50 -0500439 header.setVisibility(showPrompt ? VISIBLE : GONE);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500440
441 mExpandAnimation = new AnimatorSet();
442 mExpandAnimation.playTogether(promptAnim, confirmAnim);
443 mExpandAnimation.setDuration(150);
444 mExpandAnimation.addListener(new AnimatorListenerAdapter() {
445 boolean cancelled = false;
446
447 @Override
448 public void onAnimationCancel(Animator animation) {
449 cancelled = true;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500450 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500451
452 @Override
453 public void onAnimationEnd(Animator animation) {
454 if (!cancelled) {
455 prompt.setVisibility(showPrompt ? VISIBLE : GONE);
456 confirmation.setVisibility(showPrompt ? GONE : VISIBLE);
457 }
458 }
Mady Mellor87d79452017-01-10 11:52:52 -0800459 });
Julia Reynolds437cdb12018-01-03 12:27:24 -0500460 mExpandAnimation.start();
Rohan Shah142e2da2018-06-14 13:14:18 -0700461
462 // Since we're swapping/update the content, reset the timeout so the UI can't close
463 // immediately after the update.
464 if (mGutsContainer != null) {
465 mGutsContainer.resetFalsingCheck();
466 }
Mady Mellor87d79452017-01-10 11:52:52 -0800467 }
468
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400469 @Override
470 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
471 super.onInitializeAccessibilityEvent(event);
472 if (mGutsContainer != null &&
473 event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
474 if (mGutsContainer.isExposed()) {
475 event.getText().add(mContext.getString(
476 R.string.notification_channel_controls_opened_accessibility, mAppName));
477 } else {
478 event.getText().add(mContext.getString(
479 R.string.notification_channel_controls_closed_accessibility, mAppName));
480 }
481 }
482 }
483
Julia Reynolds3aedded2017-03-31 14:42:09 -0400484 private Intent getAppSettingsIntent(PackageManager pm, String packageName,
485 NotificationChannel channel, int id, String tag) {
486 Intent intent = new Intent(Intent.ACTION_MAIN)
487 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
488 .setPackage(packageName);
489 final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
490 intent,
491 PackageManager.MATCH_DEFAULT_ONLY
492 );
493 if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
494 return null;
495 }
496 final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
497 intent.setClassName(activityInfo.packageName, activityInfo.name);
498 if (channel != null) {
499 intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
500 }
501 intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
502 intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
503 return intent;
504 }
505
Rohan Shahca0447e2018-03-30 15:18:27 -0700506 /**
507 * Closes the controls and commits the updated importance values (indirectly). If this view is
508 * being used to show the blocking helper, this will immediately dismiss the blocking helper and
509 * commit the updated importance.
510 *
511 * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
512 * user does not have the ability to undo the action anymore. See {@link #swapContent(boolean)}
513 * for where undo is handled.
514 */
Rohan Shah524cf7b2018-03-15 14:40:02 -0700515 @VisibleForTesting
516 void closeControls(View v) {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700517 int[] parentLoc = new int[2];
518 int[] targetLoc = new int[2];
519 mGutsContainer.getLocationOnScreen(parentLoc);
520 v.getLocationOnScreen(targetLoc);
521 final int centerX = v.getWidth() / 2;
522 final int centerY = v.getHeight() / 2;
523 final int x = targetLoc[0] - parentLoc[0] + centerX;
524 final int y = targetLoc[1] - parentLoc[1] + centerY;
525 mGutsContainer.closeControls(x, y, true /* save */, false /* force */);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500526 }
527
Mady Mellor87d79452017-01-10 11:52:52 -0800528 @Override
Mady Mellor95d743c2017-01-10 12:05:27 -0800529 public void setGutsParent(NotificationGuts guts) {
530 mGutsContainer = guts;
Mady Mellor87d79452017-01-10 11:52:52 -0800531 }
532
533 @Override
Mady Mellor434180c2017-02-13 11:29:42 -0800534 public boolean willBeRemoved() {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500535 return hasImportanceChanged();
Mady Mellor434180c2017-02-13 11:29:42 -0800536 }
537
538 @Override
Lucas Dupin9b08c012018-05-16 19:53:32 -0700539 public boolean shouldBeSaved() {
540 return hasImportanceChanged();
541 }
542
543 @Override
Mady Mellor87d79452017-01-10 11:52:52 -0800544 public View getContentView() {
545 return this;
546 }
547
548 @Override
Mady Mellorc2dbe492017-03-30 13:22:03 -0700549 public boolean handleCloseControls(boolean save, boolean force) {
Julia Reynoldsc65656a2018-02-12 09:55:14 -0500550 // Save regardless of the importance so we can lock the importance field if the user wants
551 // to keep getting notifications
552 if (save) {
Rohan Shahca0447e2018-03-30 15:18:27 -0700553 saveImportance();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500554 }
Rohan Shahdd588c72018-05-09 20:32:15 -0700555 logBlockingHelperCounter(mExitReason);
Mady Mellor87d79452017-01-10 11:52:52 -0800556 return false;
557 }
Mady Mellore09fb702017-03-30 13:23:29 -0700558
559 @Override
560 public int getActualHeight() {
561 return getHeight();
562 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700563
Gus Prevas533836a2018-09-24 17:15:32 -0400564 @VisibleForTesting
565 public boolean isAnimating() {
566 return mExpandAnimation != null && mExpandAnimation.isRunning();
567 }
568
Rohan Shahca0447e2018-03-30 15:18:27 -0700569 /**
570 * Runnable to either update the given channel (with a new importance value) or, if no channel
571 * is provided, update notifications enabled state for the package.
572 */
573 private static class UpdateImportanceRunnable implements Runnable {
574 private final INotificationManager mINotificationManager;
575 private final String mPackageName;
576 private final int mAppUid;
577 private final @Nullable NotificationChannel mChannelToUpdate;
578 private final int mCurrentImportance;
579 private final int mNewImportance;
580
581
582 public UpdateImportanceRunnable(INotificationManager notificationManager,
583 String packageName, int appUid, @Nullable NotificationChannel channelToUpdate,
584 int currentImportance, int newImportance) {
585 mINotificationManager = notificationManager;
586 mPackageName = packageName;
587 mAppUid = appUid;
588 mChannelToUpdate = channelToUpdate;
589 mCurrentImportance = currentImportance;
590 mNewImportance = newImportance;
591 }
592
593 @Override
594 public void run() {
595 try {
596 if (mChannelToUpdate != null) {
597 mChannelToUpdate.setImportance(mNewImportance);
598 mChannelToUpdate.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
599 mINotificationManager.updateNotificationChannelForPackage(
600 mPackageName, mAppUid, mChannelToUpdate);
601 } else {
602 // For notifications with more than one channel, update notification enabled
603 // state. If the importance was lowered, we disable notifications.
Rohan Shah590e1b22018-04-10 23:48:47 -0400604 mINotificationManager.setNotificationsEnabledWithImportanceLockForPackage(
Rohan Shahca0447e2018-03-30 15:18:27 -0700605 mPackageName, mAppUid, mNewImportance >= mCurrentImportance);
606 }
607 } catch (RemoteException e) {
608 Log.e(TAG, "Unable to update notification importance", e);
609 }
610 }
611 }
Mady Mellor87d79452017-01-10 11:52:52 -0800612}