blob: 4f29b31fd9418ae0c7253d1975972ca153edd5da [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
Geoffrey Pitschdf44b602017-02-03 13:31:50 -050017package com.android.systemui.statusbar;
18
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 Shahda5dcdd2018-04-27 17:21:50 -070057import com.android.systemui.statusbar.notification.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;
Rohan Shahca0447e2018-03-30 15:18:27 -070075 private int mNumUniqueChannelsInRow;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050076 private NotificationChannel mSingleNotificationChannel;
Mady Mellor87d79452017-01-10 11:52:52 -080077 private int mStartingUserImportance;
Julia Reynolds437cdb12018-01-03 12:27:24 -050078 private int mChosenImportance;
79 private boolean mIsSingleDefaultChannel;
Rohan Shah63411fc2018-03-28 19:05:52 -070080 private boolean mIsNonblockable;
Julia Reynolds437cdb12018-01-03 12:27:24 -050081 private StatusBarNotification mSbn;
82 private AnimatorSet mExpandAnimation;
Julia Reynoldse0341482018-03-08 14:42:50 -050083 private boolean mIsForeground;
Mady Mellor87d79452017-01-10 11:52:52 -080084
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -040085 private CheckSaveListener mCheckSaveListener;
Julia Reynolds437cdb12018-01-03 12:27:24 -050086 private OnSettingsClickListener mOnSettingsClickListener;
Julia Reynolds3aedded2017-03-31 14:42:09 -040087 private OnAppSettingsClickListener mAppSettingsClickListener;
Mady Mellor95d743c2017-01-10 12:05:27 -080088 private NotificationGuts mGutsContainer;
Rohan Shah524cf7b2018-03-15 14:40:02 -070089
Rohan Shahda5dcdd2018-04-27 17:21:50 -070090 /** Whether this view is being shown as part of the blocking helper. */
Rohan Shah524cf7b2018-03-15 14:40:02 -070091 private boolean mIsForBlockingHelper;
Julia Reynolds0ef7d842018-01-24 17:50:31 -050092 private boolean mNegativeUserSentiment;
Mady Mellor87d79452017-01-10 11:52:52 -080093
Rohan Shahdd588c72018-05-09 20:32:15 -070094 /**
95 * String that describes how the user exit or quit out of this view, also used as a counter tag.
96 */
97 private String mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
Rohan Shahda5dcdd2018-04-27 17:21:50 -070098
99 private OnClickListener mOnKeepShowing = v -> {
Rohan Shahdd588c72018-05-09 20:32:15 -0700100 mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700101 closeControls(v);
102 };
Julia Reynolds437cdb12018-01-03 12:27:24 -0500103
Rohan Shahca0447e2018-03-30 15:18:27 -0700104 private OnClickListener mOnStopOrMinimizeNotifications = v -> {
Gus Prevasf4f0b062018-09-24 17:15:32 -0400105 Runnable saveImportance = () -> {
106 mExitReason = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS;
107 swapContent(false);
108 };
109 if (mCheckSaveListener != null) {
110 mCheckSaveListener.checkSave(saveImportance, mSbn);
111 } else {
112 saveImportance.run();
113 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500114 };
115
116 private OnClickListener mOnUndo = v -> {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700117 // Reset exit counter that we'll log and record an undo event separately (not an exit event)
Rohan Shahdd588c72018-05-09 20:32:15 -0700118 mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700119 logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500120 swapContent(true);
121 };
122
Mady Mellor87d79452017-01-10 11:52:52 -0800123 public NotificationInfo(Context context, AttributeSet attrs) {
124 super(context, attrs);
125 }
126
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400127 // Specify a CheckSaveListener to override when/if the user's changes are committed.
128 public interface CheckSaveListener {
129 // Invoked when importance has changed and the NotificationInfo wants to try to save it.
130 // Listener should run saveImportance unless the change should be canceled.
Eliot Courtney47098cb2017-10-18 17:30:30 +0900131 void checkSave(Runnable saveImportance, StatusBarNotification sbn);
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400132 }
133
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500134 public interface OnSettingsClickListener {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500135 void onClick(View v, NotificationChannel channel, int appUid);
Mady Mellor87d79452017-01-10 11:52:52 -0800136 }
137
Julia Reynolds3aedded2017-03-31 14:42:09 -0400138 public interface OnAppSettingsClickListener {
139 void onClick(View v, Intent intent);
140 }
141
Rohan Shah524cf7b2018-03-15 14:40:02 -0700142 @VisibleForTesting
143 void bindNotification(
144 final PackageManager pm,
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500145 final INotificationManager iNotificationManager,
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500146 final String pkg,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500147 final NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700148 final int numUniqueChannelsInRow,
Julia Reynolds3aedded2017-03-31 14:42:09 -0400149 final StatusBarNotification sbn,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500150 final CheckSaveListener checkSaveListener,
151 final OnSettingsClickListener onSettingsClick,
152 final OnAppSettingsClickListener onAppSettingsClick,
Rohan Shah63411fc2018-03-28 19:05:52 -0700153 boolean isNonblockable)
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500154 throws RemoteException {
Rohan Shahca0447e2018-03-30 15:18:27 -0700155 bindNotification(pm, iNotificationManager, pkg, notificationChannel,
156 numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
157 onAppSettingsClick, isNonblockable, false /* isBlockingHelper */,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700158 false /* isUserSentimentNegative */);
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500159 }
160
Rohan Shah524cf7b2018-03-15 14:40:02 -0700161 public void bindNotification(
162 PackageManager pm,
163 INotificationManager iNotificationManager,
164 String pkg,
165 NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700166 int numUniqueChannelsInRow,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700167 StatusBarNotification sbn,
168 CheckSaveListener checkSaveListener,
169 OnSettingsClickListener onSettingsClick,
170 OnAppSettingsClickListener onAppSettingsClick,
Rohan Shah63411fc2018-03-28 19:05:52 -0700171 boolean isNonblockable,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700172 boolean isForBlockingHelper,
173 boolean isUserSentimentNegative)
174 throws RemoteException {
Mady Mellor87d79452017-01-10 11:52:52 -0800175 mINotificationManager = iNotificationManager;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700176 mMetricsLogger = Dependency.get(MetricsLogger.class);
Rohan Shahca0447e2018-03-30 15:18:27 -0700177 mPackageName = pkg;
178 mNumUniqueChannelsInRow = numUniqueChannelsInRow;
Julia Reynolds3aedded2017-03-31 14:42:09 -0400179 mSbn = sbn;
180 mPm = pm;
181 mAppSettingsClickListener = onAppSettingsClick;
Rohan Shahca0447e2018-03-30 15:18:27 -0700182 mAppName = mPackageName;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500183 mCheckSaveListener = checkSaveListener;
184 mOnSettingsClickListener = onSettingsClick;
185 mSingleNotificationChannel = notificationChannel;
186 mStartingUserImportance = mChosenImportance = mSingleNotificationChannel.getImportance();
Rohan Shah524cf7b2018-03-15 14:40:02 -0700187 mNegativeUserSentiment = isUserSentimentNegative;
Rohan Shah63411fc2018-03-28 19:05:52 -0700188 mIsNonblockable = isNonblockable;
Julia Reynoldse0341482018-03-08 14:42:50 -0500189 mIsForeground =
190 (mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700191 mIsForBlockingHelper = isForBlockingHelper;
Rohan Shahdbd64e72018-03-28 14:46:50 -0700192 mAppUid = mSbn.getUid();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500193
Julia Reynolds437cdb12018-01-03 12:27:24 -0500194 int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400195 pkg, mAppUid, false /* includeDeleted */);
Rohan Shahca0447e2018-03-30 15:18:27 -0700196 if (mNumUniqueChannelsInRow == 0) {
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400197 throw new IllegalArgumentException("bindNotification requires at least one channel");
198 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500199 // Special behavior for the Default channel if no other channels have been defined.
Rohan Shahca0447e2018-03-30 15:18:27 -0700200 mIsSingleDefaultChannel = mNumUniqueChannelsInRow == 1
Rohan Shahdbd64e72018-03-28 14:46:50 -0700201 && mSingleNotificationChannel.getId().equals(
202 NotificationChannel.DEFAULT_CHANNEL_ID)
203 && numTotalChannels == 1;
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400204 }
205
Julia Reynolds437cdb12018-01-03 12:27:24 -0500206 bindHeader();
207 bindPrompt();
208 bindButtons();
209 }
210
211 private void bindHeader() throws RemoteException {
212 // Package name
213 Drawable pkgicon = null;
214 ApplicationInfo info;
215 try {
Rohan Shahca0447e2018-03-30 15:18:27 -0700216 info = mPm.getApplicationInfo(
217 mPackageName,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500218 PackageManager.MATCH_UNINSTALLED_PACKAGES
219 | PackageManager.MATCH_DISABLED_COMPONENTS
220 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
221 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
222 if (info != null) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500223 mAppName = String.valueOf(mPm.getApplicationLabel(info));
224 pkgicon = mPm.getApplicationIcon(info);
Geoffrey Pitschee8c81e2017-03-23 11:38:56 -0400225 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500226 } catch (PackageManager.NameNotFoundException e) {
227 // app is gone, just show package name and generic icon
228 pkgicon = mPm.getDefaultActivityIcon();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500229 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500230 ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400231 ((TextView) findViewById(R.id.pkgname)).setText(mAppName);
Mady Mellor87d79452017-01-10 11:52:52 -0800232
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500233 // Set group information if this channel has an associated group.
234 CharSequence groupName = null;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500235 if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
236 final NotificationChannelGroup notificationChannelGroup =
Julia Reynolds437cdb12018-01-03 12:27:24 -0500237 mINotificationManager.getNotificationChannelGroupForPackage(
Rohan Shahca0447e2018-03-30 15:18:27 -0700238 mSingleNotificationChannel.getGroup(), mPackageName, mAppUid);
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500239 if (notificationChannelGroup != null) {
240 groupName = notificationChannelGroup.getName();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500241 }
Mady Mellor87d79452017-01-10 11:52:52 -0800242 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500243 TextView groupNameView = findViewById(R.id.group_name);
244 TextView groupDividerView = findViewById(R.id.pkg_group_divider);
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500245 if (groupName != null) {
246 groupNameView.setText(groupName);
247 groupNameView.setVisibility(View.VISIBLE);
248 groupDividerView.setVisibility(View.VISIBLE);
249 } else {
250 groupNameView.setVisibility(View.GONE);
251 groupDividerView.setVisibility(View.GONE);
252 }
Mady Mellor87d79452017-01-10 11:52:52 -0800253
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500254 // Settings button.
Julia Reynolds437cdb12018-01-03 12:27:24 -0500255 final View settingsButton = findViewById(R.id.info);
256 if (mAppUid >= 0 && mOnSettingsClickListener != null) {
Geoffrey Pitsch029a3fa2017-03-30 13:01:57 -0400257 settingsButton.setVisibility(View.VISIBLE);
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500258 final int appUidF = mAppUid;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500259 settingsButton.setOnClickListener(
260 (View view) -> {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700261 logBlockingHelperCounter(
262 NotificationCounters.BLOCKING_HELPER_NOTIF_SETTINGS);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500263 mOnSettingsClickListener.onClick(view,
Rohan Shahca0447e2018-03-30 15:18:27 -0700264 mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500265 appUidF);
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500266 });
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500267 } else {
268 settingsButton.setVisibility(View.GONE);
269 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500270 }
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500271
Julia Reynolds437cdb12018-01-03 12:27:24 -0500272 private void bindPrompt() {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500273 final TextView blockPrompt = findViewById(R.id.block_prompt);
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500274 bindName();
Rohan Shah63411fc2018-03-28 19:05:52 -0700275 if (mIsNonblockable) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500276 blockPrompt.setText(R.string.notification_unblockable_desc);
277 } else {
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500278 if (mNegativeUserSentiment) {
279 blockPrompt.setText(R.string.inline_blocking_helper);
Rohan Shahca0447e2018-03-30 15:18:27 -0700280 } else if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500281 blockPrompt.setText(R.string.inline_keep_showing_app);
282 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500283 blockPrompt.setText(R.string.inline_keep_showing);
284 }
285 }
Mady Mellor87d79452017-01-10 11:52:52 -0800286 }
287
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500288 private void bindName() {
289 final TextView channelName = findViewById(R.id.channel_name);
Rohan Shahca0447e2018-03-30 15:18:27 -0700290 if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500291 channelName.setVisibility(View.GONE);
292 } else {
293 channelName.setText(mSingleNotificationChannel.getName());
294 }
295 }
296
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700297 @VisibleForTesting
298 void logBlockingHelperCounter(String counterTag) {
299 if (mIsForBlockingHelper) {
300 mMetricsLogger.count(counterTag, 1);
301 }
302 }
303
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400304 private boolean hasImportanceChanged() {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500305 return mSingleNotificationChannel != null && mStartingUserImportance != mChosenImportance;
Mady Mellor87d79452017-01-10 11:52:52 -0800306 }
307
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500308 private void saveImportance() {
Rohan Shahca0447e2018-03-30 15:18:27 -0700309 if (!mIsNonblockable) {
Gus Prevasf4f0b062018-09-24 17:15:32 -0400310 updateImportance();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500311 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700312 }
313
314 /**
315 * Commits the updated importance values on the background thread.
316 */
317 private void updateImportance() {
Mady Mellor87d79452017-01-10 11:52:52 -0800318 MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500319 mChosenImportance - mStartingUserImportance);
Rohan Shahca0447e2018-03-30 15:18:27 -0700320
321 Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
322 bgHandler.post(new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
323 mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
324 mStartingUserImportance, mChosenImportance));
Mady Mellor87d79452017-01-10 11:52:52 -0800325 }
326
Julia Reynolds437cdb12018-01-03 12:27:24 -0500327 private void bindButtons() {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700328 // Set up stay-in-notification actions
Julia Reynolds437cdb12018-01-03 12:27:24 -0500329 View block = findViewById(R.id.block);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500330 TextView keep = findViewById(R.id.keep);
Julia Reynoldse0341482018-03-08 14:42:50 -0500331 View minimize = findViewById(R.id.minimize);
Rohan Shah524cf7b2018-03-15 14:40:02 -0700332
333 findViewById(R.id.undo).setOnClickListener(mOnUndo);
Rohan Shahca0447e2018-03-30 15:18:27 -0700334 block.setOnClickListener(mOnStopOrMinimizeNotifications);
Rohan Shah524cf7b2018-03-15 14:40:02 -0700335 keep.setOnClickListener(mOnKeepShowing);
Rohan Shahca0447e2018-03-30 15:18:27 -0700336 minimize.setOnClickListener(mOnStopOrMinimizeNotifications);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500337
Rohan Shah63411fc2018-03-28 19:05:52 -0700338 if (mIsNonblockable) {
Rohan Shah53529aa2018-04-12 00:14:50 -0400339 keep.setText(android.R.string.ok);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500340 block.setVisibility(GONE);
Julia Reynoldse0341482018-03-08 14:42:50 -0500341 minimize.setVisibility(GONE);
342 } else if (mIsForeground) {
343 block.setVisibility(GONE);
344 minimize.setVisibility(VISIBLE);
345 } else if (!mIsForeground) {
346 block.setVisibility(VISIBLE);
347 minimize.setVisibility(GONE);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500348 }
349
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000350 // Set up app settings link (i.e. Customize)
Julia Reynolds437cdb12018-01-03 12:27:24 -0500351 TextView settingsLinkView = findViewById(R.id.app_settings);
Rohan Shahca0447e2018-03-30 15:18:27 -0700352 Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName, mSingleNotificationChannel,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500353 mSbn.getId(), mSbn.getTag());
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000354 if (!mIsForBlockingHelper
355 && settingsIntent != null
Julia Reynolds437cdb12018-01-03 12:27:24 -0500356 && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
Julia Reynoldse0341482018-03-08 14:42:50 -0500357 settingsLinkView.setVisibility(VISIBLE);
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000358 settingsLinkView.setText(mContext.getString(R.string.notification_app_settings));
Julia Reynolds437cdb12018-01-03 12:27:24 -0500359 settingsLinkView.setOnClickListener((View view) -> {
360 mAppSettingsClickListener.onClick(view, settingsIntent);
361 });
Mady Mellor87d79452017-01-10 11:52:52 -0800362 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500363 settingsLinkView.setVisibility(View.GONE);
Mady Mellor87d79452017-01-10 11:52:52 -0800364 }
365 }
366
Julia Reynolds437cdb12018-01-03 12:27:24 -0500367 private void swapContent(boolean showPrompt) {
368 if (mExpandAnimation != null) {
369 mExpandAnimation.cancel();
370 }
Mady Mellor87d79452017-01-10 11:52:52 -0800371
Julia Reynoldse0341482018-03-08 14:42:50 -0500372 View prompt = findViewById(R.id.prompt);
373 ViewGroup confirmation = findViewById(R.id.confirmation);
374 TextView confirmationText = findViewById(R.id.confirmation_text);
375 View header = findViewById(R.id.header);
376
Julia Reynolds437cdb12018-01-03 12:27:24 -0500377 if (showPrompt) {
378 mChosenImportance = mStartingUserImportance;
Julia Reynoldse0341482018-03-08 14:42:50 -0500379 } else if (mIsForeground) {
380 mChosenImportance = IMPORTANCE_MIN;
381 confirmationText.setText(R.string.notification_channel_minimized);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500382 } else {
383 mChosenImportance = IMPORTANCE_NONE;
Julia Reynoldse0341482018-03-08 14:42:50 -0500384 confirmationText.setText(R.string.notification_channel_disabled);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500385 }
386
Julia Reynolds437cdb12018-01-03 12:27:24 -0500387 ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA,
388 prompt.getAlpha(), showPrompt ? 1f : 0f);
389 promptAnim.setInterpolator(showPrompt ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
390 ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA,
391 confirmation.getAlpha(), showPrompt ? 0f : 1f);
392 confirmAnim.setInterpolator(showPrompt ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN);
393
394 prompt.setVisibility(showPrompt ? VISIBLE : GONE);
395 confirmation.setVisibility(showPrompt ? GONE : VISIBLE);
Julia Reynoldse0341482018-03-08 14:42:50 -0500396 header.setVisibility(showPrompt ? VISIBLE : GONE);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500397
398 mExpandAnimation = new AnimatorSet();
399 mExpandAnimation.playTogether(promptAnim, confirmAnim);
400 mExpandAnimation.setDuration(150);
401 mExpandAnimation.addListener(new AnimatorListenerAdapter() {
402 boolean cancelled = false;
403
404 @Override
405 public void onAnimationCancel(Animator animation) {
406 cancelled = true;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500407 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500408
409 @Override
410 public void onAnimationEnd(Animator animation) {
411 if (!cancelled) {
412 prompt.setVisibility(showPrompt ? VISIBLE : GONE);
413 confirmation.setVisibility(showPrompt ? GONE : VISIBLE);
414 }
415 }
Mady Mellor87d79452017-01-10 11:52:52 -0800416 });
Julia Reynolds437cdb12018-01-03 12:27:24 -0500417 mExpandAnimation.start();
Mady Mellor87d79452017-01-10 11:52:52 -0800418 }
419
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400420 @Override
421 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
422 super.onInitializeAccessibilityEvent(event);
423 if (mGutsContainer != null &&
424 event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
425 if (mGutsContainer.isExposed()) {
426 event.getText().add(mContext.getString(
427 R.string.notification_channel_controls_opened_accessibility, mAppName));
428 } else {
429 event.getText().add(mContext.getString(
430 R.string.notification_channel_controls_closed_accessibility, mAppName));
431 }
432 }
433 }
434
Julia Reynolds3aedded2017-03-31 14:42:09 -0400435 private Intent getAppSettingsIntent(PackageManager pm, String packageName,
436 NotificationChannel channel, int id, String tag) {
437 Intent intent = new Intent(Intent.ACTION_MAIN)
438 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
439 .setPackage(packageName);
440 final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
441 intent,
442 PackageManager.MATCH_DEFAULT_ONLY
443 );
444 if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
445 return null;
446 }
447 final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
448 intent.setClassName(activityInfo.packageName, activityInfo.name);
449 if (channel != null) {
450 intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
451 }
452 intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
453 intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
454 return intent;
455 }
456
Rohan Shahca0447e2018-03-30 15:18:27 -0700457 /**
458 * Closes the controls and commits the updated importance values (indirectly). If this view is
459 * being used to show the blocking helper, this will immediately dismiss the blocking helper and
460 * commit the updated importance.
461 *
462 * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
463 * user does not have the ability to undo the action anymore. See {@link #swapContent(boolean)}
464 * for where undo is handled.
465 */
Rohan Shah524cf7b2018-03-15 14:40:02 -0700466 @VisibleForTesting
467 void closeControls(View v) {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700468 int[] parentLoc = new int[2];
469 int[] targetLoc = new int[2];
470 mGutsContainer.getLocationOnScreen(parentLoc);
471 v.getLocationOnScreen(targetLoc);
472 final int centerX = v.getWidth() / 2;
473 final int centerY = v.getHeight() / 2;
474 final int x = targetLoc[0] - parentLoc[0] + centerX;
475 final int y = targetLoc[1] - parentLoc[1] + centerY;
476 mGutsContainer.closeControls(x, y, true /* save */, false /* force */);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500477 }
478
Mady Mellor87d79452017-01-10 11:52:52 -0800479 @Override
Mady Mellor95d743c2017-01-10 12:05:27 -0800480 public void setGutsParent(NotificationGuts guts) {
481 mGutsContainer = guts;
Mady Mellor87d79452017-01-10 11:52:52 -0800482 }
483
484 @Override
Mady Mellor434180c2017-02-13 11:29:42 -0800485 public boolean willBeRemoved() {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500486 return hasImportanceChanged();
Mady Mellor434180c2017-02-13 11:29:42 -0800487 }
488
489 @Override
Lucas Dupin9b08c012018-05-16 19:53:32 -0700490 public boolean shouldBeSaved() {
491 return hasImportanceChanged();
492 }
493
494 @Override
Mady Mellor87d79452017-01-10 11:52:52 -0800495 public View getContentView() {
496 return this;
497 }
498
499 @Override
Mady Mellorc2dbe492017-03-30 13:22:03 -0700500 public boolean handleCloseControls(boolean save, boolean force) {
Julia Reynoldsc65656a2018-02-12 09:55:14 -0500501 // Save regardless of the importance so we can lock the importance field if the user wants
502 // to keep getting notifications
503 if (save) {
Rohan Shahca0447e2018-03-30 15:18:27 -0700504 saveImportance();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500505 }
Rohan Shahdd588c72018-05-09 20:32:15 -0700506 logBlockingHelperCounter(mExitReason);
Mady Mellor87d79452017-01-10 11:52:52 -0800507 return false;
508 }
Mady Mellore09fb702017-03-30 13:23:29 -0700509
510 @Override
511 public int getActualHeight() {
512 return getHeight();
513 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700514
Gus Prevasf4f0b062018-09-24 17:15:32 -0400515 @VisibleForTesting
516 public boolean isAnimating() {
517 return mExpandAnimation != null && mExpandAnimation.isRunning();
518 }
519
Rohan Shahca0447e2018-03-30 15:18:27 -0700520 /**
521 * Runnable to either update the given channel (with a new importance value) or, if no channel
522 * is provided, update notifications enabled state for the package.
523 */
524 private static class UpdateImportanceRunnable implements Runnable {
525 private final INotificationManager mINotificationManager;
526 private final String mPackageName;
527 private final int mAppUid;
528 private final @Nullable NotificationChannel mChannelToUpdate;
529 private final int mCurrentImportance;
530 private final int mNewImportance;
531
532
533 public UpdateImportanceRunnable(INotificationManager notificationManager,
534 String packageName, int appUid, @Nullable NotificationChannel channelToUpdate,
535 int currentImportance, int newImportance) {
536 mINotificationManager = notificationManager;
537 mPackageName = packageName;
538 mAppUid = appUid;
539 mChannelToUpdate = channelToUpdate;
540 mCurrentImportance = currentImportance;
541 mNewImportance = newImportance;
542 }
543
544 @Override
545 public void run() {
546 try {
547 if (mChannelToUpdate != null) {
548 mChannelToUpdate.setImportance(mNewImportance);
549 mChannelToUpdate.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
550 mINotificationManager.updateNotificationChannelForPackage(
551 mPackageName, mAppUid, mChannelToUpdate);
552 } else {
553 // For notifications with more than one channel, update notification enabled
554 // state. If the importance was lowered, we disable notifications.
Rohan Shah590e1b22018-04-10 23:48:47 -0400555 mINotificationManager.setNotificationsEnabledWithImportanceLockForPackage(
Rohan Shahca0447e2018-03-30 15:18:27 -0700556 mPackageName, mAppUid, mNewImportance >= mCurrentImportance);
557 }
558 } catch (RemoteException e) {
559 Log.e(TAG, "Unable to update notification importance", e);
560 }
561 }
562 }
Mady Mellor87d79452017-01-10 11:52:52 -0800563}