2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package com.android.systemui.statusbar;
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.app.INotificationManager;
22 import android.content.Context;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageManager;
25 import android.content.res.ColorStateList;
26 import android.content.res.TypedArray;
27 import android.graphics.Canvas;
28 import android.graphics.drawable.Drawable;
29 import android.os.Handler;
30 import android.os.RemoteException;
31 import android.os.ServiceManager;
32 import android.service.notification.NotificationListenerService;
33 import android.service.notification.NotificationListenerService.Ranking;
34 import android.service.notification.StatusBarNotification;
35 import android.util.AttributeSet;
36 import android.view.View;
37 import android.view.ViewAnimationUtils;
38 import android.widget.ImageView;
39 import android.widget.LinearLayout;
40 import android.widget.RadioButton;
41 import android.widget.RadioGroup;
42 import android.widget.SeekBar;
43 import android.widget.TextView;
45 import com.android.internal.logging.MetricsLogger;
46 import com.android.internal.logging.MetricsProto.MetricsEvent;
47 import com.android.settingslib.Utils;
48 import com.android.systemui.Interpolators;
49 import com.android.systemui.R;
50 import com.android.systemui.statusbar.stack.StackStateAnimator;
51 import com.android.systemui.tuner.TunerService;
54 * The guts of a notification revealed when performing a long press.
56 public class NotificationGuts extends LinearLayout implements TunerService.Tunable {
57 public static final String SHOW_SLIDER = "show_importance_slider";
59 private static final long CLOSE_GUTS_DELAY = 8000;
61 private Drawable mBackground;
62 private int mClipTopAmount;
63 private int mActualHeight;
64 private boolean mExposed;
65 private INotificationManager mINotificationManager;
66 private int mStartingUserImportance;
67 private int mNotificationImportance;
68 private boolean mShowSlider;
70 private SeekBar mSeekBar;
71 private ImageView mAutoButton;
72 private ColorStateList mActiveSliderTint;
73 private ColorStateList mInactiveSliderTint;
74 private float mActiveSliderAlpha = 1.0f;
75 private float mInactiveSliderAlpha;
76 private TextView mImportanceSummary;
77 private TextView mImportanceTitle;
78 private boolean mAuto;
80 private RadioButton mBlock;
81 private RadioButton mSilent;
82 private RadioButton mReset;
84 private Handler mHandler;
85 private Runnable mFalsingCheck;
86 private boolean mNeedsFalsingProtection;
87 private OnGutsClosedListener mListener;
89 public interface OnGutsClosedListener {
90 public void onGutsClosed(NotificationGuts guts);
93 public NotificationGuts(Context context, AttributeSet attrs) {
94 super(context, attrs);
95 setWillNotDraw(false);
96 mHandler = new Handler();
97 mFalsingCheck = new Runnable() {
100 if (mNeedsFalsingProtection && mExposed) {
101 closeControls(-1 /* x */, -1 /* y */, true /* notify */);
105 final TypedArray ta =
106 context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Theme, 0, 0);
107 mInactiveSliderAlpha =
108 ta.getFloat(com.android.internal.R.styleable.Theme_disabledAlpha, 0.5f);
113 protected void onAttachedToWindow() {
114 super.onAttachedToWindow();
115 TunerService.get(mContext).addTunable(this, SHOW_SLIDER);
119 protected void onDetachedFromWindow() {
120 TunerService.get(mContext).removeTunable(this);
121 super.onDetachedFromWindow();
124 public void resetFalsingCheck() {
125 mHandler.removeCallbacks(mFalsingCheck);
126 if (mNeedsFalsingProtection && mExposed) {
127 mHandler.postDelayed(mFalsingCheck, CLOSE_GUTS_DELAY);
132 protected void onDraw(Canvas canvas) {
133 draw(canvas, mBackground);
136 private void draw(Canvas canvas, Drawable drawable) {
137 if (drawable != null) {
138 drawable.setBounds(0, mClipTopAmount, getWidth(), mActualHeight);
139 drawable.draw(canvas);
144 protected void onFinishInflate() {
145 super.onFinishInflate();
146 mBackground = mContext.getDrawable(R.drawable.notification_guts_bg);
147 if (mBackground != null) {
148 mBackground.setCallback(this);
153 protected boolean verifyDrawable(Drawable who) {
154 return super.verifyDrawable(who) || who == mBackground;
158 protected void drawableStateChanged() {
159 drawableStateChanged(mBackground);
162 private void drawableStateChanged(Drawable d) {
163 if (d != null && d.isStateful()) {
164 d.setState(getDrawableState());
169 public void drawableHotspotChanged(float x, float y) {
170 if (mBackground != null) {
171 mBackground.setHotspot(x, y);
175 void bindImportance(final PackageManager pm, final StatusBarNotification sbn,
176 final int importance) {
177 mINotificationManager = INotificationManager.Stub.asInterface(
178 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
179 mStartingUserImportance = NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
181 mStartingUserImportance =
182 mINotificationManager.getImportance(sbn.getPackageName(), sbn.getUid());
183 } catch (RemoteException e) {}
184 mNotificationImportance = importance;
185 boolean systemApp = false;
187 final PackageInfo info =
188 pm.getPackageInfo(sbn.getPackageName(), PackageManager.GET_SIGNATURES);
189 systemApp = Utils.isSystemPackage(pm, info);
190 } catch (PackageManager.NameNotFoundException e) {
194 final View importanceSlider = findViewById(R.id.importance_slider);
195 final View importanceButtons = findViewById(R.id.importance_buttons);
197 bindSlider(importanceSlider, systemApp);
198 importanceSlider.setVisibility(View.VISIBLE);
199 importanceButtons.setVisibility(View.GONE);
202 bindToggles(importanceButtons, mStartingUserImportance, systemApp);
203 importanceButtons.setVisibility(View.VISIBLE);
204 importanceSlider.setVisibility(View.GONE);
208 public boolean hasImportanceChanged() {
209 return mStartingUserImportance != getSelectedImportance();
212 void saveImportance(final StatusBarNotification sbn) {
213 int progress = getSelectedImportance();
214 MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
215 progress - mStartingUserImportance);
217 mINotificationManager.setImportance(sbn.getPackageName(), sbn.getUid(), progress);
218 } catch (RemoteException e) {
223 private int getSelectedImportance() {
224 if (mSeekBar!= null && mSeekBar.isShown()) {
225 if (mSeekBar.isEnabled()) {
226 return mSeekBar.getProgress();
228 return Ranking.IMPORTANCE_UNSPECIFIED;
231 if (mBlock.isChecked()) {
232 return Ranking.IMPORTANCE_NONE;
233 } else if (mSilent.isChecked()) {
234 return Ranking.IMPORTANCE_LOW;
236 return Ranking.IMPORTANCE_UNSPECIFIED;
241 private void bindToggles(final View importanceButtons, final int importance,
242 final boolean systemApp) {
243 ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
244 new RadioGroup.OnCheckedChangeListener() {
246 public void onCheckedChanged(RadioGroup group, int checkedId) {
250 mBlock = (RadioButton) importanceButtons.findViewById(R.id.block_importance);
251 mSilent = (RadioButton) importanceButtons.findViewById(R.id.silent_importance);
252 mReset = (RadioButton) importanceButtons.findViewById(R.id.reset_importance);
254 mBlock.setVisibility(View.GONE);
255 mReset.setText(mContext.getString(R.string.do_not_silence));
257 mReset.setText(mContext.getString(R.string.do_not_silence_block));
259 mBlock.setText(mContext.getString(R.string.block));
260 mSilent.setText(mContext.getString(R.string.show_silently));
261 if (importance == NotificationListenerService.Ranking.IMPORTANCE_LOW) {
262 mSilent.setChecked(true);
264 mReset.setChecked(true);
268 private void bindSlider(final View importanceSlider, final boolean systemApp) {
269 mActiveSliderTint = loadColorStateList(R.color.notification_guts_slider_color);
270 mInactiveSliderTint = loadColorStateList(R.color.notification_guts_disabled_slider_color);
272 mImportanceSummary = ((TextView) importanceSlider.findViewById(R.id.summary));
273 mImportanceTitle = ((TextView) importanceSlider.findViewById(R.id.title));
274 mSeekBar = (SeekBar) importanceSlider.findViewById(R.id.seekbar);
276 final int minProgress = systemApp ?
277 NotificationListenerService.Ranking.IMPORTANCE_MIN
278 : NotificationListenerService.Ranking.IMPORTANCE_NONE;
279 mSeekBar.setMax(NotificationListenerService.Ranking.IMPORTANCE_MAX);
280 mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
282 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
284 if (progress < minProgress) {
285 seekBar.setProgress(minProgress);
286 progress = minProgress;
288 updateTitleAndSummary(progress);
290 MetricsLogger.action(mContext, MetricsEvent.ACTION_MODIFY_IMPORTANCE_SLIDER);
295 public void onStartTrackingTouch(SeekBar seekBar) {
300 public void onStopTrackingTouch(SeekBar seekBar) {
306 mSeekBar.setProgress(mNotificationImportance);
308 mAutoButton = (ImageView) importanceSlider.findViewById(R.id.auto_importance);
309 mAutoButton.setOnClickListener(new OnClickListener() {
311 public void onClick(View v) {
316 mAuto = mStartingUserImportance == Ranking.IMPORTANCE_UNSPECIFIED;
320 private void applyAuto() {
321 mSeekBar.setEnabled(!mAuto);
323 final ColorStateList starTint = mAuto ? mActiveSliderTint : mInactiveSliderTint;
324 final float alpha = mAuto ? mInactiveSliderAlpha : mActiveSliderAlpha;
325 Drawable icon = mAutoButton.getDrawable().mutate();
326 icon.setTintList(starTint);
327 mAutoButton.setImageDrawable(icon);
328 mSeekBar.setAlpha(alpha);
331 mSeekBar.setProgress(mNotificationImportance);
332 mImportanceSummary.setText(mContext.getString(
333 R.string.notification_importance_user_unspecified));
334 mImportanceTitle.setText(mContext.getString(
335 R.string.user_unspecified_importance));
337 updateTitleAndSummary(mSeekBar.getProgress());
341 private void updateTitleAndSummary(int progress) {
343 case Ranking.IMPORTANCE_NONE:
344 mImportanceSummary.setText(mContext.getString(
345 R.string.notification_importance_blocked));
346 mImportanceTitle.setText(mContext.getString(R.string.blocked_importance));
348 case Ranking.IMPORTANCE_MIN:
349 mImportanceSummary.setText(mContext.getString(
350 R.string.notification_importance_min));
351 mImportanceTitle.setText(mContext.getString(R.string.min_importance));
353 case Ranking.IMPORTANCE_LOW:
354 mImportanceSummary.setText(mContext.getString(
355 R.string.notification_importance_low));
356 mImportanceTitle.setText(mContext.getString(R.string.low_importance));
358 case Ranking.IMPORTANCE_DEFAULT:
359 mImportanceSummary.setText(mContext.getString(
360 R.string.notification_importance_default));
361 mImportanceTitle.setText(mContext.getString(R.string.default_importance));
363 case Ranking.IMPORTANCE_HIGH:
364 mImportanceSummary.setText(mContext.getString(
365 R.string.notification_importance_high));
366 mImportanceTitle.setText(mContext.getString(R.string.high_importance));
368 case Ranking.IMPORTANCE_MAX:
369 mImportanceSummary.setText(mContext.getString(
370 R.string.notification_importance_max));
371 mImportanceTitle.setText(mContext.getString(R.string.max_importance));
376 private ColorStateList loadColorStateList(int colorResId) {
377 return ColorStateList.valueOf(mContext.getColor(colorResId));
380 public void closeControls(int x, int y, boolean notify) {
381 if (getWindowToken() == null) {
382 if (notify && mListener != null) {
383 mListener.onGutsClosed(this);
387 if (x == -1 || y == -1) {
388 x = (getLeft() + getRight()) / 2;
389 y = (getTop() + getHeight() / 2);
391 final double horz = Math.max(getWidth() - x, x);
392 final double vert = Math.max(getHeight() - y, y);
393 final float r = (float) Math.hypot(horz, vert);
394 final Animator a = ViewAnimationUtils.createCircularReveal(this,
396 a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
397 a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
398 a.addListener(new AnimatorListenerAdapter() {
400 public void onAnimationEnd(Animator animation) {
401 super.onAnimationEnd(animation);
402 setVisibility(View.GONE);
406 setExposed(false, mNeedsFalsingProtection);
407 if (notify && mListener != null) {
408 mListener.onGutsClosed(this);
412 public void setActualHeight(int actualHeight) {
413 mActualHeight = actualHeight;
417 public int getActualHeight() {
418 return mActualHeight;
421 public void setClipTopAmount(int clipTopAmount) {
422 mClipTopAmount = clipTopAmount;
427 public boolean hasOverlappingRendering() {
428 // Prevents this view from creating a layer when alpha is animating.
432 public void setClosedListener(OnGutsClosedListener listener) {
433 mListener = listener;
436 public void setExposed(boolean exposed, boolean needsFalsingProtection) {
438 mNeedsFalsingProtection = needsFalsingProtection;
439 if (mExposed && mNeedsFalsingProtection) {
442 mHandler.removeCallbacks(mFalsingCheck);
446 public boolean areGutsExposed() {
451 public void onTuningChanged(String key, String newValue) {
452 if (SHOW_SLIDER.equals(key)) {
453 mShowSlider = newValue != null && Integer.parseInt(newValue) != 0;