blob: 21a0dc9f24e903a101a61b778b39b18bfc25fd35 [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
Mady Mellor87d79452017-01-10 11:52:52 -080019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.app.INotificationManager;
22import android.app.NotificationChannel;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -050023import android.app.NotificationChannelGroup;
Mady Mellor87d79452017-01-10 11:52:52 -080024import android.app.NotificationManager;
25import android.content.Context;
26import android.content.pm.ApplicationInfo;
27import android.content.pm.PackageInfo;
28import android.content.pm.PackageManager;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -050029import android.content.pm.ParceledListSlice;
Mady Mellor87d79452017-01-10 11:52:52 -080030import android.content.res.ColorStateList;
31import android.content.res.TypedArray;
32import android.graphics.Canvas;
33import android.graphics.drawable.Drawable;
34import android.os.Handler;
35import android.os.RemoteException;
36import android.os.ServiceManager;
37import android.service.notification.NotificationListenerService;
Mady Mellor87d79452017-01-10 11:52:52 -080038import android.util.AttributeSet;
39import android.util.Log;
40import android.view.View;
41import android.view.ViewAnimationUtils;
42import android.view.ViewGroup;
43import android.view.View.OnClickListener;
44import android.widget.CompoundButton;
45import android.widget.ImageView;
46import android.widget.LinearLayout;
Mady Mellor87d79452017-01-10 11:52:52 -080047import android.widget.SeekBar;
48import android.widget.Switch;
49import android.widget.TextView;
50
51import com.android.internal.logging.MetricsLogger;
52import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
53import com.android.settingslib.Utils;
54import com.android.systemui.Interpolators;
55import com.android.systemui.R;
Mady Mellor87d79452017-01-10 11:52:52 -080056import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener;
57import com.android.systemui.statusbar.stack.StackStateAnimator;
58
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050059import java.lang.IllegalArgumentException;
60import java.util.List;
Mady Mellor87d79452017-01-10 11:52:52 -080061import java.util.Set;
62
63/**
64 * The guts of a notification revealed when performing a long press.
65 */
Mady Mellor95d743c2017-01-10 12:05:27 -080066public class NotificationInfo extends LinearLayout implements NotificationGuts.GutsContent {
Mady Mellor87d79452017-01-10 11:52:52 -080067 private static final String TAG = "InfoGuts";
68
69 private INotificationManager mINotificationManager;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050070 private String mPkg;
71 private int mAppUid;
72 private List<NotificationChannel> mNotificationChannels;
73 private NotificationChannel mSingleNotificationChannel;
Mady Mellor87d79452017-01-10 11:52:52 -080074 private int mStartingUserImportance;
Mady Mellor87d79452017-01-10 11:52:52 -080075
Geoffrey Pitschdf44b602017-02-03 13:31:50 -050076 private TextView mNumChannelsView;
77 private View mChannelDisabledView;
Mady Mellor87d79452017-01-10 11:52:52 -080078 private Switch mChannelEnabledSwitch;
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -040079 private CheckSaveListener mCheckSaveListener;
Mady Mellor87d79452017-01-10 11:52:52 -080080
Mady Mellor95d743c2017-01-10 12:05:27 -080081 private NotificationGuts mGutsContainer;
Mady Mellor87d79452017-01-10 11:52:52 -080082
83 public NotificationInfo(Context context, AttributeSet attrs) {
84 super(context, attrs);
85 }
86
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -040087 // Specify a CheckSaveListener to override when/if the user's changes are committed.
88 public interface CheckSaveListener {
89 // Invoked when importance has changed and the NotificationInfo wants to try to save it.
90 // Listener should run saveImportance unless the change should be canceled.
91 void checkSave(Runnable saveImportance);
92 }
93
Jason Monk2a6ea9c2017-01-26 11:14:51 -050094 public interface OnSettingsClickListener {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -050095 void onClick(View v, NotificationChannel channel, int appUid);
Mady Mellor87d79452017-01-10 11:52:52 -080096 }
97
Jason Monk2a6ea9c2017-01-26 11:14:51 -050098 public void bindNotification(final PackageManager pm,
99 final INotificationManager iNotificationManager,
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500100 final String pkg,
101 final List<NotificationChannel> notificationChannels,
Mady Mellor87d79452017-01-10 11:52:52 -0800102 OnSettingsClickListener onSettingsClick,
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400103 OnClickListener onDoneClick,
104 CheckSaveListener checkSaveListener,
105 final Set<String> nonBlockablePkgs)
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500106 throws RemoteException {
Mady Mellor87d79452017-01-10 11:52:52 -0800107 mINotificationManager = iNotificationManager;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500108 mPkg = pkg;
109 mNotificationChannels = notificationChannels;
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400110 mCheckSaveListener = checkSaveListener;
Geoffrey Pitschee8c81e2017-03-23 11:38:56 -0400111 boolean isSingleDefaultChannel = false;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500112 if (mNotificationChannels.isEmpty()) {
113 throw new IllegalArgumentException("bindNotification requires at least one channel");
114 } else if (mNotificationChannels.size() == 1) {
115 mSingleNotificationChannel = mNotificationChannels.get(0);
116 mStartingUserImportance = mSingleNotificationChannel.getImportance();
Geoffrey Pitschee8c81e2017-03-23 11:38:56 -0400117 isSingleDefaultChannel = mSingleNotificationChannel.getId()
118 .equals(NotificationChannel.DEFAULT_CHANNEL_ID);
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500119 } else {
120 mSingleNotificationChannel = null;
121 }
Mady Mellor87d79452017-01-10 11:52:52 -0800122
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500123 String appName = mPkg;
Mady Mellor87d79452017-01-10 11:52:52 -0800124 Drawable pkgicon = null;
Julia Reynolds5a311932017-03-01 16:33:44 -0500125 CharSequence channelNameText = "";
126 ApplicationInfo info = null;
Mady Mellor87d79452017-01-10 11:52:52 -0800127 try {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500128 info = pm.getApplicationInfo(mPkg,
Mady Mellor87d79452017-01-10 11:52:52 -0800129 PackageManager.MATCH_UNINSTALLED_PACKAGES
130 | PackageManager.MATCH_DISABLED_COMPONENTS
131 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
132 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
133 if (info != null) {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500134 mAppUid = info.uid;
Geoffrey Pitsch1f7fb1a2017-02-13 16:57:40 -0500135 appName = String.valueOf(pm.getApplicationLabel(info));
Mady Mellor87d79452017-01-10 11:52:52 -0800136 pkgicon = pm.getApplicationIcon(info);
137 }
138 } catch (PackageManager.NameNotFoundException e) {
139 // app is gone, just show package name and generic icon
140 pkgicon = pm.getDefaultActivityIcon();
141 }
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500142 ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
143
144 int numChannels = 1;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500145 numChannels = iNotificationManager.getNumNotificationChannelsForPackage(
146 pkg, mAppUid, false /* includeDeleted */);
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500147
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500148 String channelsDescText;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500149 mNumChannelsView = (TextView) (findViewById(R.id.num_channels_desc));
Geoffrey Pitschee8c81e2017-03-23 11:38:56 -0400150 if (isSingleDefaultChannel) {
151 channelsDescText = mContext.getString(R.string.notification_default_channel_desc);
152 } else {
153 switch (mNotificationChannels.size()) {
154 case 1:
155 channelsDescText = String.format(mContext.getResources().getQuantityString(
156 R.plurals.notification_num_channels_desc, numChannels), numChannels);
157 break;
158 case 2:
159 channelsDescText = mContext.getString(
160 R.string.notification_channels_list_desc_2,
161 mNotificationChannels.get(0).getName(),
162 mNotificationChannels.get(1).getName());
163 break;
164 default:
165 final int numOthers = mNotificationChannels.size() - 2;
166 channelsDescText = String.format(
167 mContext.getResources().getQuantityString(
168 R.plurals.notification_channels_list_desc_2_and_others,
169 numOthers),
170 mNotificationChannels.get(0).getName(),
171 mNotificationChannels.get(1).getName(),
172 numOthers);
173 }
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500174 }
175 mNumChannelsView.setText(channelsDescText);
Mady Mellor87d79452017-01-10 11:52:52 -0800176
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500177 if (mSingleNotificationChannel == null) {
178 // Multiple channels don't use a channel name for the title.
179 channelNameText = mContext.getString(R.string.notification_num_channels,
180 mNotificationChannels.size());
Geoffrey Pitschee8c81e2017-03-23 11:38:56 -0400181 } else if (isSingleDefaultChannel) {
182 // If this is the default channel, don't use our channel-specific text.
Mady Mellor87d79452017-01-10 11:52:52 -0800183 channelNameText = mContext.getString(R.string.notification_header_default_channel);
184 } else {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500185 channelNameText = mSingleNotificationChannel.getName();
Mady Mellor87d79452017-01-10 11:52:52 -0800186 }
Geoffrey Pitsch1f7fb1a2017-02-13 16:57:40 -0500187 ((TextView) findViewById(R.id.pkgname)).setText(appName);
Mady Mellor87d79452017-01-10 11:52:52 -0800188 ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
189
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500190 // Set group information if this channel has an associated group.
191 CharSequence groupName = null;
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500192 if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
193 final NotificationChannelGroup notificationChannelGroup =
194 iNotificationManager.getNotificationChannelGroupForPackage(
195 mSingleNotificationChannel.getGroup(), pkg, mAppUid);
196 if (notificationChannelGroup != null) {
197 groupName = notificationChannelGroup.getName();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500198 }
Mady Mellor87d79452017-01-10 11:52:52 -0800199 }
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500200 TextView groupNameView = ((TextView) findViewById(R.id.group_name));
201 TextView groupDividerView = ((TextView) findViewById(R.id.pkg_group_divider));
202 if (groupName != null) {
203 groupNameView.setText(groupName);
204 groupNameView.setVisibility(View.VISIBLE);
205 groupDividerView.setVisibility(View.VISIBLE);
206 } else {
207 groupNameView.setVisibility(View.GONE);
208 groupDividerView.setVisibility(View.GONE);
209 }
Mady Mellor87d79452017-01-10 11:52:52 -0800210
211 boolean nonBlockable = false;
212 try {
Julia Reynolds5a311932017-03-01 16:33:44 -0500213 final PackageInfo pkgInfo = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
214 nonBlockable = Utils.isSystemPackage(getResources(), pm, pkgInfo);
Mady Mellor87d79452017-01-10 11:52:52 -0800215 } catch (PackageManager.NameNotFoundException e) {
216 // unlikely.
217 }
218 if (nonBlockablePkgs != null) {
219 nonBlockable |= nonBlockablePkgs.contains(pkg);
220 }
221
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500222 bindButtons(nonBlockable);
Mady Mellor87d79452017-01-10 11:52:52 -0800223
224 // Top-level importance group
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500225 mChannelDisabledView = findViewById(R.id.channel_disabled);
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500226 updateSecondaryText();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500227
228 // Settings button.
229 final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500230 if (mAppUid >= 0 && onSettingsClick != null) {
231 final int appUidF = mAppUid;
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500232 settingsButton.setOnClickListener(
233 (View view) -> {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500234 onSettingsClick.onClick(view, mSingleNotificationChannel, appUidF);
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500235 });
236 if (numChannels > 1) {
237 settingsButton.setText(R.string.notification_all_categories);
238 } else {
239 settingsButton.setText(R.string.notification_more_settings);
240 }
241
242 } else {
243 settingsButton.setVisibility(View.GONE);
244 }
245
246 // Done button.
247 final TextView doneButton = (TextView) findViewById(R.id.done);
248 doneButton.setText(R.string.notification_done);
249 doneButton.setOnClickListener(onDoneClick);
Mady Mellor87d79452017-01-10 11:52:52 -0800250 }
251
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400252 private boolean hasImportanceChanged() {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500253 return mSingleNotificationChannel != null &&
254 mStartingUserImportance != getSelectedImportance();
Mady Mellor87d79452017-01-10 11:52:52 -0800255 }
256
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500257 private void saveImportance() {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500258 if (mSingleNotificationChannel == null) {
259 return;
260 }
Mady Mellor87d79452017-01-10 11:52:52 -0800261 int selectedImportance = getSelectedImportance();
262 if (selectedImportance == mStartingUserImportance) {
263 return;
264 }
265 MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
266 selectedImportance - mStartingUserImportance);
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500267 mSingleNotificationChannel.setImportance(selectedImportance);
Mady Mellor87d79452017-01-10 11:52:52 -0800268 try {
269 mINotificationManager.updateNotificationChannelForPackage(
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500270 mPkg, mAppUid, mSingleNotificationChannel);
Mady Mellor87d79452017-01-10 11:52:52 -0800271 } catch (RemoteException e) {
272 // :(
273 }
274 }
275
276 private int getSelectedImportance() {
277 if (!mChannelEnabledSwitch.isChecked()) {
278 return NotificationManager.IMPORTANCE_NONE;
Mady Mellor87d79452017-01-10 11:52:52 -0800279 } else {
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500280 return mStartingUserImportance;
Mady Mellor87d79452017-01-10 11:52:52 -0800281 }
282 }
283
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500284 private void bindButtons(final boolean nonBlockable) {
Mady Mellor87d79452017-01-10 11:52:52 -0800285 // Enabled Switch
286 mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500287 mChannelEnabledSwitch.setChecked(
288 mStartingUserImportance != NotificationManager.IMPORTANCE_NONE);
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500289 final boolean visible = !nonBlockable && mSingleNotificationChannel != null;
290 mChannelEnabledSwitch.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
Mady Mellor87d79452017-01-10 11:52:52 -0800291
Mady Mellor87d79452017-01-10 11:52:52 -0800292 // Callback when checked.
293 mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
Mady Mellor95d743c2017-01-10 12:05:27 -0800294 if (mGutsContainer != null) {
295 mGutsContainer.resetFalsingCheck();
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500296 }
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500297 updateSecondaryText();
Mady Mellor87d79452017-01-10 11:52:52 -0800298 });
Mady Mellor87d79452017-01-10 11:52:52 -0800299 }
300
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500301 private void updateSecondaryText() {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500302 final boolean disabled = mSingleNotificationChannel != null &&
303 getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
Geoffrey Pitschee8c81e2017-03-23 11:38:56 -0400304 if (disabled) {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500305 mChannelDisabledView.setVisibility(View.VISIBLE);
306 mNumChannelsView.setVisibility(View.GONE);
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500307 } else {
Geoffrey Pitschd0856f02017-02-16 10:51:18 -0500308 mChannelDisabledView.setVisibility(View.GONE);
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500309 mNumChannelsView.setVisibility(View.VISIBLE);
Mady Mellor87d79452017-01-10 11:52:52 -0800310 }
311 }
312
313 @Override
Mady Mellor95d743c2017-01-10 12:05:27 -0800314 public void setGutsParent(NotificationGuts guts) {
315 mGutsContainer = guts;
Mady Mellor87d79452017-01-10 11:52:52 -0800316 }
317
318 @Override
Mady Mellor434180c2017-02-13 11:29:42 -0800319 public boolean willBeRemoved() {
320 return !mChannelEnabledSwitch.isChecked();
321 }
322
323 @Override
Mady Mellor87d79452017-01-10 11:52:52 -0800324 public View getContentView() {
325 return this;
326 }
327
328 @Override
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500329 public boolean handleCloseControls(boolean save) {
Geoffrey Pitsch5278d3d2017-03-29 13:39:10 -0400330 if (save && hasImportanceChanged()) {
331 if (mCheckSaveListener != null) {
332 mCheckSaveListener.checkSave(() -> { saveImportance(); });
333 } else {
334 saveImportance();
335 }
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500336 }
Mady Mellor87d79452017-01-10 11:52:52 -0800337 return false;
338 }
339}