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.notification.stack;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.util.MathUtils;
23 import android.view.View;
24 
25 import com.android.systemui.R;
26 import com.android.systemui.statusbar.NotificationShelf;
27 import com.android.systemui.statusbar.StatusBarState;
28 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
29 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
30 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
31 import com.android.systemui.statusbar.notification.row.ExpandableView;
32 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider;
33 import com.android.systemui.statusbar.policy.HeadsUpManager;
34 
35 import java.util.ArrayList;
36 
37 /**
38  * A global state to track all input states for the algorithm.
39  */
40 public class AmbientState {
41 
42     private static final float MAX_PULSE_HEIGHT = 100000f;
43 
44     private final SectionProvider mSectionProvider;
45     private ArrayList<ExpandableView> mDraggedViews = new ArrayList<>();
46     private int mScrollY;
47     private int mAnchorViewIndex;
48     private int mAnchorViewY;
49     private boolean mDimmed;
50     private ActivatableNotificationView mActivatedChild;
51     private float mOverScrollTopAmount;
52     private float mOverScrollBottomAmount;
53     private int mSpeedBumpIndex = -1;
54     private boolean mDozing;
55     private boolean mHideSensitive;
56     private float mStackTranslation;
57     private int mLayoutHeight;
58     private int mTopPadding;
59     private boolean mShadeExpanded;
60     private float mMaxHeadsUpTranslation;
61     private boolean mDismissAllInProgress;
62     private int mLayoutMinHeight;
63     private NotificationShelf mShelf;
64     private int mZDistanceBetweenElements;
65     private int mBaseZHeight;
66     private int mMaxLayoutHeight;
67     private ExpandableView mLastVisibleBackgroundChild;
68     private float mCurrentScrollVelocity;
69     private int mStatusBarState;
70     private float mExpandingVelocity;
71     private boolean mPanelTracking;
72     private boolean mExpansionChanging;
73     private boolean mPanelFullWidth;
74     private boolean mPulsing;
75     private boolean mUnlockHintRunning;
76     private boolean mQsCustomizerShowing;
77     private int mIntrinsicPadding;
78     private int mExpandAnimationTopChange;
79     private ExpandableNotificationRow mExpandingNotification;
80     private float mHideAmount;
81     private boolean mAppearing;
82     private float mPulseHeight = MAX_PULSE_HEIGHT;
83     private float mDozeAmount = 0.0f;
84     private HeadsUpManager mHeadUpManager;
85     private Runnable mOnPulseHeightChangedListener;
86     private ExpandableNotificationRow mTrackedHeadsUpRow;
87     private float mAppearFraction;
88 
AmbientState( Context context, @NonNull SectionProvider sectionProvider, HeadsUpManager headsUpManager)89     public AmbientState(
90             Context context,
91             @NonNull SectionProvider sectionProvider,
92             HeadsUpManager headsUpManager) {
93         mSectionProvider = sectionProvider;
94         mHeadUpManager = headsUpManager;
95         reload(context);
96     }
97 
98     /**
99      * Reload the dimens e.g. if the density changed.
100      */
reload(Context context)101     public void reload(Context context) {
102         mZDistanceBetweenElements = getZDistanceBetweenElements(context);
103         mBaseZHeight = getBaseHeight(mZDistanceBetweenElements);
104     }
105 
getZDistanceBetweenElements(Context context)106     private static int getZDistanceBetweenElements(Context context) {
107         return Math.max(1, context.getResources()
108                 .getDimensionPixelSize(R.dimen.z_distance_between_notifications));
109     }
110 
getBaseHeight(int zdistanceBetweenElements)111     private static int getBaseHeight(int zdistanceBetweenElements) {
112         return 4 * zdistanceBetweenElements;
113     }
114 
115     /**
116      * @return the launch height for notifications that are launched
117      */
getNotificationLaunchHeight(Context context)118     public static int getNotificationLaunchHeight(Context context) {
119         int zDistance = getZDistanceBetweenElements(context);
120         return getBaseHeight(zDistance) * 2;
121     }
122 
123     /**
124      * @return the basic Z height on which notifications remain.
125      */
getBaseZHeight()126     public int getBaseZHeight() {
127         return mBaseZHeight;
128     }
129 
130     /**
131      * @return the distance in Z between two overlaying notifications.
132      */
getZDistanceBetweenElements()133     public int getZDistanceBetweenElements() {
134         return mZDistanceBetweenElements;
135     }
136 
getScrollY()137     public int getScrollY() {
138         return mScrollY;
139     }
140 
setScrollY(int scrollY)141     public void setScrollY(int scrollY) {
142         this.mScrollY = scrollY;
143     }
144 
145     /**
146      * Index of the child view whose Y position on screen is returned by {@link #getAnchorViewY()}.
147      * Other views are laid out outwards from this view in both directions.
148      */
getAnchorViewIndex()149     public int getAnchorViewIndex() {
150         return mAnchorViewIndex;
151     }
152 
setAnchorViewIndex(int anchorViewIndex)153     public void setAnchorViewIndex(int anchorViewIndex) {
154         mAnchorViewIndex = anchorViewIndex;
155     }
156 
157     /** Current Y position of the view at {@link #getAnchorViewIndex()}. */
getAnchorViewY()158     public int getAnchorViewY() {
159         return mAnchorViewY;
160     }
161 
setAnchorViewY(int anchorViewY)162     public void setAnchorViewY(int anchorViewY) {
163         mAnchorViewY = anchorViewY;
164     }
165 
166     /** Call when dragging begins. */
onBeginDrag(ExpandableView view)167     public void onBeginDrag(ExpandableView view) {
168         mDraggedViews.add(view);
169     }
170 
onDragFinished(View view)171     public void onDragFinished(View view) {
172         mDraggedViews.remove(view);
173     }
174 
getDraggedViews()175     public ArrayList<ExpandableView> getDraggedViews() {
176         return mDraggedViews;
177     }
178 
179     /**
180      * @param dimmed Whether we are in a dimmed state (on the lockscreen), where the backgrounds are
181      *               translucent and everything is scaled back a bit.
182      */
setDimmed(boolean dimmed)183     public void setDimmed(boolean dimmed) {
184         mDimmed = dimmed;
185     }
186 
187     /** While dozing, we draw as little as possible, assuming a black background */
setDozing(boolean dozing)188     public void setDozing(boolean dozing) {
189         mDozing = dozing;
190     }
191 
192     /** Hide ratio of the status bar **/
setHideAmount(float hidemount)193     public void setHideAmount(float hidemount) {
194         if (hidemount == 1.0f && mHideAmount != hidemount) {
195             // Whenever we are fully hidden, let's reset the pulseHeight again
196             setPulseHeight(MAX_PULSE_HEIGHT);
197         }
198         mHideAmount = hidemount;
199     }
200 
201     /** Returns the hide ratio of the status bar */
getHideAmount()202     public float getHideAmount() {
203         return mHideAmount;
204     }
205 
setHideSensitive(boolean hideSensitive)206     public void setHideSensitive(boolean hideSensitive) {
207         mHideSensitive = hideSensitive;
208     }
209 
210     /**
211      * In dimmed mode, a child can be activated, which happens on the first tap of the double-tap
212      * interaction. This child is then scaled normally and its background is fully opaque.
213      */
setActivatedChild(ActivatableNotificationView activatedChild)214     public void setActivatedChild(ActivatableNotificationView activatedChild) {
215         mActivatedChild = activatedChild;
216     }
217 
isDimmed()218     public boolean isDimmed() {
219         // While we are expanding from pulse, we want the notifications not to be dimmed, otherwise
220         // you'd see the difference to the pulsing notification
221         return mDimmed && !(isPulseExpanding() && mDozeAmount == 1.0f);
222     }
223 
isDozing()224     public boolean isDozing() {
225         return mDozing;
226     }
227 
isHideSensitive()228     public boolean isHideSensitive() {
229         return mHideSensitive;
230     }
231 
getActivatedChild()232     public ActivatableNotificationView getActivatedChild() {
233         return mActivatedChild;
234     }
235 
setOverScrollAmount(float amount, boolean onTop)236     public void setOverScrollAmount(float amount, boolean onTop) {
237         if (onTop) {
238             mOverScrollTopAmount = amount;
239         } else {
240             mOverScrollBottomAmount = amount;
241         }
242     }
243 
getOverScrollAmount(boolean top)244     public float getOverScrollAmount(boolean top) {
245         return top ? mOverScrollTopAmount : mOverScrollBottomAmount;
246     }
247 
getSpeedBumpIndex()248     public int getSpeedBumpIndex() {
249         return mSpeedBumpIndex;
250     }
251 
setSpeedBumpIndex(int shelfIndex)252     public void setSpeedBumpIndex(int shelfIndex) {
253         mSpeedBumpIndex = shelfIndex;
254     }
255 
getSectionProvider()256     public SectionProvider getSectionProvider() {
257         return mSectionProvider;
258     }
259 
getStackTranslation()260     public float getStackTranslation() {
261         return mStackTranslation;
262     }
263 
setStackTranslation(float stackTranslation)264     public void setStackTranslation(float stackTranslation) {
265         mStackTranslation = stackTranslation;
266     }
267 
setLayoutHeight(int layoutHeight)268     public void setLayoutHeight(int layoutHeight) {
269         mLayoutHeight = layoutHeight;
270     }
271 
getTopPadding()272     public float getTopPadding() {
273         return mTopPadding;
274     }
275 
setTopPadding(int topPadding)276     public void setTopPadding(int topPadding) {
277         mTopPadding = topPadding;
278     }
279 
getInnerHeight()280     public int getInnerHeight() {
281         return getInnerHeight(false /* ignorePulseHeight */);
282     }
283 
284     /**
285      * @param ignorePulseHeight ignore the pulse height for this request
286      * @return the inner height of the algorithm.
287      */
getInnerHeight(boolean ignorePulseHeight)288     public int getInnerHeight(boolean ignorePulseHeight) {
289         if (mDozeAmount == 1.0f && !isPulseExpanding()) {
290             return mShelf.getHeight();
291         }
292         int height = Math.max(mLayoutMinHeight,
293                 Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding);
294         if (ignorePulseHeight) {
295             return height;
296         }
297         float pulseHeight = Math.min(mPulseHeight, (float) height);
298         return (int) MathUtils.lerp(height, pulseHeight, mDozeAmount);
299     }
300 
isPulseExpanding()301     public boolean isPulseExpanding() {
302         return mPulseHeight != MAX_PULSE_HEIGHT && mDozeAmount != 0.0f && mHideAmount != 1.0f;
303     }
304 
isShadeExpanded()305     public boolean isShadeExpanded() {
306         return mShadeExpanded;
307     }
308 
setShadeExpanded(boolean shadeExpanded)309     public void setShadeExpanded(boolean shadeExpanded) {
310         mShadeExpanded = shadeExpanded;
311     }
312 
setMaxHeadsUpTranslation(float maxHeadsUpTranslation)313     public void setMaxHeadsUpTranslation(float maxHeadsUpTranslation) {
314         mMaxHeadsUpTranslation = maxHeadsUpTranslation;
315     }
316 
getMaxHeadsUpTranslation()317     public float getMaxHeadsUpTranslation() {
318         return mMaxHeadsUpTranslation;
319     }
320 
setDismissAllInProgress(boolean dismissAllInProgress)321     public void setDismissAllInProgress(boolean dismissAllInProgress) {
322         mDismissAllInProgress = dismissAllInProgress;
323     }
324 
isDismissAllInProgress()325     public boolean isDismissAllInProgress() {
326         return mDismissAllInProgress;
327     }
328 
setLayoutMinHeight(int layoutMinHeight)329     public void setLayoutMinHeight(int layoutMinHeight) {
330         mLayoutMinHeight = layoutMinHeight;
331     }
332 
setShelf(NotificationShelf shelf)333     public void setShelf(NotificationShelf shelf) {
334         mShelf = shelf;
335     }
336 
337     @Nullable
getShelf()338     public NotificationShelf getShelf() {
339         return mShelf;
340     }
341 
setLayoutMaxHeight(int maxLayoutHeight)342     public void setLayoutMaxHeight(int maxLayoutHeight) {
343         mMaxLayoutHeight = maxLayoutHeight;
344     }
345 
346     /**
347      * Sets the last visible view of the host layout, that has a background, i.e the very last
348      * view in the shade, without the clear all button.
349      */
setLastVisibleBackgroundChild( ExpandableView lastVisibleBackgroundChild)350     public void setLastVisibleBackgroundChild(
351             ExpandableView lastVisibleBackgroundChild) {
352         mLastVisibleBackgroundChild = lastVisibleBackgroundChild;
353     }
354 
getLastVisibleBackgroundChild()355     public ExpandableView getLastVisibleBackgroundChild() {
356         return mLastVisibleBackgroundChild;
357     }
358 
setCurrentScrollVelocity(float currentScrollVelocity)359     public void setCurrentScrollVelocity(float currentScrollVelocity) {
360         mCurrentScrollVelocity = currentScrollVelocity;
361     }
362 
getCurrentScrollVelocity()363     public float getCurrentScrollVelocity() {
364         return mCurrentScrollVelocity;
365     }
366 
isOnKeyguard()367     public boolean isOnKeyguard() {
368         return mStatusBarState == StatusBarState.KEYGUARD;
369     }
370 
setStatusBarState(int statusBarState)371     public void setStatusBarState(int statusBarState) {
372         mStatusBarState = statusBarState;
373     }
374 
setExpandingVelocity(float expandingVelocity)375     public void setExpandingVelocity(float expandingVelocity) {
376         mExpandingVelocity = expandingVelocity;
377     }
378 
setExpansionChanging(boolean expansionChanging)379     public void setExpansionChanging(boolean expansionChanging) {
380         mExpansionChanging = expansionChanging;
381     }
382 
isExpansionChanging()383     public boolean isExpansionChanging() {
384         return mExpansionChanging;
385     }
386 
getExpandingVelocity()387     public float getExpandingVelocity() {
388         return mExpandingVelocity;
389     }
390 
setPanelTracking(boolean panelTracking)391     public void setPanelTracking(boolean panelTracking) {
392         mPanelTracking = panelTracking;
393     }
394 
hasPulsingNotifications()395     public boolean hasPulsingNotifications() {
396         return mPulsing && mHeadUpManager != null && mHeadUpManager.hasNotifications();
397     }
398 
setPulsing(boolean hasPulsing)399     public void setPulsing(boolean hasPulsing) {
400         mPulsing = hasPulsing;
401     }
402 
403     /**
404      * @return if we're pulsing in general
405      */
isPulsing()406     public boolean isPulsing() {
407         return mPulsing;
408     }
409 
isPulsing(NotificationEntry entry)410     public boolean isPulsing(NotificationEntry entry) {
411         if (!mPulsing || mHeadUpManager == null) {
412             return false;
413         }
414         return mHeadUpManager.isAlerting(entry.getKey());
415     }
416 
isPanelTracking()417     public boolean isPanelTracking() {
418         return mPanelTracking;
419     }
420 
isPanelFullWidth()421     public boolean isPanelFullWidth() {
422         return mPanelFullWidth;
423     }
424 
setPanelFullWidth(boolean panelFullWidth)425     public void setPanelFullWidth(boolean panelFullWidth) {
426         mPanelFullWidth = panelFullWidth;
427     }
428 
setUnlockHintRunning(boolean unlockHintRunning)429     public void setUnlockHintRunning(boolean unlockHintRunning) {
430         mUnlockHintRunning = unlockHintRunning;
431     }
432 
isUnlockHintRunning()433     public boolean isUnlockHintRunning() {
434         return mUnlockHintRunning;
435     }
436 
isQsCustomizerShowing()437     public boolean isQsCustomizerShowing() {
438         return mQsCustomizerShowing;
439     }
440 
setQsCustomizerShowing(boolean qsCustomizerShowing)441     public void setQsCustomizerShowing(boolean qsCustomizerShowing) {
442         mQsCustomizerShowing = qsCustomizerShowing;
443     }
444 
setIntrinsicPadding(int intrinsicPadding)445     public void setIntrinsicPadding(int intrinsicPadding) {
446         mIntrinsicPadding = intrinsicPadding;
447     }
448 
getIntrinsicPadding()449     public int getIntrinsicPadding() {
450         return mIntrinsicPadding;
451     }
452 
453     /**
454      * @return whether a view is dozing and not pulsing right now
455      */
isDozingAndNotPulsing(ExpandableView view)456     public boolean isDozingAndNotPulsing(ExpandableView view) {
457         if (view instanceof ExpandableNotificationRow) {
458             return isDozingAndNotPulsing((ExpandableNotificationRow) view);
459         }
460         return false;
461     }
462 
463     /**
464      * @return whether a row is dozing and not pulsing right now
465      */
isDozingAndNotPulsing(ExpandableNotificationRow row)466     public boolean isDozingAndNotPulsing(ExpandableNotificationRow row) {
467         return isDozing() && !isPulsing(row.getEntry());
468     }
469 
setExpandAnimationTopChange(int expandAnimationTopChange)470     public void setExpandAnimationTopChange(int expandAnimationTopChange) {
471         mExpandAnimationTopChange = expandAnimationTopChange;
472     }
473 
setExpandingNotification(ExpandableNotificationRow row)474     public void setExpandingNotification(ExpandableNotificationRow row) {
475         mExpandingNotification = row;
476     }
477 
getExpandingNotification()478     public ExpandableNotificationRow getExpandingNotification() {
479         return mExpandingNotification;
480     }
481 
getExpandAnimationTopChange()482     public int getExpandAnimationTopChange() {
483         return mExpandAnimationTopChange;
484     }
485 
486     /**
487      * @return {@code true } when shade is completely hidden: in AOD, ambient display or when
488      * bypassing.
489      */
isFullyHidden()490     public boolean isFullyHidden() {
491         return mHideAmount == 1;
492     }
493 
isHiddenAtAll()494     public boolean isHiddenAtAll() {
495         return mHideAmount != 0;
496     }
497 
setAppearing(boolean appearing)498     public void setAppearing(boolean appearing) {
499         mAppearing = appearing;
500     }
501 
isAppearing()502     public boolean isAppearing() {
503         return mAppearing;
504     }
505 
setPulseHeight(float height)506     public void setPulseHeight(float height) {
507         if (height != mPulseHeight) {
508             mPulseHeight = height;
509             if (mOnPulseHeightChangedListener != null) {
510                 mOnPulseHeightChangedListener.run();
511             }
512         }
513     }
514 
getPulseHeight()515     public float getPulseHeight() {
516         if (mPulseHeight == MAX_PULSE_HEIGHT) {
517             // If we're not pulse expanding, the height should be 0
518             return 0;
519         }
520         return mPulseHeight;
521     }
522 
setDozeAmount(float dozeAmount)523     public void setDozeAmount(float dozeAmount) {
524         if (dozeAmount != mDozeAmount) {
525             mDozeAmount = dozeAmount;
526             if (dozeAmount == 0.0f || dozeAmount == 1.0f) {
527                 // We woke all the way up, let's reset the pulse height
528                 setPulseHeight(MAX_PULSE_HEIGHT);
529             }
530         }
531     }
532 
533     /**
534      * Is the device fully awake, which is different from not tark at all when there are pulsing
535      * notifications.
536      */
isFullyAwake()537     public boolean isFullyAwake() {
538         return mDozeAmount == 0.0f;
539     }
540 
setOnPulseHeightChangedListener(Runnable onPulseHeightChangedListener)541     public void setOnPulseHeightChangedListener(Runnable onPulseHeightChangedListener) {
542         mOnPulseHeightChangedListener = onPulseHeightChangedListener;
543     }
544 
getOnPulseHeightChangedListener()545     public Runnable getOnPulseHeightChangedListener() {
546         return mOnPulseHeightChangedListener;
547     }
548 
setTrackedHeadsUpRow(ExpandableNotificationRow row)549     public void setTrackedHeadsUpRow(ExpandableNotificationRow row) {
550         mTrackedHeadsUpRow = row;
551     }
552 
553     /**
554      * Returns the currently tracked heads up row, if there is one and it is currently above the
555      * shelf (still appearing).
556      */
getTrackedHeadsUpRow()557     public ExpandableNotificationRow getTrackedHeadsUpRow() {
558         if (mTrackedHeadsUpRow == null || !mTrackedHeadsUpRow.isAboveShelf()) {
559             return null;
560         }
561         return mTrackedHeadsUpRow;
562     }
563 
setAppearFraction(float appearFraction)564     public void setAppearFraction(float appearFraction) {
565         mAppearFraction = appearFraction;
566     }
567 
getAppearFraction()568     public float getAppearFraction() {
569         return mAppearFraction;
570     }
571 }
572