blob: 098984639a998b038988219b61998889cb24522b [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
94 interface OnSettingsClickListener {
95 void onClick(View v, int appUid);
96 }
97
98 void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
99 final StatusBarNotification sbn, final NotificationChannel channel,
100 OnSettingsClickListener onSettingsClick,
101 OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
102 mINotificationManager = iNotificationManager;
103 mNotificationChannel = channel;
104 mStatusBarNotification = sbn;
105 mStartingUserImportance = channel.getImportance();
106
107 final String pkg = sbn.getPackageName();
108 int appUid = -1;
109 String appname = pkg;
110 Drawable pkgicon = null;
111 try {
112 final ApplicationInfo info = pm.getApplicationInfo(pkg,
113 PackageManager.MATCH_UNINSTALLED_PACKAGES
114 | PackageManager.MATCH_DISABLED_COMPONENTS
115 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
116 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
117 if (info != null) {
118 appUid = info.uid;
119 appname = String.valueOf(pm.getApplicationLabel(info));
120 pkgicon = pm.getApplicationIcon(info);
121 }
122 } catch (PackageManager.NameNotFoundException e) {
123 // app is gone, just show package name and generic icon
124 pkgicon = pm.getDefaultActivityIcon();
125 }
126
127 // If this is the placeholder channel, don't use our channel-specific text.
128 String appNameText;
129 CharSequence channelNameText;
130 if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
131 appNameText = appname;
132 channelNameText = mContext.getString(R.string.notification_header_default_channel);
133 } else {
134 appNameText = mContext.getString(R.string.notification_importance_header_app, appname);
135 channelNameText = channel.getName();
136 }
137 ((TextView) findViewById(R.id.pkgname)).setText(appNameText);
138 ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
139
140 // Settings button.
141 final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
142 if (appUid >= 0 && onSettingsClick != null) {
143 final int appUidF = appUid;
144 settingsButton.setOnClickListener(
145 (View view) -> {
146 onSettingsClick.onClick(view, appUidF);
147 });
148 settingsButton.setText(R.string.notification_more_settings);
149 } else {
150 settingsButton.setVisibility(View.GONE);
151 }
152
153 // Done button.
154 final TextView doneButton = (TextView) findViewById(R.id.done);
155 doneButton.setText(R.string.notification_done);
156 doneButton.setOnClickListener(onDoneClick);
157
158 boolean nonBlockable = false;
159 try {
160 final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
161 nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
162 } catch (PackageManager.NameNotFoundException e) {
163 // unlikely.
164 }
165 if (nonBlockablePkgs != null) {
166 nonBlockable |= nonBlockablePkgs.contains(pkg);
167 }
168
169 final View importanceButtons = findViewById(R.id.importance_buttons);
170 bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
171
172 // Importance Text (hardcoded to 4 importance levels)
173 final ViewGroup importanceTextGroup = (ViewGroup) findViewById(
174 R.id.importance_buttons_text);
175 final int size = importanceTextGroup.getChildCount();
176 for (int i = 0; i < size; i++) {
177 int importanceNameResId = 0;
178 int importanceDescResId = 0;
179 switch (i) {
180 case 0:
181 importanceNameResId = R.string.high_importance;
182 importanceDescResId = R.string.notification_importance_high;
183 break;
184 case 1:
185 importanceNameResId = R.string.default_importance;
186 importanceDescResId = R.string.notification_importance_default;
187 break;
188 case 2:
189 importanceNameResId = R.string.low_importance;
190 importanceDescResId = R.string.notification_importance_low;
191 break;
192 case 3:
193 importanceNameResId = R.string.min_importance;
194 importanceDescResId = R.string.notification_importance_min;
195 break;
196 default:
197 Log.e(TAG, "Too many importance groups in this layout.");
198 break;
199 }
200 final ViewGroup importanceChildGroup = (ViewGroup) importanceTextGroup.getChildAt(i);
201 ((TextView) importanceChildGroup.getChildAt(0)).setText(importanceNameResId);
202 ((TextView) importanceChildGroup.getChildAt(1)).setText(importanceDescResId);
203 }
204
205 // Top-level importance group
206 mImportanceGroup = findViewById(R.id.importance);
207 mChannelDisabled = findViewById(R.id.channel_disabled);
208 updateImportanceGroup();
209 }
210
211 public boolean hasImportanceChanged() {
212 return mStartingUserImportance != getSelectedImportance();
213 }
214
215 public void saveImportance() {
216 int selectedImportance = getSelectedImportance();
217 if (selectedImportance == mStartingUserImportance) {
218 return;
219 }
220 MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
221 selectedImportance - mStartingUserImportance);
222 mNotificationChannel.setImportance(selectedImportance);
223 try {
224 mINotificationManager.updateNotificationChannelForPackage(
225 mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
226 mNotificationChannel);
227 } catch (RemoteException e) {
228 // :(
229 }
230 }
231
232 private int getSelectedImportance() {
233 if (!mChannelEnabledSwitch.isChecked()) {
234 return NotificationManager.IMPORTANCE_NONE;
235 } else if (mMinImportanceButton.isChecked()) {
236 return NotificationManager.IMPORTANCE_MIN;
237 } else if (mLowImportanceButton.isChecked()) {
238 return NotificationManager.IMPORTANCE_LOW;
239 } else if (mDefaultImportanceButton.isChecked()) {
240 return NotificationManager.IMPORTANCE_DEFAULT;
241 } else if (mHighImportanceButton.isChecked()) {
242 return NotificationManager.IMPORTANCE_HIGH;
243 } else {
244 return NotificationManager.IMPORTANCE_UNSPECIFIED;
245 }
246 }
247
248 private void bindToggles(final View importanceButtons, final int importance,
249 final boolean nonBlockable) {
250 // Enabled Switch
251 mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
252 mChannelEnabledSwitch.setChecked(importance != NotificationManager.IMPORTANCE_NONE);
253 mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
254
255 // Importance Buttons
256 mMinImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.min_importance);
257 mLowImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.low_importance);
258 mDefaultImportanceButton = (RadioButton) importanceButtons
259 .findViewById(R.id.default_importance);
260 mHighImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.high_importance);
261
262 // Set to current importance setting
263 switch (importance) {
264 case NotificationManager.IMPORTANCE_UNSPECIFIED:
265 case NotificationManager.IMPORTANCE_NONE:
266 break;
267 case NotificationManager.IMPORTANCE_MIN:
268 mMinImportanceButton.setChecked(true);
269 break;
270 case NotificationManager.IMPORTANCE_LOW:
271 mLowImportanceButton.setChecked(true);
272 break;
273 case NotificationManager.IMPORTANCE_DEFAULT:
274 mDefaultImportanceButton.setChecked(true);
275 break;
276 case NotificationManager.IMPORTANCE_HIGH:
277 case NotificationManager.IMPORTANCE_MAX:
278 mHighImportanceButton.setChecked(true);
279 break;
280 }
281
282 // Callback when checked.
283 mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
284 mGutsInteractionListener.onInteraction(NotificationInfo.this);
285 updateImportanceGroup();
286 });
287 ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
288 (buttonView, isChecked) -> {
289 mGutsInteractionListener.onInteraction(NotificationInfo.this);
290 });
291 }
292
293 private void updateImportanceGroup() {
294 final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
295 mImportanceGroup.setVisibility(disabled ? View.GONE : View.VISIBLE);
296 mChannelDisabled.setVisibility(disabled ? View.VISIBLE : View.GONE);
297 }
298
299 public void closeControls() {
300 if (mGutsInteractionListener != null) {
301 mGutsInteractionListener.closeGuts(this);
302 }
303 }
304
305 @Override
306 public void setInteractionListener(GutsInteractionListener listener) {
307 mGutsInteractionListener = listener;
308 }
309
310 @Override
311 public View getContentView() {
312 return this;
313 }
314
315 @Override
316 public boolean handleCloseControls() {
317 return false;
318 }
319}