• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 
17 package com.android.systemui.statusbar;
18 
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;
44 
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;
52 
53 import java.util.Set;
54 
55 /**
56  * The guts of a notification revealed when performing a long press.
57  */
58 public class NotificationGuts extends LinearLayout implements TunerService.Tunable {
59     public static final String SHOW_SLIDER = "show_importance_slider";
60 
61     private static final long CLOSE_GUTS_DELAY = 8000;
62 
63     private Drawable mBackground;
64     private int mClipTopAmount;
65     private int mActualHeight;
66     private boolean mExposed;
67     private INotificationManager mINotificationManager;
68     private int mStartingUserImportance;
69     private int mNotificationImportance;
70     private boolean mShowSlider;
71 
72     private SeekBar mSeekBar;
73     private ImageView mAutoButton;
74     private ColorStateList mActiveSliderTint;
75     private ColorStateList mInactiveSliderTint;
76     private float mActiveSliderAlpha = 1.0f;
77     private float mInactiveSliderAlpha;
78     private TextView mImportanceSummary;
79     private TextView mImportanceTitle;
80     private boolean mAuto;
81 
82     private RadioButton mBlock;
83     private RadioButton mSilent;
84     private RadioButton mReset;
85 
86     private Handler mHandler;
87     private Runnable mFalsingCheck;
88     private boolean mNeedsFalsingProtection;
89     private OnGutsClosedListener mListener;
90 
91     public interface OnGutsClosedListener {
onGutsClosed(NotificationGuts guts)92         public void onGutsClosed(NotificationGuts guts);
93     }
94 
NotificationGuts(Context context, AttributeSet attrs)95     public NotificationGuts(Context context, AttributeSet attrs) {
96         super(context, attrs);
97         setWillNotDraw(false);
98         mHandler = new Handler();
99         mFalsingCheck = new Runnable() {
100             @Override
101             public void run() {
102                 if (mNeedsFalsingProtection && mExposed) {
103                     closeControls(-1 /* x */, -1 /* y */, true /* notify */);
104                 }
105             }
106         };
107         final TypedArray ta =
108                 context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Theme, 0, 0);
109         mInactiveSliderAlpha =
110                 ta.getFloat(com.android.internal.R.styleable.Theme_disabledAlpha, 0.5f);
111         ta.recycle();
112     }
113 
114     @Override
onAttachedToWindow()115     protected void onAttachedToWindow() {
116         super.onAttachedToWindow();
117         TunerService.get(mContext).addTunable(this, SHOW_SLIDER);
118     }
119 
120     @Override
onDetachedFromWindow()121     protected void onDetachedFromWindow() {
122         TunerService.get(mContext).removeTunable(this);
123         super.onDetachedFromWindow();
124     }
125 
resetFalsingCheck()126     public void resetFalsingCheck() {
127         mHandler.removeCallbacks(mFalsingCheck);
128         if (mNeedsFalsingProtection && mExposed) {
129             mHandler.postDelayed(mFalsingCheck, CLOSE_GUTS_DELAY);
130         }
131     }
132 
133     @Override
onDraw(Canvas canvas)134     protected void onDraw(Canvas canvas) {
135         draw(canvas, mBackground);
136     }
137 
draw(Canvas canvas, Drawable drawable)138     private void draw(Canvas canvas, Drawable drawable) {
139         if (drawable != null) {
140             drawable.setBounds(0, mClipTopAmount, getWidth(), mActualHeight);
141             drawable.draw(canvas);
142         }
143     }
144 
145     @Override
onFinishInflate()146     protected void onFinishInflate() {
147         super.onFinishInflate();
148         mBackground = mContext.getDrawable(R.drawable.notification_guts_bg);
149         if (mBackground != null) {
150             mBackground.setCallback(this);
151         }
152     }
153 
154     @Override
verifyDrawable(Drawable who)155     protected boolean verifyDrawable(Drawable who) {
156         return super.verifyDrawable(who) || who == mBackground;
157     }
158 
159     @Override
drawableStateChanged()160     protected void drawableStateChanged() {
161         drawableStateChanged(mBackground);
162     }
163 
drawableStateChanged(Drawable d)164     private void drawableStateChanged(Drawable d) {
165         if (d != null && d.isStateful()) {
166             d.setState(getDrawableState());
167         }
168     }
169 
170     @Override
drawableHotspotChanged(float x, float y)171     public void drawableHotspotChanged(float x, float y) {
172         if (mBackground != null) {
173             mBackground.setHotspot(x, y);
174         }
175     }
176 
bindImportance(final PackageManager pm, final StatusBarNotification sbn, final Set<String> nonBlockablePkgs, final int importance)177     void bindImportance(final PackageManager pm, final StatusBarNotification sbn,
178             final Set<String> nonBlockablePkgs, final int importance) {
179         mINotificationManager = INotificationManager.Stub.asInterface(
180                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
181         mStartingUserImportance = NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
182         try {
183             mStartingUserImportance =
184                     mINotificationManager.getImportance(sbn.getPackageName(), sbn.getUid());
185         } catch (RemoteException e) {}
186         mNotificationImportance = importance;
187 
188         final View importanceSlider = findViewById(R.id.importance_slider);
189         final View importanceButtons = findViewById(R.id.importance_buttons);
190         final View cantTouchThis = findViewById(R.id.cant_silence_or_block);
191 
192         final boolean essentialPackage =
193                 (nonBlockablePkgs != null && nonBlockablePkgs.contains(sbn.getPackageName()));
194         if (essentialPackage) {
195             importanceButtons.setVisibility(View.GONE);
196             importanceSlider.setVisibility(View.GONE);
197             cantTouchThis.setVisibility(View.VISIBLE);
198         } else {
199             cantTouchThis.setVisibility(View.GONE);
200 
201             boolean nonBlockable = false;
202             try {
203                 final PackageInfo info =
204                         pm.getPackageInfo(sbn.getPackageName(), PackageManager.GET_SIGNATURES);
205                 nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
206             } catch (PackageManager.NameNotFoundException e) {
207                 // unlikely.
208             }
209 
210             if (mShowSlider) {
211                 bindSlider(importanceSlider, nonBlockable);
212                 importanceSlider.setVisibility(View.VISIBLE);
213                 importanceButtons.setVisibility(View.GONE);
214             } else {
215                 bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
216                 importanceButtons.setVisibility(View.VISIBLE);
217                 importanceSlider.setVisibility(View.GONE);
218             }
219         }
220     }
221 
hasImportanceChanged()222     public boolean hasImportanceChanged() {
223         return mStartingUserImportance != getSelectedImportance();
224     }
225 
saveImportance(final StatusBarNotification sbn)226     void saveImportance(final StatusBarNotification sbn) {
227         int progress = getSelectedImportance();
228         MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
229                 progress - mStartingUserImportance);
230         try {
231             mINotificationManager.setImportance(sbn.getPackageName(), sbn.getUid(), progress);
232         } catch (RemoteException e) {
233             // :(
234         }
235     }
236 
getSelectedImportance()237     private int getSelectedImportance() {
238         if (mSeekBar!= null && mSeekBar.isShown()) {
239             if (mSeekBar.isEnabled()) {
240                 return mSeekBar.getProgress();
241             } else {
242                 return Ranking.IMPORTANCE_UNSPECIFIED;
243             }
244         } else {
245             if (mBlock != null && mBlock.isChecked()) {
246                 return Ranking.IMPORTANCE_NONE;
247             } else if (mSilent != null && mSilent.isChecked()) {
248                 return Ranking.IMPORTANCE_LOW;
249             } else {
250                 return Ranking.IMPORTANCE_UNSPECIFIED;
251             }
252         }
253     }
254 
bindToggles(final View importanceButtons, final int importance, final boolean nonBlockable)255     private void bindToggles(final View importanceButtons, final int importance,
256             final boolean nonBlockable) {
257         ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
258                 new RadioGroup.OnCheckedChangeListener() {
259                     @Override
260                     public void onCheckedChanged(RadioGroup group, int checkedId) {
261                         resetFalsingCheck();
262                     }
263                 });
264         mBlock = (RadioButton) importanceButtons.findViewById(R.id.block_importance);
265         mSilent = (RadioButton) importanceButtons.findViewById(R.id.silent_importance);
266         mReset = (RadioButton) importanceButtons.findViewById(R.id.reset_importance);
267         if (nonBlockable) {
268             mBlock.setVisibility(View.GONE);
269             mReset.setText(mContext.getString(R.string.do_not_silence));
270         } else {
271             mReset.setText(mContext.getString(R.string.do_not_silence_block));
272         }
273         mBlock.setText(mContext.getString(R.string.block));
274         mSilent.setText(mContext.getString(R.string.show_silently));
275         if (importance == NotificationListenerService.Ranking.IMPORTANCE_LOW) {
276             mSilent.setChecked(true);
277         } else {
278             mReset.setChecked(true);
279         }
280     }
281 
bindSlider(final View importanceSlider, final boolean nonBlockable)282     private void bindSlider(final View importanceSlider, final boolean nonBlockable) {
283         mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
284         mInactiveSliderTint = loadColorStateList(R.color.notification_guts_disabled_slider_color);
285 
286         mImportanceSummary = ((TextView) importanceSlider.findViewById(R.id.summary));
287         mImportanceTitle = ((TextView) importanceSlider.findViewById(R.id.title));
288         mSeekBar = (SeekBar) importanceSlider.findViewById(R.id.seekbar);
289 
290         final int minProgress = nonBlockable ?
291                 NotificationListenerService.Ranking.IMPORTANCE_MIN
292                 : NotificationListenerService.Ranking.IMPORTANCE_NONE;
293         mSeekBar.setMax(NotificationListenerService.Ranking.IMPORTANCE_MAX);
294         mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
295             @Override
296             public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
297                 resetFalsingCheck();
298                 if (progress < minProgress) {
299                     seekBar.setProgress(minProgress);
300                     progress = minProgress;
301                 }
302                 updateTitleAndSummary(progress);
303                 if (fromUser) {
304                     MetricsLogger.action(mContext, MetricsEvent.ACTION_MODIFY_IMPORTANCE_SLIDER);
305                 }
306             }
307 
308             @Override
309             public void onStartTrackingTouch(SeekBar seekBar) {
310                 resetFalsingCheck();
311             }
312 
313             @Override
314             public void onStopTrackingTouch(SeekBar seekBar) {
315                 // no-op
316             }
317 
318 
319         });
320         mSeekBar.setProgress(mNotificationImportance);
321 
322         mAutoButton = (ImageView) importanceSlider.findViewById(R.id.auto_importance);
323         mAutoButton.setOnClickListener(new OnClickListener() {
324             @Override
325             public void onClick(View v) {
326                 mAuto = !mAuto;
327                 applyAuto();
328             }
329         });
330         mAuto = mStartingUserImportance == Ranking.IMPORTANCE_UNSPECIFIED;
331         applyAuto();
332     }
333 
applyAuto()334     private void applyAuto() {
335         mSeekBar.setEnabled(!mAuto);
336 
337         final ColorStateList starTint = mAuto ?  mActiveSliderTint : mInactiveSliderTint;
338         final float alpha = mAuto ? mInactiveSliderAlpha : mActiveSliderAlpha;
339         Drawable icon = mAutoButton.getDrawable().mutate();
340         icon.setTintList(starTint);
341         mAutoButton.setImageDrawable(icon);
342         mSeekBar.setAlpha(alpha);
343 
344         if (mAuto) {
345             mSeekBar.setProgress(mNotificationImportance);
346             mImportanceSummary.setText(mContext.getString(
347                     R.string.notification_importance_user_unspecified));
348             mImportanceTitle.setText(mContext.getString(
349                     R.string.user_unspecified_importance));
350         } else {
351             updateTitleAndSummary(mSeekBar.getProgress());
352         }
353     }
354 
updateTitleAndSummary(int progress)355     private void updateTitleAndSummary(int progress) {
356         switch (progress) {
357             case Ranking.IMPORTANCE_NONE:
358                 mImportanceSummary.setText(mContext.getString(
359                         R.string.notification_importance_blocked));
360                 mImportanceTitle.setText(mContext.getString(R.string.blocked_importance));
361                 break;
362             case Ranking.IMPORTANCE_MIN:
363                 mImportanceSummary.setText(mContext.getString(
364                         R.string.notification_importance_min));
365                 mImportanceTitle.setText(mContext.getString(R.string.min_importance));
366                 break;
367             case Ranking.IMPORTANCE_LOW:
368                 mImportanceSummary.setText(mContext.getString(
369                         R.string.notification_importance_low));
370                 mImportanceTitle.setText(mContext.getString(R.string.low_importance));
371                 break;
372             case Ranking.IMPORTANCE_DEFAULT:
373                 mImportanceSummary.setText(mContext.getString(
374                         R.string.notification_importance_default));
375                 mImportanceTitle.setText(mContext.getString(R.string.default_importance));
376                 break;
377             case Ranking.IMPORTANCE_HIGH:
378                 mImportanceSummary.setText(mContext.getString(
379                         R.string.notification_importance_high));
380                 mImportanceTitle.setText(mContext.getString(R.string.high_importance));
381                 break;
382             case Ranking.IMPORTANCE_MAX:
383                 mImportanceSummary.setText(mContext.getString(
384                         R.string.notification_importance_max));
385                 mImportanceTitle.setText(mContext.getString(R.string.max_importance));
386                 break;
387         }
388     }
389 
loadColorStateList(int colorResId)390     private ColorStateList loadColorStateList(int colorResId) {
391         return ColorStateList.valueOf(mContext.getColor(colorResId));
392     }
393 
closeControls(int x, int y, boolean notify)394     public void closeControls(int x, int y, boolean notify) {
395         if (getWindowToken() == null) {
396             if (notify && mListener != null) {
397                 mListener.onGutsClosed(this);
398             }
399             return;
400         }
401         if (x == -1 || y == -1) {
402             x = (getLeft() + getRight()) / 2;
403             y = (getTop() + getHeight() / 2);
404         }
405         final double horz = Math.max(getWidth() - x, x);
406         final double vert = Math.max(getHeight() - y, y);
407         final float r = (float) Math.hypot(horz, vert);
408         final Animator a = ViewAnimationUtils.createCircularReveal(this,
409                 x, y, r, 0);
410         a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
411         a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
412         a.addListener(new AnimatorListenerAdapter() {
413             @Override
414             public void onAnimationEnd(Animator animation) {
415                 super.onAnimationEnd(animation);
416                 setVisibility(View.GONE);
417             }
418         });
419         a.start();
420         setExposed(false, mNeedsFalsingProtection);
421         if (notify && mListener != null) {
422             mListener.onGutsClosed(this);
423         }
424     }
425 
setActualHeight(int actualHeight)426     public void setActualHeight(int actualHeight) {
427         mActualHeight = actualHeight;
428         invalidate();
429     }
430 
getActualHeight()431     public int getActualHeight() {
432         return mActualHeight;
433     }
434 
setClipTopAmount(int clipTopAmount)435     public void setClipTopAmount(int clipTopAmount) {
436         mClipTopAmount = clipTopAmount;
437         invalidate();
438     }
439 
440     @Override
hasOverlappingRendering()441     public boolean hasOverlappingRendering() {
442         // Prevents this view from creating a layer when alpha is animating.
443         return false;
444     }
445 
setClosedListener(OnGutsClosedListener listener)446     public void setClosedListener(OnGutsClosedListener listener) {
447         mListener = listener;
448     }
449 
setExposed(boolean exposed, boolean needsFalsingProtection)450     public void setExposed(boolean exposed, boolean needsFalsingProtection) {
451         mExposed = exposed;
452         mNeedsFalsingProtection = needsFalsingProtection;
453         if (mExposed && mNeedsFalsingProtection) {
454             resetFalsingCheck();
455         } else {
456             mHandler.removeCallbacks(mFalsingCheck);
457         }
458     }
459 
areGutsExposed()460     public boolean areGutsExposed() {
461         return mExposed;
462     }
463 
464     @Override
onTuningChanged(String key, String newValue)465     public void onTuningChanged(String key, String newValue) {
466         if (SHOW_SLIDER.equals(key)) {
467             mShowSlider = newValue != null && Integer.parseInt(newValue) != 0;
468         }
469     }
470 }
471