• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  /*
2   * Copyright (C) 2013 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.content.Context;
20  import android.graphics.drawable.AnimatedVectorDrawable;
21  import android.graphics.drawable.AnimationDrawable;
22  import android.graphics.drawable.Drawable;
23  import android.service.notification.StatusBarNotification;
24  import android.util.AttributeSet;
25  import android.view.MotionEvent;
26  import android.view.View;
27  import android.view.ViewStub;
28  import android.view.accessibility.AccessibilityEvent;
29  import android.widget.ImageView;
30  import com.android.systemui.R;
31  
32  public class ExpandableNotificationRow extends ActivatableNotificationView {
33      private int mRowMinHeight;
34      private int mRowMaxHeight;
35  
36      /** Does this row contain layouts that can adapt to row expansion */
37      private boolean mExpandable;
38      /** Has the user actively changed the expansion state of this row */
39      private boolean mHasUserChangedExpansion;
40      /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
41      private boolean mUserExpanded;
42      /** Is the user touching this row */
43      private boolean mUserLocked;
44      /** Are we showing the "public" version */
45      private boolean mShowingPublic;
46      private boolean mSensitive;
47      private boolean mShowingPublicInitialized;
48      private boolean mShowingPublicForIntrinsicHeight;
49  
50      /**
51       * Is this notification expanded by the system. The expansion state can be overridden by the
52       * user expansion.
53       */
54      private boolean mIsSystemExpanded;
55  
56      /**
57       * Whether the notification expansion is disabled. This is the case on Keyguard.
58       */
59      private boolean mExpansionDisabled;
60  
61      private NotificationContentView mPublicLayout;
62      private NotificationContentView mPrivateLayout;
63      private int mMaxExpandHeight;
64      private View mVetoButton;
65      private boolean mClearable;
66      private ExpansionLogger mLogger;
67      private String mLoggingKey;
68      private boolean mWasReset;
69      private NotificationGuts mGuts;
70  
71      private StatusBarNotification mStatusBarNotification;
72      private boolean mIsHeadsUp;
73  
setIconAnimationRunning(boolean running)74      public void setIconAnimationRunning(boolean running) {
75          setIconAnimationRunning(running, mPublicLayout);
76          setIconAnimationRunning(running, mPrivateLayout);
77      }
78  
setIconAnimationRunning(boolean running, NotificationContentView layout)79      private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
80          if (layout != null) {
81              View contractedChild = layout.getContractedChild();
82              View expandedChild = layout.getExpandedChild();
83              setIconAnimationRunningForChild(running, contractedChild);
84              setIconAnimationRunningForChild(running, expandedChild);
85          }
86      }
87  
setIconAnimationRunningForChild(boolean running, View child)88      private void setIconAnimationRunningForChild(boolean running, View child) {
89          if (child != null) {
90              ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon);
91              setIconRunning(icon, running);
92              ImageView rightIcon = (ImageView) child.findViewById(
93                      com.android.internal.R.id.right_icon);
94              setIconRunning(rightIcon, running);
95          }
96      }
97  
setIconRunning(ImageView imageView, boolean running)98      private void setIconRunning(ImageView imageView, boolean running) {
99          if (imageView != null) {
100              Drawable drawable = imageView.getDrawable();
101              if (drawable instanceof AnimationDrawable) {
102                  AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
103                  if (running) {
104                      animationDrawable.start();
105                  } else {
106                      animationDrawable.stop();
107                  }
108              } else if (drawable instanceof AnimatedVectorDrawable) {
109                  AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable;
110                  if (running) {
111                      animationDrawable.start();
112                  } else {
113                      animationDrawable.stop();
114                  }
115              }
116          }
117      }
118  
setStatusBarNotification(StatusBarNotification statusBarNotification)119      public void setStatusBarNotification(StatusBarNotification statusBarNotification) {
120          mStatusBarNotification = statusBarNotification;
121          updateVetoButton();
122      }
123  
getStatusBarNotification()124      public StatusBarNotification getStatusBarNotification() {
125          return mStatusBarNotification;
126      }
127  
setHeadsUp(boolean isHeadsUp)128      public void setHeadsUp(boolean isHeadsUp) {
129          mIsHeadsUp = isHeadsUp;
130      }
131  
132      public interface ExpansionLogger {
logNotificationExpansion(String key, boolean userAction, boolean expanded)133          public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
134      }
135  
ExpandableNotificationRow(Context context, AttributeSet attrs)136      public ExpandableNotificationRow(Context context, AttributeSet attrs) {
137          super(context, attrs);
138      }
139  
140      /**
141       * Resets this view so it can be re-used for an updated notification.
142       */
143      @Override
reset()144      public void reset() {
145          super.reset();
146          mRowMinHeight = 0;
147          final boolean wasExpanded = isExpanded();
148          mRowMaxHeight = 0;
149          mExpandable = false;
150          mHasUserChangedExpansion = false;
151          mUserLocked = false;
152          mShowingPublic = false;
153          mSensitive = false;
154          mShowingPublicInitialized = false;
155          mIsSystemExpanded = false;
156          mExpansionDisabled = false;
157          mPublicLayout.reset(mIsHeadsUp);
158          mPrivateLayout.reset(mIsHeadsUp);
159          resetHeight();
160          logExpansionEvent(false, wasExpanded);
161      }
162  
resetHeight()163      public void resetHeight() {
164          if (mIsHeadsUp) {
165              resetActualHeight();
166          }
167          mMaxExpandHeight = 0;
168          mWasReset = true;
169          onHeightReset();
170          requestLayout();
171      }
172  
173      @Override
filterMotionEvent(MotionEvent event)174      protected boolean filterMotionEvent(MotionEvent event) {
175          return mIsHeadsUp || super.filterMotionEvent(event);
176      }
177  
178      @Override
onFinishInflate()179      protected void onFinishInflate() {
180          super.onFinishInflate();
181          mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
182          mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
183          ViewStub gutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
184          gutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
185              @Override
186              public void onInflate(ViewStub stub, View inflated) {
187                  mGuts = (NotificationGuts) inflated;
188                  mGuts.setClipTopAmount(getClipTopAmount());
189                  mGuts.setActualHeight(getActualHeight());
190              }
191          });
192          mVetoButton = findViewById(R.id.veto);
193      }
194  
195      @Override
onRequestSendAccessibilityEvent(View child, AccessibilityEvent event)196      public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
197          if (super.onRequestSendAccessibilityEvent(child, event)) {
198              // Add a record for the entire layout since its content is somehow small.
199              // The event comes from a leaf view that is interacted with.
200              AccessibilityEvent record = AccessibilityEvent.obtain();
201              onInitializeAccessibilityEvent(record);
202              dispatchPopulateAccessibilityEvent(record);
203              event.appendRecord(record);
204              return true;
205          }
206          return false;
207      }
208  
209      @Override
setDark(boolean dark, boolean fade, long delay)210      public void setDark(boolean dark, boolean fade, long delay) {
211          super.setDark(dark, fade, delay);
212          final NotificationContentView showing = getShowingLayout();
213          if (showing != null) {
214              showing.setDark(dark, fade, delay);
215          }
216      }
217  
setHeightRange(int rowMinHeight, int rowMaxHeight)218      public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
219          mRowMinHeight = rowMinHeight;
220          mRowMaxHeight = rowMaxHeight;
221      }
222  
isExpandable()223      public boolean isExpandable() {
224          return mExpandable;
225      }
226  
setExpandable(boolean expandable)227      public void setExpandable(boolean expandable) {
228          mExpandable = expandable;
229      }
230  
231      /**
232       * @return whether the user has changed the expansion state
233       */
hasUserChangedExpansion()234      public boolean hasUserChangedExpansion() {
235          return mHasUserChangedExpansion;
236      }
237  
isUserExpanded()238      public boolean isUserExpanded() {
239          return mUserExpanded;
240      }
241  
242      /**
243       * Set this notification to be expanded by the user
244       *
245       * @param userExpanded whether the user wants this notification to be expanded
246       */
setUserExpanded(boolean userExpanded)247      public void setUserExpanded(boolean userExpanded) {
248          if (userExpanded && !mExpandable) return;
249          final boolean wasExpanded = isExpanded();
250          mHasUserChangedExpansion = true;
251          mUserExpanded = userExpanded;
252          logExpansionEvent(true, wasExpanded);
253      }
254  
resetUserExpansion()255      public void resetUserExpansion() {
256          mHasUserChangedExpansion = false;
257          mUserExpanded = false;
258      }
259  
isUserLocked()260      public boolean isUserLocked() {
261          return mUserLocked;
262      }
263  
setUserLocked(boolean userLocked)264      public void setUserLocked(boolean userLocked) {
265          mUserLocked = userLocked;
266      }
267  
268      /**
269       * @return has the system set this notification to be expanded
270       */
isSystemExpanded()271      public boolean isSystemExpanded() {
272          return mIsSystemExpanded;
273      }
274  
275      /**
276       * Set this notification to be expanded by the system.
277       *
278       * @param expand whether the system wants this notification to be expanded.
279       */
setSystemExpanded(boolean expand)280      public void setSystemExpanded(boolean expand) {
281          if (expand != mIsSystemExpanded) {
282              final boolean wasExpanded = isExpanded();
283              mIsSystemExpanded = expand;
284              notifyHeightChanged();
285              logExpansionEvent(false, wasExpanded);
286          }
287      }
288  
289      /**
290       * @param expansionDisabled whether to prevent notification expansion
291       */
setExpansionDisabled(boolean expansionDisabled)292      public void setExpansionDisabled(boolean expansionDisabled) {
293          if (expansionDisabled != mExpansionDisabled) {
294              final boolean wasExpanded = isExpanded();
295              mExpansionDisabled = expansionDisabled;
296              logExpansionEvent(false, wasExpanded);
297              if (wasExpanded != isExpanded()) {
298                  notifyHeightChanged();
299              }
300          }
301      }
302  
303      /**
304       * @return Can the underlying notification be cleared?
305       */
isClearable()306      public boolean isClearable() {
307          return mStatusBarNotification != null && mStatusBarNotification.isClearable();
308      }
309  
310      /**
311       * Apply an expansion state to the layout.
312       */
applyExpansionToLayout()313      public void applyExpansionToLayout() {
314          boolean expand = isExpanded();
315          if (expand && mExpandable) {
316              setActualHeight(mMaxExpandHeight);
317          } else {
318              setActualHeight(mRowMinHeight);
319          }
320      }
321  
322      @Override
getIntrinsicHeight()323      public int getIntrinsicHeight() {
324          if (isUserLocked()) {
325              return getActualHeight();
326          }
327          boolean inExpansionState = isExpanded();
328          if (!inExpansionState) {
329              // not expanded, so we return the collapsed size
330              return mRowMinHeight;
331          }
332  
333          return mShowingPublicForIntrinsicHeight ? mRowMinHeight : getMaxExpandHeight();
334      }
335  
336      /**
337       * Check whether the view state is currently expanded. This is given by the system in {@link
338       * #setSystemExpanded(boolean)} and can be overridden by user expansion or
339       * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
340       * view can differ from this state, if layout params are modified from outside.
341       *
342       * @return whether the view state is currently expanded.
343       */
isExpanded()344      private boolean isExpanded() {
345          return !mExpansionDisabled
346                  && (!hasUserChangedExpansion() && isSystemExpanded() || isUserExpanded());
347      }
348  
349      @Override
onLayout(boolean changed, int left, int top, int right, int bottom)350      protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
351          super.onLayout(changed, left, top, right, bottom);
352          boolean updateExpandHeight = mMaxExpandHeight == 0 && !mWasReset;
353          updateMaxExpandHeight();
354          if (updateExpandHeight) {
355              applyExpansionToLayout();
356          }
357          mWasReset = false;
358      }
359  
updateMaxExpandHeight()360      private void updateMaxExpandHeight() {
361          int intrinsicBefore = getIntrinsicHeight();
362          mMaxExpandHeight = mPrivateLayout.getMaxHeight();
363          if (intrinsicBefore != getIntrinsicHeight()) {
364              notifyHeightChanged();
365          }
366      }
367  
setSensitive(boolean sensitive)368      public void setSensitive(boolean sensitive) {
369          mSensitive = sensitive;
370      }
371  
setHideSensitiveForIntrinsicHeight(boolean hideSensitive)372      public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
373          mShowingPublicForIntrinsicHeight = mSensitive && hideSensitive;
374      }
375  
setHideSensitive(boolean hideSensitive, boolean animated, long delay, long duration)376      public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
377              long duration) {
378          boolean oldShowingPublic = mShowingPublic;
379          mShowingPublic = mSensitive && hideSensitive;
380          if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
381              return;
382          }
383  
384          // bail out if no public version
385          if (mPublicLayout.getChildCount() == 0) return;
386  
387          if (!animated) {
388              mPublicLayout.animate().cancel();
389              mPrivateLayout.animate().cancel();
390              mPublicLayout.setAlpha(1f);
391              mPrivateLayout.setAlpha(1f);
392              mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
393              mPrivateLayout.setVisibility(mShowingPublic ? View.INVISIBLE : View.VISIBLE);
394          } else {
395              animateShowingPublic(delay, duration);
396          }
397  
398          updateVetoButton();
399          mShowingPublicInitialized = true;
400      }
401  
animateShowingPublic(long delay, long duration)402      private void animateShowingPublic(long delay, long duration) {
403          final View source = mShowingPublic ? mPrivateLayout : mPublicLayout;
404          View target = mShowingPublic ? mPublicLayout : mPrivateLayout;
405          source.setVisibility(View.VISIBLE);
406          target.setVisibility(View.VISIBLE);
407          target.setAlpha(0f);
408          source.animate().cancel();
409          target.animate().cancel();
410          source.animate()
411                  .alpha(0f)
412                  .setStartDelay(delay)
413                  .setDuration(duration)
414                  .withEndAction(new Runnable() {
415                      @Override
416                      public void run() {
417                          source.setVisibility(View.INVISIBLE);
418                      }
419                  });
420          target.animate()
421                  .alpha(1f)
422                  .setStartDelay(delay)
423                  .setDuration(duration);
424      }
425  
updateVetoButton()426      private void updateVetoButton() {
427          // public versions cannot be dismissed
428          mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE);
429      }
430  
getMaxExpandHeight()431      public int getMaxExpandHeight() {
432          return mShowingPublicForIntrinsicHeight ? mRowMinHeight : mMaxExpandHeight;
433      }
434  
435      @Override
isContentExpandable()436      public boolean isContentExpandable() {
437          NotificationContentView showingLayout = getShowingLayout();
438          return showingLayout.isContentExpandable();
439      }
440  
441      @Override
setActualHeight(int height, boolean notifyListeners)442      public void setActualHeight(int height, boolean notifyListeners) {
443          mPrivateLayout.setActualHeight(height);
444          mPublicLayout.setActualHeight(height);
445          if (mGuts != null) {
446              mGuts.setActualHeight(height);
447          }
448          invalidate();
449          super.setActualHeight(height, notifyListeners);
450      }
451  
452      @Override
getMaxHeight()453      public int getMaxHeight() {
454          NotificationContentView showingLayout = getShowingLayout();
455          return showingLayout.getMaxHeight();
456      }
457  
458      @Override
getMinHeight()459      public int getMinHeight() {
460          NotificationContentView showingLayout = getShowingLayout();
461          return showingLayout.getMinHeight();
462      }
463  
464      @Override
setClipTopAmount(int clipTopAmount)465      public void setClipTopAmount(int clipTopAmount) {
466          super.setClipTopAmount(clipTopAmount);
467          mPrivateLayout.setClipTopAmount(clipTopAmount);
468          mPublicLayout.setClipTopAmount(clipTopAmount);
469          if (mGuts != null) {
470              mGuts.setClipTopAmount(clipTopAmount);
471          }
472      }
473  
notifyContentUpdated()474      public void notifyContentUpdated() {
475          mPublicLayout.notifyContentUpdated();
476          mPrivateLayout.notifyContentUpdated();
477      }
478  
isMaxExpandHeightInitialized()479      public boolean isMaxExpandHeightInitialized() {
480          return mMaxExpandHeight != 0;
481      }
482  
getShowingLayout()483      private NotificationContentView getShowingLayout() {
484          return mShowingPublic ? mPublicLayout : mPrivateLayout;
485      }
486  
setExpansionLogger(ExpansionLogger logger, String key)487      public void setExpansionLogger(ExpansionLogger logger, String key) {
488          mLogger = logger;
489          mLoggingKey = key;
490      }
491  
492  
logExpansionEvent(boolean userAction, boolean wasExpanded)493      private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
494          final boolean nowExpanded = isExpanded();
495          if (wasExpanded != nowExpanded && mLogger != null) {
496              mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ;
497          }
498      }
499  }
500