blob: ec49f436b7d0f8f888481343fb483c8ac7fa3829 [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 Shahda5dcdd2018-04-27 17:21:50 -070094 /** Counter tag that describes how the user exit or quit out of this view. */
95 private String mExitReasonCounter = NotificationCounters.BLOCKING_HELPER_DISMISSED;
96
97 private OnClickListener mOnKeepShowing = v -> {
98 mExitReasonCounter = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
99 closeControls(v);
100 };
Julia Reynolds437cdb12018-01-03 12:27:24 -0500101
Rohan Shahca0447e2018-03-30 15:18:27 -0700102 private OnClickListener mOnStopOrMinimizeNotifications = v -> {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700103 mExitReasonCounter = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500104 swapContent(false);
105 };
106
107 private OnClickListener mOnUndo = v -> {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700108 // Reset exit counter that we'll log and record an undo event separately (not an exit event)
109 mExitReasonCounter = NotificationCounters.BLOCKING_HELPER_DISMISSED;
110 logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500111 swapContent(true);
112 };
113
Mady Mellor87d79452017-01-10 11:52:52 -0800114 public NotificationInfo(Context context, AttributeSet attrs) {
115 super(context, attrs);
116 }
117
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400118 // Specify a CheckSaveListener to override when/if the user's changes are committed.
119 public interface CheckSaveListener {
120 // Invoked when importance has changed and the NotificationInfo wants to try to save it.
121 // Listener should run saveImportance unless the change should be canceled.
Eliot Courtney47098cb2017-10-18 17:30:30 +0900122 void checkSave(Runnable saveImportance, StatusBarNotification sbn);
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400123 }
124
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500125 public interface OnSettingsClickListener {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500126 void onClick(View v, NotificationChannel channel, int appUid);
Mady Mellor87d79452017-01-10 11:52:52 -0800127 }
128
Julia Reynolds3aedded2017-03-31 14:42:09 -0400129 public interface OnAppSettingsClickListener {
130 void onClick(View v, Intent intent);
131 }
132
Rohan Shah524cf7b2018-03-15 14:40:02 -0700133 @VisibleForTesting
134 void bindNotification(
135 final PackageManager pm,
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500136 final INotificationManager iNotificationManager,
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500137 final String pkg,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500138 final NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700139 final int numUniqueChannelsInRow,
Julia Reynolds3aedded2017-03-31 14:42:09 -0400140 final StatusBarNotification sbn,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500141 final CheckSaveListener checkSaveListener,
142 final OnSettingsClickListener onSettingsClick,
143 final OnAppSettingsClickListener onAppSettingsClick,
Rohan Shah63411fc2018-03-28 19:05:52 -0700144 boolean isNonblockable)
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500145 throws RemoteException {
Rohan Shahca0447e2018-03-30 15:18:27 -0700146 bindNotification(pm, iNotificationManager, pkg, notificationChannel,
147 numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
148 onAppSettingsClick, isNonblockable, false /* isBlockingHelper */,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700149 false /* isUserSentimentNegative */);
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500150 }
151
Rohan Shah524cf7b2018-03-15 14:40:02 -0700152 public void bindNotification(
153 PackageManager pm,
154 INotificationManager iNotificationManager,
155 String pkg,
156 NotificationChannel notificationChannel,
Rohan Shahca0447e2018-03-30 15:18:27 -0700157 int numUniqueChannelsInRow,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700158 StatusBarNotification sbn,
159 CheckSaveListener checkSaveListener,
160 OnSettingsClickListener onSettingsClick,
161 OnAppSettingsClickListener onAppSettingsClick,
Rohan Shah63411fc2018-03-28 19:05:52 -0700162 boolean isNonblockable,
Rohan Shah524cf7b2018-03-15 14:40:02 -0700163 boolean isForBlockingHelper,
164 boolean isUserSentimentNegative)
165 throws RemoteException {
Mady Mellor87d79452017-01-10 11:52:52 -0800166 mINotificationManager = iNotificationManager;
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700167 mMetricsLogger = Dependency.get(MetricsLogger.class);
Rohan Shahca0447e2018-03-30 15:18:27 -0700168 mPackageName = pkg;
169 mNumUniqueChannelsInRow = numUniqueChannelsInRow;
Julia Reynolds3aedded2017-03-31 14:42:09 -0400170 mSbn = sbn;
171 mPm = pm;
172 mAppSettingsClickListener = onAppSettingsClick;
Rohan Shahca0447e2018-03-30 15:18:27 -0700173 mAppName = mPackageName;
Julia Reynolds437cdb12018-01-03 12:27:24 -0500174 mCheckSaveListener = checkSaveListener;
175 mOnSettingsClickListener = onSettingsClick;
176 mSingleNotificationChannel = notificationChannel;
177 mStartingUserImportance = mChosenImportance = mSingleNotificationChannel.getImportance();
Rohan Shah524cf7b2018-03-15 14:40:02 -0700178 mNegativeUserSentiment = isUserSentimentNegative;
Rohan Shah63411fc2018-03-28 19:05:52 -0700179 mIsNonblockable = isNonblockable;
Julia Reynoldse0341482018-03-08 14:42:50 -0500180 mIsForeground =
181 (mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700182 mIsForBlockingHelper = isForBlockingHelper;
Rohan Shahdbd64e72018-03-28 14:46:50 -0700183 mAppUid = mSbn.getUid();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500184
Julia Reynolds437cdb12018-01-03 12:27:24 -0500185 int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400186 pkg, mAppUid, false /* includeDeleted */);
Rohan Shahca0447e2018-03-30 15:18:27 -0700187 if (mNumUniqueChannelsInRow == 0) {
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400188 throw new IllegalArgumentException("bindNotification requires at least one channel");
189 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500190 // Special behavior for the Default channel if no other channels have been defined.
Rohan Shahca0447e2018-03-30 15:18:27 -0700191 mIsSingleDefaultChannel = mNumUniqueChannelsInRow == 1
Rohan Shahdbd64e72018-03-28 14:46:50 -0700192 && mSingleNotificationChannel.getId().equals(
193 NotificationChannel.DEFAULT_CHANNEL_ID)
194 && numTotalChannels == 1;
Geoffrey Pitschd034d292017-05-12 11:59:20 -0400195 }
196
Julia Reynolds437cdb12018-01-03 12:27:24 -0500197 bindHeader();
198 bindPrompt();
199 bindButtons();
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700200
201 logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_SHOWN);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500202 }
203
204 private void bindHeader() throws RemoteException {
205 // Package name
206 Drawable pkgicon = null;
207 ApplicationInfo info;
208 try {
Rohan Shahca0447e2018-03-30 15:18:27 -0700209 info = mPm.getApplicationInfo(
210 mPackageName,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500211 PackageManager.MATCH_UNINSTALLED_PACKAGES
212 | PackageManager.MATCH_DISABLED_COMPONENTS
213 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
214 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
215 if (info != null) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500216 mAppName = String.valueOf(mPm.getApplicationLabel(info));
217 pkgicon = mPm.getApplicationIcon(info);
Geoffrey Pitschee8c81e2017-03-23 11:38:56 -0400218 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500219 } catch (PackageManager.NameNotFoundException e) {
220 // app is gone, just show package name and generic icon
221 pkgicon = mPm.getDefaultActivityIcon();
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500222 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500223 ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400224 ((TextView) findViewById(R.id.pkgname)).setText(mAppName);
Mady Mellor87d79452017-01-10 11:52:52 -0800225
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500226 // Set group information if this channel has an associated group.
227 CharSequence groupName = null;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500228 if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
229 final NotificationChannelGroup notificationChannelGroup =
Julia Reynolds437cdb12018-01-03 12:27:24 -0500230 mINotificationManager.getNotificationChannelGroupForPackage(
Rohan Shahca0447e2018-03-30 15:18:27 -0700231 mSingleNotificationChannel.getGroup(), mPackageName, mAppUid);
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500232 if (notificationChannelGroup != null) {
233 groupName = notificationChannelGroup.getName();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500234 }
Mady Mellor87d79452017-01-10 11:52:52 -0800235 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500236 TextView groupNameView = findViewById(R.id.group_name);
237 TextView groupDividerView = findViewById(R.id.pkg_group_divider);
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500238 if (groupName != null) {
239 groupNameView.setText(groupName);
240 groupNameView.setVisibility(View.VISIBLE);
241 groupDividerView.setVisibility(View.VISIBLE);
242 } else {
243 groupNameView.setVisibility(View.GONE);
244 groupDividerView.setVisibility(View.GONE);
245 }
Mady Mellor87d79452017-01-10 11:52:52 -0800246
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500247 // Settings button.
Julia Reynolds437cdb12018-01-03 12:27:24 -0500248 final View settingsButton = findViewById(R.id.info);
249 if (mAppUid >= 0 && mOnSettingsClickListener != null) {
Geoffrey Pitsch029a3fa2017-03-30 13:01:57 -0400250 settingsButton.setVisibility(View.VISIBLE);
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500251 final int appUidF = mAppUid;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500252 settingsButton.setOnClickListener(
253 (View view) -> {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700254 logBlockingHelperCounter(
255 NotificationCounters.BLOCKING_HELPER_NOTIF_SETTINGS);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500256 mOnSettingsClickListener.onClick(view,
Rohan Shahca0447e2018-03-30 15:18:27 -0700257 mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500258 appUidF);
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500259 });
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500260 } else {
261 settingsButton.setVisibility(View.GONE);
262 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500263 }
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500264
Julia Reynolds437cdb12018-01-03 12:27:24 -0500265 private void bindPrompt() {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500266 final TextView blockPrompt = findViewById(R.id.block_prompt);
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500267 bindName();
Rohan Shah63411fc2018-03-28 19:05:52 -0700268 if (mIsNonblockable) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500269 blockPrompt.setText(R.string.notification_unblockable_desc);
270 } else {
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500271 if (mNegativeUserSentiment) {
272 blockPrompt.setText(R.string.inline_blocking_helper);
Rohan Shahca0447e2018-03-30 15:18:27 -0700273 } else if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500274 blockPrompt.setText(R.string.inline_keep_showing_app);
275 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500276 blockPrompt.setText(R.string.inline_keep_showing);
277 }
278 }
Mady Mellor87d79452017-01-10 11:52:52 -0800279 }
280
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500281 private void bindName() {
282 final TextView channelName = findViewById(R.id.channel_name);
Rohan Shahca0447e2018-03-30 15:18:27 -0700283 if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
Julia Reynolds0ef7d842018-01-24 17:50:31 -0500284 channelName.setVisibility(View.GONE);
285 } else {
286 channelName.setText(mSingleNotificationChannel.getName());
287 }
288 }
289
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700290 @VisibleForTesting
291 void logBlockingHelperCounter(String counterTag) {
292 if (mIsForBlockingHelper) {
293 mMetricsLogger.count(counterTag, 1);
294 }
295 }
296
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400297 private boolean hasImportanceChanged() {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500298 return mSingleNotificationChannel != null && mStartingUserImportance != mChosenImportance;
Mady Mellor87d79452017-01-10 11:52:52 -0800299 }
300
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500301 private void saveImportance() {
Rohan Shahca0447e2018-03-30 15:18:27 -0700302 if (!mIsNonblockable) {
303 if (mCheckSaveListener != null) {
304 mCheckSaveListener.checkSave(this::updateImportance, mSbn);
305 } else {
306 updateImportance();
307 }
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500308 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700309 }
310
311 /**
312 * Commits the updated importance values on the background thread.
313 */
314 private void updateImportance() {
Mady Mellor87d79452017-01-10 11:52:52 -0800315 MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500316 mChosenImportance - mStartingUserImportance);
Rohan Shahca0447e2018-03-30 15:18:27 -0700317
318 Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
319 bgHandler.post(new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
320 mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
321 mStartingUserImportance, mChosenImportance));
Mady Mellor87d79452017-01-10 11:52:52 -0800322 }
323
Julia Reynolds437cdb12018-01-03 12:27:24 -0500324 private void bindButtons() {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700325 // Set up stay-in-notification actions
Julia Reynolds437cdb12018-01-03 12:27:24 -0500326 View block = findViewById(R.id.block);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500327 TextView keep = findViewById(R.id.keep);
Julia Reynoldse0341482018-03-08 14:42:50 -0500328 View minimize = findViewById(R.id.minimize);
Rohan Shah524cf7b2018-03-15 14:40:02 -0700329
330 findViewById(R.id.undo).setOnClickListener(mOnUndo);
Rohan Shahca0447e2018-03-30 15:18:27 -0700331 block.setOnClickListener(mOnStopOrMinimizeNotifications);
Rohan Shah524cf7b2018-03-15 14:40:02 -0700332 keep.setOnClickListener(mOnKeepShowing);
Rohan Shahca0447e2018-03-30 15:18:27 -0700333 minimize.setOnClickListener(mOnStopOrMinimizeNotifications);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500334
Rohan Shah63411fc2018-03-28 19:05:52 -0700335 if (mIsNonblockable) {
Rohan Shah53529aa2018-04-12 00:14:50 -0400336 keep.setText(android.R.string.ok);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500337 block.setVisibility(GONE);
Julia Reynoldse0341482018-03-08 14:42:50 -0500338 minimize.setVisibility(GONE);
339 } else if (mIsForeground) {
340 block.setVisibility(GONE);
341 minimize.setVisibility(VISIBLE);
342 } else if (!mIsForeground) {
343 block.setVisibility(VISIBLE);
344 minimize.setVisibility(GONE);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500345 }
346
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000347 // Set up app settings link (i.e. Customize)
Julia Reynolds437cdb12018-01-03 12:27:24 -0500348 TextView settingsLinkView = findViewById(R.id.app_settings);
Rohan Shahca0447e2018-03-30 15:18:27 -0700349 Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName, mSingleNotificationChannel,
Julia Reynolds437cdb12018-01-03 12:27:24 -0500350 mSbn.getId(), mSbn.getTag());
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000351 if (!mIsForBlockingHelper
352 && settingsIntent != null
Julia Reynolds437cdb12018-01-03 12:27:24 -0500353 && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
Julia Reynoldse0341482018-03-08 14:42:50 -0500354 settingsLinkView.setVisibility(VISIBLE);
Rohan Shah7c6b37a2018-03-30 21:07:45 +0000355 settingsLinkView.setText(mContext.getString(R.string.notification_app_settings));
Julia Reynolds437cdb12018-01-03 12:27:24 -0500356 settingsLinkView.setOnClickListener((View view) -> {
357 mAppSettingsClickListener.onClick(view, settingsIntent);
358 });
Mady Mellor87d79452017-01-10 11:52:52 -0800359 } else {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500360 settingsLinkView.setVisibility(View.GONE);
Mady Mellor87d79452017-01-10 11:52:52 -0800361 }
362 }
363
Julia Reynolds437cdb12018-01-03 12:27:24 -0500364 private void swapContent(boolean showPrompt) {
365 if (mExpandAnimation != null) {
366 mExpandAnimation.cancel();
367 }
Mady Mellor87d79452017-01-10 11:52:52 -0800368
Julia Reynoldse0341482018-03-08 14:42:50 -0500369 View prompt = findViewById(R.id.prompt);
370 ViewGroup confirmation = findViewById(R.id.confirmation);
371 TextView confirmationText = findViewById(R.id.confirmation_text);
372 View header = findViewById(R.id.header);
373
Julia Reynolds437cdb12018-01-03 12:27:24 -0500374 if (showPrompt) {
375 mChosenImportance = mStartingUserImportance;
Julia Reynoldse0341482018-03-08 14:42:50 -0500376 } else if (mIsForeground) {
377 mChosenImportance = IMPORTANCE_MIN;
378 confirmationText.setText(R.string.notification_channel_minimized);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500379 } else {
380 mChosenImportance = IMPORTANCE_NONE;
Julia Reynoldse0341482018-03-08 14:42:50 -0500381 confirmationText.setText(R.string.notification_channel_disabled);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500382 }
383
Julia Reynolds437cdb12018-01-03 12:27:24 -0500384 ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA,
385 prompt.getAlpha(), showPrompt ? 1f : 0f);
386 promptAnim.setInterpolator(showPrompt ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
387 ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA,
388 confirmation.getAlpha(), showPrompt ? 0f : 1f);
389 confirmAnim.setInterpolator(showPrompt ? Interpolators.ALPHA_OUT : Interpolators.ALPHA_IN);
390
391 prompt.setVisibility(showPrompt ? VISIBLE : GONE);
392 confirmation.setVisibility(showPrompt ? GONE : VISIBLE);
Julia Reynoldse0341482018-03-08 14:42:50 -0500393 header.setVisibility(showPrompt ? VISIBLE : GONE);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500394
395 mExpandAnimation = new AnimatorSet();
396 mExpandAnimation.playTogether(promptAnim, confirmAnim);
397 mExpandAnimation.setDuration(150);
398 mExpandAnimation.addListener(new AnimatorListenerAdapter() {
399 boolean cancelled = false;
400
401 @Override
402 public void onAnimationCancel(Animator animation) {
403 cancelled = true;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500404 }
Julia Reynolds437cdb12018-01-03 12:27:24 -0500405
406 @Override
407 public void onAnimationEnd(Animator animation) {
408 if (!cancelled) {
409 prompt.setVisibility(showPrompt ? VISIBLE : GONE);
410 confirmation.setVisibility(showPrompt ? GONE : VISIBLE);
411 }
412 }
Mady Mellor87d79452017-01-10 11:52:52 -0800413 });
Julia Reynolds437cdb12018-01-03 12:27:24 -0500414 mExpandAnimation.start();
Mady Mellor87d79452017-01-10 11:52:52 -0800415 }
416
Geoffrey Pitschd94e7882017-04-06 09:52:11 -0400417 @Override
418 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
419 super.onInitializeAccessibilityEvent(event);
420 if (mGutsContainer != null &&
421 event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
422 if (mGutsContainer.isExposed()) {
423 event.getText().add(mContext.getString(
424 R.string.notification_channel_controls_opened_accessibility, mAppName));
425 } else {
426 event.getText().add(mContext.getString(
427 R.string.notification_channel_controls_closed_accessibility, mAppName));
428 }
429 }
430 }
431
Julia Reynolds3aedded2017-03-31 14:42:09 -0400432 private Intent getAppSettingsIntent(PackageManager pm, String packageName,
433 NotificationChannel channel, int id, String tag) {
434 Intent intent = new Intent(Intent.ACTION_MAIN)
435 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
436 .setPackage(packageName);
437 final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
438 intent,
439 PackageManager.MATCH_DEFAULT_ONLY
440 );
441 if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
442 return null;
443 }
444 final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
445 intent.setClassName(activityInfo.packageName, activityInfo.name);
446 if (channel != null) {
447 intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
448 }
449 intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
450 intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
451 return intent;
452 }
453
Rohan Shahca0447e2018-03-30 15:18:27 -0700454 /**
455 * Closes the controls and commits the updated importance values (indirectly). If this view is
456 * being used to show the blocking helper, this will immediately dismiss the blocking helper and
457 * commit the updated importance.
458 *
459 * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
460 * user does not have the ability to undo the action anymore. See {@link #swapContent(boolean)}
461 * for where undo is handled.
462 */
Rohan Shah524cf7b2018-03-15 14:40:02 -0700463 @VisibleForTesting
464 void closeControls(View v) {
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700465 int[] parentLoc = new int[2];
466 int[] targetLoc = new int[2];
467 mGutsContainer.getLocationOnScreen(parentLoc);
468 v.getLocationOnScreen(targetLoc);
469 final int centerX = v.getWidth() / 2;
470 final int centerY = v.getHeight() / 2;
471 final int x = targetLoc[0] - parentLoc[0] + centerX;
472 final int y = targetLoc[1] - parentLoc[1] + centerY;
473 mGutsContainer.closeControls(x, y, true /* save */, false /* force */);
Julia Reynolds437cdb12018-01-03 12:27:24 -0500474 }
475
Mady Mellor87d79452017-01-10 11:52:52 -0800476 @Override
Mady Mellor95d743c2017-01-10 12:05:27 -0800477 public void setGutsParent(NotificationGuts guts) {
478 mGutsContainer = guts;
Mady Mellor87d79452017-01-10 11:52:52 -0800479 }
480
481 @Override
Mady Mellor434180c2017-02-13 11:29:42 -0800482 public boolean willBeRemoved() {
Julia Reynolds437cdb12018-01-03 12:27:24 -0500483 return hasImportanceChanged();
Mady Mellor434180c2017-02-13 11:29:42 -0800484 }
485
486 @Override
Mady Mellor87d79452017-01-10 11:52:52 -0800487 public View getContentView() {
488 return this;
489 }
490
491 @Override
Mady Mellorc2dbe492017-03-30 13:22:03 -0700492 public boolean handleCloseControls(boolean save, boolean force) {
Julia Reynoldsc65656a2018-02-12 09:55:14 -0500493 // Save regardless of the importance so we can lock the importance field if the user wants
494 // to keep getting notifications
495 if (save) {
Rohan Shahca0447e2018-03-30 15:18:27 -0700496 saveImportance();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500497 }
Rohan Shahda5dcdd2018-04-27 17:21:50 -0700498 logBlockingHelperCounter(mExitReasonCounter);
Mady Mellor87d79452017-01-10 11:52:52 -0800499 return false;
500 }
Mady Mellore09fb702017-03-30 13:23:29 -0700501
502 @Override
503 public int getActualHeight() {
504 return getHeight();
505 }
Rohan Shahca0447e2018-03-30 15:18:27 -0700506
507 /**
508 * Runnable to either update the given channel (with a new importance value) or, if no channel
509 * is provided, update notifications enabled state for the package.
510 */
511 private static class UpdateImportanceRunnable implements Runnable {
512 private final INotificationManager mINotificationManager;
513 private final String mPackageName;
514 private final int mAppUid;
515 private final @Nullable NotificationChannel mChannelToUpdate;
516 private final int mCurrentImportance;
517 private final int mNewImportance;
518
519
520 public UpdateImportanceRunnable(INotificationManager notificationManager,
521 String packageName, int appUid, @Nullable NotificationChannel channelToUpdate,
522 int currentImportance, int newImportance) {
523 mINotificationManager = notificationManager;
524 mPackageName = packageName;
525 mAppUid = appUid;
526 mChannelToUpdate = channelToUpdate;
527 mCurrentImportance = currentImportance;
528 mNewImportance = newImportance;
529 }
530
531 @Override
532 public void run() {
533 try {
534 if (mChannelToUpdate != null) {
535 mChannelToUpdate.setImportance(mNewImportance);
536 mChannelToUpdate.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
537 mINotificationManager.updateNotificationChannelForPackage(
538 mPackageName, mAppUid, mChannelToUpdate);
539 } else {
540 // For notifications with more than one channel, update notification enabled
541 // state. If the importance was lowered, we disable notifications.
Rohan Shah590e1b22018-04-10 23:48:47 -0400542 mINotificationManager.setNotificationsEnabledWithImportanceLockForPackage(
Rohan Shahca0447e2018-03-30 15:18:27 -0700543 mPackageName, mAppUid, mNewImportance >= mCurrentImportance);
544 }
545 } catch (RemoteException e) {
546 Log.e(TAG, "Unable to update notification importance", e);
547 }
548 }
549 }
Mady Mellor87d79452017-01-10 11:52:52 -0800550}