• 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