blob: bdbc9b32cbce6c99476937635a317bddafd2de95 [file] [log] [blame]
Mady Mellor87d79452017-01-10 11:52:52 -08001package com.android.systemui.statusbar;
2
3/*
4 * Copyright (C) 2017 The Android Open Source Project
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License
17 */
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.app.INotificationManager;
22import android.app.NotificationChannel;
23import android.app.NotificationManager;
24import android.content.Context;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.PackageInfo;
27import android.content.pm.PackageManager;
28import android.content.res.ColorStateList;
29import android.content.res.TypedArray;
30import android.graphics.Canvas;
31import android.graphics.drawable.Drawable;
32import android.os.Handler;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.service.notification.NotificationListenerService;
36import android.service.notification.StatusBarNotification;
37import android.util.AttributeSet;
38import android.util.Log;
39import android.view.View;
40import android.view.ViewAnimationUtils;
41import android.view.ViewGroup;
42import android.view.View.OnClickListener;
43import android.widget.CompoundButton;
44import android.widget.ImageView;
45import android.widget.LinearLayout;
46import android.widget.RadioButton;
47import android.widget.RadioGroup;
48import android.widget.SeekBar;
49import android.widget.Switch;
50import android.widget.TextView;
51
52import com.android.internal.logging.MetricsLogger;
53import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
54import com.android.settingslib.Utils;
55import com.android.systemui.Interpolators;
56import com.android.systemui.R;
57import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
58import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener;
59import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener;
60import com.android.systemui.statusbar.stack.StackStateAnimator;
61
62import java.util.Set;
63
64/**
65 * The guts of a notification revealed when performing a long press.
66 */
67public class NotificationInfo extends LinearLayout implements GutsContent {
68 private static final String TAG = "InfoGuts";
69
70 private INotificationManager mINotificationManager;
71 private int mStartingUserImportance;
72 private StatusBarNotification mStatusBarNotification;
73 private NotificationChannel mNotificationChannel;
74
75 private ImageView mAutoButton;
76 private TextView mImportanceSummary;
77 private TextView mImportanceTitle;
78 private boolean mAuto;
79
80 private View mImportanceGroup;
81 private View mChannelDisabled;
82 private Switch mChannelEnabledSwitch;
83 private RadioButton mMinImportanceButton;
84 private RadioButton mLowImportanceButton;
85 private RadioButton mDefaultImportanceButton;
86 private RadioButton mHighImportanceButton;
87
88 private GutsInteractionListener mGutsInteractionListener;
89
90 public NotificationInfo(Context context, AttributeSet attrs) {
91 super(context, attrs);
92 }
93
Jason Monk2a6ea9c2017-01-26 11:14:51 -050094 public interface OnSettingsClickListener {
Mady Mellor87d79452017-01-10 11:52:52 -080095 void onClick(View v, int appUid);
96 }
97
Jason Monk2a6ea9c2017-01-26 11:14:51 -050098 public void bindNotification(final PackageManager pm,
99 final INotificationManager iNotificationManager,
Mady Mellor87d79452017-01-10 11:52:52 -0800100 final StatusBarNotification sbn, final NotificationChannel channel,
101 OnSettingsClickListener onSettingsClick,
102 OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
103 mINotificationManager = iNotificationManager;
104 mNotificationChannel = channel;
105 mStatusBarNotification = sbn;
106 mStartingUserImportance = channel.getImportance();
107
108 final String pkg = sbn.getPackageName();
109 int appUid = -1;
110 String appname = pkg;
111 Drawable pkgicon = null;
112 try {
113 final ApplicationInfo info = pm.getApplicationInfo(pkg,
114 PackageManager.MATCH_UNINSTALLED_PACKAGES
115 | PackageManager.MATCH_DISABLED_COMPONENTS
116 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
117 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
118 if (info != null) {
119 appUid = info.uid;
120 appname = String.valueOf(pm.getApplicationLabel(info));
121 pkgicon = pm.getApplicationIcon(info);
122 }
123 } catch (PackageManager.NameNotFoundException e) {
124 // app is gone, just show package name and generic icon
125 pkgicon = pm.getDefaultActivityIcon();
126 }
127
128 // If this is the placeholder channel, don't use our channel-specific text.
129 String appNameText;
130 CharSequence channelNameText;
131 if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
132 appNameText = appname;
133 channelNameText = mContext.getString(R.string.notification_header_default_channel);
134 } else {
135 appNameText = mContext.getString(R.string.notification_importance_header_app, appname);
136 channelNameText = channel.getName();
137 }
138 ((TextView) findViewById(R.id.pkgname)).setText(appNameText);
139 ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
140
141 // Settings button.
142 final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
143 if (appUid >= 0 && onSettingsClick != null) {
144 final int appUidF = appUid;
145 settingsButton.setOnClickListener(
146 (View view) -> {
147 onSettingsClick.onClick(view, appUidF);
148 });
149 settingsButton.setText(R.string.notification_more_settings);
150 } else {
151 settingsButton.setVisibility(View.GONE);
152 }
153
154 // Done button.
155 final TextView doneButton = (TextView) findViewById(R.id.done);
156 doneButton.setText(R.string.notification_done);
157 doneButton.setOnClickListener(onDoneClick);
158
159 boolean nonBlockable = false;
160 try {
161 final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
162 nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
163 } catch (PackageManager.NameNotFoundException e) {
164 // unlikely.
165 }
166 if (nonBlockablePkgs != null) {
167 nonBlockable |= nonBlockablePkgs.contains(pkg);
168 }
169
170 final View importanceButtons = findViewById(R.id.importance_buttons);
171 bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
172
173 // Importance Text (hardcoded to 4 importance levels)
174 final ViewGroup importanceTextGroup = (ViewGroup) findViewById(
175 R.id.importance_buttons_text);
176 final int size = importanceTextGroup.getChildCount();
177 for (int i = 0; i < size; i++) {
178 int importanceNameResId = 0;
179 int importanceDescResId = 0;
180 switch (i) {
181 case 0:
182 importanceNameResId = R.string.high_importance;
183 importanceDescResId = R.string.notification_importance_high;
184 break;
185 case 1:
186 importanceNameResId = R.string.default_importance;
187 importanceDescResId = R.string.notification_importance_default;
188 break;
189 case 2:
190 importanceNameResId = R.string.low_importance;
191 importanceDescResId = R.string.notification_importance_low;
192 break;
193 case 3:
194 importanceNameResId = R.string.min_importance;
195 importanceDescResId = R.string.notification_importance_min;
196 break;
197 default:
198 Log.e(TAG, "Too many importance groups in this layout.");
199 break;
200 }
201 final ViewGroup importanceChildGroup = (ViewGroup) importanceTextGroup.getChildAt(i);
202 ((TextView) importanceChildGroup.getChildAt(0)).setText(importanceNameResId);
203 ((TextView) importanceChildGroup.getChildAt(1)).setText(importanceDescResId);
204 }
205
206 // Top-level importance group
207 mImportanceGroup = findViewById(R.id.importance);
208 mChannelDisabled = findViewById(R.id.channel_disabled);
209 updateImportanceGroup();
210 }
211
212 public boolean hasImportanceChanged() {
213 return mStartingUserImportance != getSelectedImportance();
214 }
215
216 public void saveImportance() {
217 int selectedImportance = getSelectedImportance();
218 if (selectedImportance == mStartingUserImportance) {
219 return;
220 }
221 MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
222 selectedImportance - mStartingUserImportance);
223 mNotificationChannel.setImportance(selectedImportance);
224 try {
225 mINotificationManager.updateNotificationChannelForPackage(
226 mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
227 mNotificationChannel);
228 } catch (RemoteException e) {
229 // :(
230 }
231 }
232
233 private int getSelectedImportance() {
234 if (!mChannelEnabledSwitch.isChecked()) {
235 return NotificationManager.IMPORTANCE_NONE;
236 } else if (mMinImportanceButton.isChecked()) {
237 return NotificationManager.IMPORTANCE_MIN;
238 } else if (mLowImportanceButton.isChecked()) {
239 return NotificationManager.IMPORTANCE_LOW;
240 } else if (mDefaultImportanceButton.isChecked()) {
241 return NotificationManager.IMPORTANCE_DEFAULT;
242 } else if (mHighImportanceButton.isChecked()) {
243 return NotificationManager.IMPORTANCE_HIGH;
244 } else {
245 return NotificationManager.IMPORTANCE_UNSPECIFIED;
246 }
247 }
248
249 private void bindToggles(final View importanceButtons, final int importance,
250 final boolean nonBlockable) {
251 // Enabled Switch
252 mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
253 mChannelEnabledSwitch.setChecked(importance != NotificationManager.IMPORTANCE_NONE);
254 mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
255
256 // Importance Buttons
257 mMinImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.min_importance);
258 mLowImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.low_importance);
259 mDefaultImportanceButton = (RadioButton) importanceButtons
260 .findViewById(R.id.default_importance);
261 mHighImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.high_importance);
262
263 // Set to current importance setting
264 switch (importance) {
265 case NotificationManager.IMPORTANCE_UNSPECIFIED:
266 case NotificationManager.IMPORTANCE_NONE:
267 break;
268 case NotificationManager.IMPORTANCE_MIN:
269 mMinImportanceButton.setChecked(true);
270 break;
271 case NotificationManager.IMPORTANCE_LOW:
272 mLowImportanceButton.setChecked(true);
273 break;
274 case NotificationManager.IMPORTANCE_DEFAULT:
275 mDefaultImportanceButton.setChecked(true);
276 break;
277 case NotificationManager.IMPORTANCE_HIGH:
278 case NotificationManager.IMPORTANCE_MAX:
279 mHighImportanceButton.setChecked(true);
280 break;
281 }
282
283 // Callback when checked.
284 mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
285 mGutsInteractionListener.onInteraction(NotificationInfo.this);
286 updateImportanceGroup();
287 });
288 ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
289 (buttonView, isChecked) -> {
290 mGutsInteractionListener.onInteraction(NotificationInfo.this);
291 });
292 }
293
294 private void updateImportanceGroup() {
295 final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
296 mImportanceGroup.setVisibility(disabled ? View.GONE : View.VISIBLE);
297 mChannelDisabled.setVisibility(disabled ? View.VISIBLE : View.GONE);
298 }
299
300 public void closeControls() {
301 if (mGutsInteractionListener != null) {
302 mGutsInteractionListener.closeGuts(this);
303 }
304 }
305
306 @Override
307 public void setInteractionListener(GutsInteractionListener listener) {
308 mGutsInteractionListener = listener;
309 }
310
311 @Override
312 public View getContentView() {
313 return this;
314 }
315
316 @Override
317 public boolean handleCloseControls() {
318 return false;
319 }
320}