Handling a few more border cases with HUNs

Also does sorting correctly now.
The status bar now allows touches below when a
heads-up is on.
Also fixes a few flashes when a heads up was
dismissed or appeared.

Change-Id: I4d90a07333ad2e5ea2a13704cdc9d9184716681a
This commit is contained in:
Selim Cinek
2015-04-07 10:51:49 -07:00
parent b8f09cf553
commit a59ecc3401
10 changed files with 304 additions and 135 deletions

View File

@@ -1880,7 +1880,7 @@ public abstract class BaseStatusBar extends SystemUI implements
}
boolean applyInPlace = shouldApplyInPlace(entry, n);
final boolean shouldInterrupt = shouldInterrupt(notification);
final boolean alertAgain = shouldInterrupt && alertAgain(entry, n);
final boolean alertAgain = alertAgain(entry, n);
boolean isHeadsUp = shouldInterrupt && alertAgain;
entry.notification = notification;
@@ -1947,8 +1947,12 @@ public abstract class BaseStatusBar extends SystemUI implements
if (wasHeadsUp) {
mHeadsUpManager.updateNotification(entry, alertAgain);
if (!shouldInterrupt) {
// We don't want this to be interrupting anymore, lets remove it
mHeadsUpManager.removeNotification(key);
}
} else if (shouldInterrupt && alertAgain) {
// This notification was updated to be a heads-up, show it!
mHeadsUpManager.showNotification(entry);
}
}

View File

@@ -510,9 +510,6 @@ public class NotificationPanelView extends PanelView implements
@Override
protected void flingToHeight(float vel, boolean expand, float target) {
if (!expand && mHeadsUpManager.hasPinnedHeadsUp()) {
target = mHeadsUpManager.getHighestPinnedHeadsUp();
}
mHeadsUpTouchHelper.notifyFling(!expand);
super.flingToHeight(vel, expand, target);
}
@@ -722,7 +719,7 @@ public class NotificationPanelView extends PanelView implements
|| event.getActionMasked() == MotionEvent.ACTION_UP) {
mConflictingQsExpansionGesture = false;
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isShadeCollapsed()
&& mQsExpansionEnabled) {
mTwoFingerQsExpandPossible = true;
}
@@ -1774,7 +1771,7 @@ public class NotificationPanelView extends PanelView implements
}
private void updateMaxHeadsUpTranslation() {
mNotificationStackScroller.setMaxHeadsUpTranslation(getHeight() - mBottomBarHeight);
mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mBottomBarHeight);
}
@Override
@@ -2151,16 +2148,12 @@ public class NotificationPanelView extends PanelView implements
@Override
public void OnHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
// TODO: figure out the conditions when not to generate an animation
mNotificationStackScroller.generateHeadsUpAnimation(entry.row, isHeadsUp);
if (isShadeCollapsed()) {
setExpandedHeight(mHeadsUpManager.getHighestPinnedHeadsUp());
}
}
private boolean isShadeCollapsed() {
// TODO: handle this cleaner
return mHeader.getTranslationY() + mHeader.getCollapsedHeight() <= 0;
@Override
protected boolean isShadeCollapsed() {
return mExpandedHeight == 0 || mHeadsUpManager.hasPinnedHeadsUp();
}
@Override

View File

@@ -659,7 +659,7 @@ public abstract class PanelView extends FrameLayout {
// If the user isn't actively poking us, let's update the height
if ((!mTracking || isTrackingBlocked())
&& mHeightAnimator == null
&& mExpandedHeight > 0
&& !isShadeCollapsed()
&& currentMaxPanelHeight != mExpandedHeight
&& !mPeekPending
&& mPeekAnimator == null
@@ -730,6 +730,7 @@ public abstract class PanelView extends FrameLayout {
}
public boolean isFullyCollapsed() {
// TODO: look into whether this is still correct with HUN's
return mExpandedHeight <= 0;
}
@@ -1019,6 +1020,8 @@ public abstract class PanelView extends FrameLayout {
*/
protected abstract int getClearAllHeight();
protected abstract boolean isShadeCollapsed();
public void setHeadsUpManager(HeadsUpManager headsUpManager) {
mHeadsUpManager = headsUpManager;
}

View File

@@ -160,7 +160,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
@@ -615,7 +615,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
R.color.notification_panel_solid_background)));
}
mHeadsUpManager = new HeadsUpManager(context);
mHeadsUpManager = new HeadsUpManager(context, mNotificationPanel.getViewTreeObserver());
mHeadsUpManager.setBar(this);
mHeadsUpManager.addListener(this);
mHeadsUpManager.addListener(mNotificationPanel);
@@ -1880,10 +1880,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
@Override
public void escalateHeadsUp() {
TreeMap<String, HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getEntries();
for (String key : entries.keySet()) {
Entry entry = entries.get(key).entry;
final StatusBarNotification sbn = entry.notification;
TreeSet<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getSortedEntries();
for (HeadsUpManager.HeadsUpEntry entry : entries) {
final StatusBarNotification sbn = entry.entry.notification;
final Notification notification = sbn.getNotification();
if (notification.fullScreenIntent != null) {
if (DEBUG) {

View File

@@ -172,11 +172,20 @@ public class StatusBarWindowManager {
applyUserActivityTimeout(state);
applyInputFeatures(state);
applyFitsSystemWindows(state);
applyModalFlag(state);
if (mLp.copyFrom(mLpChanged) != 0) {
mWindowManager.updateViewLayout(mStatusBarView, mLp);
}
}
private void applyModalFlag(State state) {
if (state.headsUpShowing) {
mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
} else {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
}
public void setKeyguardShowing(boolean showing) {
mCurrentState.keyguardShowing = showing;
apply(mCurrentState);

View File

@@ -30,16 +30,18 @@ import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Stack;
import java.util.TreeMap;
import java.util.TreeSet;
public class HeadsUpManager {
public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener {
private static final String TAG = "HeadsUpManager";
private static final boolean DEBUG = false;
private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
@@ -76,8 +78,8 @@ public class HeadsUpManager {
private PhoneStatusBar mBar;
private int mSnoozeLengthMs;
private ContentObserver mSettingsObserver;
private TreeMap<String ,HeadsUpEntry> mHeadsUpEntries = new TreeMap<>();
private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
private TreeSet<HeadsUpEntry> mSortedEntries = new TreeSet<>();
private HashSet<String> mSwipedOutKeys = new HashSet<>();
private int mUser;
private Clock mClock;
@@ -86,8 +88,9 @@ public class HeadsUpManager {
private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
private boolean mIsExpanded;
private boolean mHasPinnedHeadsUp;
private int[] mTmpTwoArray = new int[2];
public HeadsUpManager(final Context context) {
public HeadsUpManager(final Context context, ViewTreeObserver observer) {
Resources resources = context.getResources();
mTouchSensitivityDelay = resources.getInteger(R.integer.heads_up_sensitivity_delay);
if (DEBUG) Log.v(TAG, "create() " + mTouchSensitivityDelay);
@@ -95,7 +98,7 @@ public class HeadsUpManager {
mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
mSnoozeLengthMs = mDefaultSnoozeLengthMs;
mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
mHeadsUpNotificationDecay = 2000000;
mHeadsUpNotificationDecay = 200000000/*resources.getInteger(R.integer.heads_up_notification_decay)*/;;
mClock = new Clock();
// TODO: shadow mSwipeHelper.setMaxSwipeProgress(mMaxAlpha);
@@ -116,12 +119,7 @@ public class HeadsUpManager {
Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
mSettingsObserver);
if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs);
// TODO: investigate whether this is still needed
// if (!mHeadsUpEntries.isEmpty()) {
// whoops, we're on already!
// showNotification(mHeadsUpEntries);
// }
observer.addOnComputeInternalInsetsListener(this);
}
public void setBar(PhoneStatusBar bar) {
@@ -144,7 +142,6 @@ public class HeadsUpManager {
addHeadsUpEntry(headsUp);
updateNotification(headsUp, true);
headsUp.setInterruption();
updatePinnedHeadsUpState(false);
}
/**
@@ -164,26 +161,32 @@ public class HeadsUpManager {
}
private void addHeadsUpEntry(NotificationData.Entry entry) {
boolean wasEmpty = mHeadsUpEntries.isEmpty();
HeadsUpEntry headsUpEntry = mEntryPool.acquire();
// This will also add the entry to the sortedList
headsUpEntry.setEntry(entry);
mHeadsUpEntries.put(entry.key, headsUpEntry);
entry.row.setHeadsUp(true);
if (!entry.row.isInShade() && mIsExpanded) {
headsUpEntry.entry.row.setInShade(true);
}
updatePinnedHeadsUpState(false);
for (OnHeadsUpChangedListener listener : mListeners) {
listener.OnHeadsUpStateChanged(entry, true);
}
entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
entry.row.setHeadsUp(true);
}
private void removeHeadsUpEntry(NotificationData.Entry entry) {
HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key);
mSortedEntries.remove(remove);
mEntryPool.release(remove);
entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
entry.row.setHeadsUp(false);
updatePinnedHeadsUpState(false);
for (OnHeadsUpChangedListener listener : mListeners) {
listener.OnHeadsUpStateChanged(entry, false);
}
updatePinnedHeadsUpState(false);
}
private void updatePinnedHeadsUpState(boolean forceImmediate) {
@@ -238,7 +241,8 @@ public class HeadsUpManager {
*/
public void releaseAllImmediately() {
if (DEBUG) Log.v(TAG, "releaseAllImmediately");
for (String key: mHeadsUpEntries.keySet()) {
HashSet<String> keys = new HashSet<>(mHeadsUpEntries.keySet());
for (String key: keys) {
releaseImmediately(key);
}
}
@@ -287,12 +291,12 @@ public class HeadsUpManager {
return mHeadsUpEntries.get(key).entry;
}
public TreeMap<String, HeadsUpEntry> getEntries() {
return mHeadsUpEntries;
public TreeSet<HeadsUpEntry> getSortedEntries() {
return mSortedEntries;
}
public HeadsUpEntry getTopEntry() {
return mHeadsUpEntries.isEmpty() ? null : mHeadsUpEntries.lastEntry().getValue();
return mSortedEntries.isEmpty() ? null : mSortedEntries.first();
}
/**
@@ -313,13 +317,25 @@ public class HeadsUpManager {
}
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
// TODO: Look into touchable region
// mContentHolder.getLocationOnScreen(mTmpTwoArray);
//
// info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
// info.touchableRegion.set(mTmpTwoArray[0], mTmpTwoArray[1],
// mTmpTwoArray[0] + mContentHolder.getWidth(),
// mTmpTwoArray[1] + mContentHolder.getHeight());
if (!mIsExpanded && mHasPinnedHeadsUp) {
int minX = Integer.MAX_VALUE;
int maxX = 0;
int minY = Integer.MAX_VALUE;
int maxY = 0;
for (HeadsUpEntry entry: mSortedEntries) {
ExpandableNotificationRow row = entry.entry.row;
if (!row.isInShade()) {
row.getLocationOnScreen(mTmpTwoArray);
minX = Math.min(minX, mTmpTwoArray[0]);
minY = Math.min(minY, 0);
maxX = Math.max(maxX, mTmpTwoArray[0] + row.getWidth());
maxY = Math.max(maxY, row.getHeadsUpHeight());
}
}
info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
info.touchableRegion.set(minX, minY, maxX, maxY);
}
}
public void setUser(int user) {
@@ -332,8 +348,8 @@ public class HeadsUpManager {
pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
pw.print(" now="); pw.println(SystemClock.elapsedRealtime());
pw.print(" mUser="); pw.println(mUser);
for (String key: mHeadsUpEntries.keySet()) {
pw.print(" HeadsUpEntry="); pw.println(mHeadsUpEntries.get(key));
for (HeadsUpEntry entry: mSortedEntries) {
pw.print(" HeadsUpEntry="); pw.println(entry.entry);
}
int N = mSnoozedPackages.size();
pw.println(" snoozed packages: " + N);
@@ -363,8 +379,7 @@ public class HeadsUpManager {
public float getHighestPinnedHeadsUp() {
float max = 0;
for (String key: mHeadsUpEntries.keySet()) {
HeadsUpEntry entry = mHeadsUpEntries.get(key);
for (HeadsUpEntry entry: mSortedEntries) {
if (!entry.entry.row.isInShade()) {
max = Math.max(max, entry.entry.row.getActualHeight());
}
@@ -437,13 +452,14 @@ public class HeadsUpManager {
earliestRemovaltime = currentTime + mMinimumDisplayTime;
removeAutoCancelCallbacks();
mHandler.postDelayed(mRemoveHeadsUpRunnable, removeDelay);
updateSortOrder(HeadsUpEntry.this);
}
@Override
public int compareTo(HeadsUpEntry o) {
return postTime < o.postTime ? -1
return postTime < o.postTime ? 1
: postTime == o.postTime ? 0
: 1;
: -1;
}
public void removeAutoCancelCallbacks() {
@@ -461,6 +477,16 @@ public class HeadsUpManager {
}
}
/**
* Update the sorted heads up order.
*
* @param headsUpEntry the headsUp that changed
*/
private void updateSortOrder(HeadsUpEntry headsUpEntry) {
mSortedEntries.remove(headsUpEntry);
mSortedEntries.add(headsUpEntry);
}
public static class Clock {
public long currentTimeMillis() {
return SystemClock.elapsedRealtime();

View File

@@ -17,11 +17,13 @@
package com.android.systemui.statusbar.stack;
import android.view.View;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.ArrayList;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* A global state to track all input states for the algorithm.
@@ -37,7 +39,7 @@ public class AmbientState {
private boolean mDark;
private boolean mHideSensitive;
private HeadsUpManager mHeadsUpManager;
private float mPaddingOffset;
private float mStackTranslation;
private int mLayoutHeight;
private int mTopPadding;
private boolean mShadeExpanded;
@@ -128,16 +130,16 @@ public class AmbientState {
mHeadsUpManager = headsUpManager;
}
public TreeMap<String, HeadsUpManager.HeadsUpEntry> getHeadsUpEntries() {
return mHeadsUpManager.getEntries();
public TreeSet<HeadsUpManager.HeadsUpEntry> getSortedHeadsUpEntries() {
return mHeadsUpManager.getSortedEntries();
}
public float getPaddingOffset() {
return mPaddingOffset;
public float getStackTranslation() {
return mStackTranslation;
}
public void setPaddingOffset(float paddingOffset) {
mPaddingOffset = paddingOffset;
public void setStackTranslation(float stackTranslation) {
mStackTranslation = stackTranslation;
}
public int getLayoutHeight() {
@@ -148,7 +150,7 @@ public class AmbientState {
mLayoutHeight = layoutHeight;
}
public int getTopPadding() {
public float getTopPadding() {
return mTopPadding;
}
@@ -157,7 +159,13 @@ public class AmbientState {
}
public int getInnerHeight() {
return mLayoutHeight - mTopPadding;
return mLayoutHeight - mTopPadding - getTopHeadsUpPushIn();
}
private int getTopHeadsUpPushIn() {
ExpandableNotificationRow topHeadsUpEntry = getTopHeadsUpEntry();
return topHeadsUpEntry != null ? topHeadsUpEntry.getHeadsUpHeight()
- topHeadsUpEntry.getMinHeight(): 0;
}
public boolean isShadeExpanded() {
@@ -175,4 +183,9 @@ public class AmbientState {
public float getMaxHeadsUpTranslation() {
return mMaxHeadsUpTranslation;
}
public ExpandableNotificationRow getTopHeadsUpEntry() {
HeadsUpManager.HeadsUpEntry topEntry = mHeadsUpManager.getTopEntry();
return topEntry == null ? null : topEntry.entry.row;
}
}

View File

@@ -123,15 +123,15 @@ public class NotificationStackScrollLayout extends ViewGroup
private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
private AmbientState mAmbientState = new AmbientState();
private NotificationGroupManager mGroupManager;
private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>();
private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>();
private ArrayList<View> mSnappedBackChildren = new ArrayList<View>();
private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>();
private ArrayList<View> mChildrenChangingPositions = new ArrayList<View>();
private ArrayList<View> mChildrenToAddAnimated = new ArrayList<>();
private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
private ArrayList<AnimationEvent> mAnimationEvents
= new ArrayList<AnimationEvent>();
private ArrayList<View> mSwipedOutViews = new ArrayList<View>();
private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
private ArrayList<View> mSwipedOutViews = new ArrayList<>();
private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
private boolean mAnimationsEnabled;
private boolean mChangePositionInProgress;
@@ -145,7 +145,6 @@ public class NotificationStackScrollLayout extends ViewGroup
* The raw amount of the overScroll on the bottom, which is not rubber-banded.
*/
private float mOverScrolledBottomPixels;
private OnChildLocationsChangedListener mListener;
private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
@@ -173,7 +172,6 @@ public class NotificationStackScrollLayout extends ViewGroup
* Was the scroller scrolled to the top when the down motion was observed?
*/
private boolean mScrolledToTopOnFirstDown;
/**
* The minimal amount of over scroll which is needed in order to switch to the quick settings
* when over scrolling on a expanded card.
@@ -187,15 +185,14 @@ public class NotificationStackScrollLayout extends ViewGroup
private boolean mRequestViewResizeAnimationOnLayout;
private boolean mNeedViewResizeAnimation;
private View mExpandedGroupView;
private boolean mEverythingNeedsAnimation;
/**
* The maximum scrollPosition which we are allowed to reach when a notification was expanded.
* This is needed to avoid scrolling too far after the notification was collapsed in the same
* motion.
*/
private int mMaxScrollAfterExpand;
private SwipeHelper.LongPressListener mLongPressListener;
/**
@@ -206,8 +203,8 @@ public class NotificationStackScrollLayout extends ViewGroup
private ViewGroup mScrollView;
private boolean mInterceptDelegateEnabled;
private boolean mDelegateToScrollView;
private boolean mDisallowScrollingInThisMotion;
private boolean mDisallowScrollingInThisMotion;
private long mGoToFullShadeDelay;
private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
= new ViewTreeObserver.OnPreDrawListener() {
@@ -529,7 +526,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private void setPaddingOffset(float paddingOffset) {
if (paddingOffset != mPaddingOffset) {
mPaddingOffset = paddingOffset;
mAmbientState.setPaddingOffset(paddingOffset);
mAmbientState.setStackTranslation(paddingOffset);
requestChildrenUpdate();
}
}
@@ -617,13 +614,29 @@ public class NotificationStackScrollLayout extends ViewGroup
public void onBeginDrag(View v) {
setSwipingInProgress(true);
mAmbientState.onBeginDrag(v);
if (mAnimationsEnabled) {
if (mAnimationsEnabled && !isPinnedHeadsUp(v)) {
mDragAnimPendingChildren.add(v);
mNeedsAnimation = true;
}
requestChildrenUpdate();
}
private boolean isPinnedHeadsUp(View v) {
if (v instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) v;
return row.isHeadsUp() && !row.isInShade();
}
return false;
}
private boolean isHeadsUp(View v) {
if (v instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) v;
return row.isHeadsUp();
}
return false;
}
public void onDragCancelled(View v) {
setSwipingInProgress(false);
}
@@ -693,6 +706,10 @@ public class NotificationStackScrollLayout extends ViewGroup
if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
if (slidingChild instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
if (row.isHeadsUp() && !row.isInShade()
&& mHeadsUpManager.getTopEntry().entry.row != row) {
continue;
}
return row.getViewAtPosition(touchY - childTop);
}
return slidingChild;
@@ -1788,6 +1805,10 @@ public class NotificationStackScrollLayout extends ViewGroup
}
mNeedsAnimation = true;
}
if (isHeadsUp(child)) {
mAddedHeadsUpChildren.add(child);
mChildrenToAddAnimated.remove(child);
}
}
/**
@@ -1826,6 +1847,7 @@ public class NotificationStackScrollLayout extends ViewGroup
}
private void generateChildHierarchyEvents() {
generateHeadsUpAnimationEvents();
generateChildRemovalEvents();
generateChildAdditionEvents();
generatePositionChangeEvents();
@@ -1839,19 +1861,42 @@ public class NotificationStackScrollLayout extends ViewGroup
generateGoToFullShadeEvent();
generateViewResizeEvent();
generateGroupExpansionEvent();
generateHeadsUpAnimationEvents();
generateAnimateEverythingEvent();
mNeedsAnimation = false;
}
private void generateHeadsUpAnimationEvents() {
for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
int type = eventPair.second ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR
: AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
mAnimationEvents.add(new AnimationEvent(eventPair.first,
type));
ExpandableNotificationRow row = eventPair.first;
boolean isHeadsUp = eventPair.second;
int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
boolean onBottom = false;
if (!row.isInShade() && !isHeadsUp) {
type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
} else if (mAddedHeadsUpChildren.contains(row)) {
if (!row.isInShade() || shouldHunAppearFromBottom(row)) {
// Our custom add animation
type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
} else {
// Normal add animation
type = AnimationEvent.ANIMATION_TYPE_ADD;
}
onBottom = row.isInShade();
}
AnimationEvent event = new AnimationEvent(row, type);
event.headsUpFromBottom = onBottom;
mAnimationEvents.add(event);
}
mHeadsUpChangeAnimations.clear();
mAddedHeadsUpChildren.clear();
}
private boolean shouldHunAppearFromBottom(ExpandableNotificationRow row) {
StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
return false;
}
return true;
}
private void generateGroupExpansionEvent() {
@@ -2649,10 +2694,19 @@ public class NotificationStackScrollLayout extends ViewGroup
public void setShadeExpanded(boolean shadeExpanded) {
mAmbientState.setShadeExpanded(shadeExpanded);
mStateAnimator.setShadeExpanded(shadeExpanded);
}
public void setMaxHeadsUpTranslation(int maxTranslation) {
mAmbientState.setMaxHeadsUpTranslation(maxTranslation);
/**
* Set the boundary for the bottom heads up position. The heads up will always be above this
* position.
*
* @param height the height of the screen
* @param bottomBarHeight the height of the bar on the bottom
*/
public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
mStateAnimator.setHeadsUpAppearHeightBottom(height);
requestChildrenUpdate();
}
@@ -2820,6 +2874,14 @@ public class NotificationStackScrollLayout extends ViewGroup
.animateY()
.animateZ(),
// ANIMATION_TYPE_HEADS_UP_OTHER
new AnimationFilter()
.animateAlpha()
.animateHeight()
.animateTopInset()
.animateY()
.animateZ(),
// ANIMATION_TYPE_EVERYTHING
new AnimationFilter()
.animateAlpha()
@@ -2883,6 +2945,9 @@ public class NotificationStackScrollLayout extends ViewGroup
// ANIMATION_TYPE_HEADS_UP_DISAPPEAR
StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
// ANIMATION_TYPE_HEADS_UP_OTHER
StackStateAnimator.ANIMATION_DURATION_STANDARD,
// ANIMATION_TYPE_EVERYTHING
StackStateAnimator.ANIMATION_DURATION_STANDARD,
};
@@ -2903,7 +2968,8 @@ public class NotificationStackScrollLayout extends ViewGroup
static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
static final int ANIMATION_TYPE_EVERYTHING = 16;
static final int ANIMATION_TYPE_HEADS_UP_OTHER = 16;
static final int ANIMATION_TYPE_EVERYTHING = 17;
static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
@@ -2915,6 +2981,7 @@ public class NotificationStackScrollLayout extends ViewGroup
final long length;
View viewAfterChangingView;
int darkAnimationOriginIndex;
boolean headsUpFromBottom;
AnimationEvent(View view, int type) {
this(view, type, LENGTHS[type]);

View File

@@ -29,7 +29,7 @@ import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* The Algorithm of the {@link com.android.systemui.statusbar.stack
@@ -167,7 +167,7 @@ public class StackScrollAlgorithm {
handleDraggedViews(ambientState, resultState, algorithmState);
updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
updateClipping(resultState, algorithmState);
updateClipping(resultState, algorithmState, ambientState);
updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex());
getNotificationChildrenStates(resultState, algorithmState);
}
@@ -198,7 +198,7 @@ public class StackScrollAlgorithm {
}
private void updateClipping(StackScrollState resultState,
StackScrollAlgorithmState algorithmState) {
StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
float previousNotificationEnd = 0;
float previousNotificationStart = 0;
boolean previousNotificationIsSwiped = false;
@@ -239,7 +239,7 @@ public class StackScrollAlgorithm {
// otherwise we would clip to a transparent view.
previousNotificationStart = newYTranslation + state.clipTopAmount * state.scale;
previousNotificationEnd = newNotificationEnd;
previousNotificationIsSwiped = child.getTranslationX() != 0;
previousNotificationIsSwiped = ambientState.getDraggedViews().contains(child);
}
}
}
@@ -311,7 +311,9 @@ public class StackScrollAlgorithm {
StackViewState viewState = resultState.getViewStateForView(
nextChild);
// The child below the dragged one must be fully visible
viewState.alpha = 1;
if (!isPinnedHeadsUpView(draggedView) || isPinnedHeadsUpView(nextChild)) {
viewState.alpha = 1;
}
}
// Lets set the alpha to the one it currently has, as its currently being dragged
@@ -322,6 +324,14 @@ public class StackScrollAlgorithm {
}
}
private boolean isPinnedHeadsUpView(View view) {
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
return row.isHeadsUp() && !row.isInShade();
}
return false;
}
/**
* Update the visible children on the state.
*/
@@ -332,16 +342,15 @@ public class StackScrollAlgorithm {
state.visibleChildren.clear();
state.visibleChildren.ensureCapacity(childCount);
int notGoneIndex = 0;
TreeMap<String, HeadsUpManager.HeadsUpEntry> headsUpEntries =
ambientState.getHeadsUpEntries();
for (String key: headsUpEntries.keySet()) {
ExpandableView v = headsUpEntries.get(key).entry.row;
TreeSet<HeadsUpManager.HeadsUpEntry> headsUpEntries
= ambientState.getSortedHeadsUpEntries();
for (HeadsUpManager.HeadsUpEntry entry: headsUpEntries) {
ExpandableView v = entry.entry.row;
notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
}
for (int i = 0; i < childCount; i++) {
ExpandableView v = (ExpandableView) hostView.getChildAt(i);
if (v.getVisibility() != View.GONE) {
if (v instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) v;
if (row.isHeadsUp()) {
@@ -401,13 +410,17 @@ public class StackScrollAlgorithm {
// How far in is the element currently transitioning into the bottom stack.
float yPositionInScrollView = 0.0f;
// If we have a heads-up higher than the collapsed height we need to add the difference to
// the padding of all other elements, i.e push in the top stack slightly.
ExpandableNotificationRow topHeadsUpEntry = ambientState.getTopHeadsUpEntry();
int childCount = algorithmState.visibleChildren.size();
int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack;
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
StackViewState childViewState = resultState.getViewStateForView(child);
childViewState.location = StackViewState.LOCATION_UNKNOWN;
int childHeight = getMaxAllowedChildHeight(child);
int childHeight = getMaxAllowedChildHeight(child, ambientState);
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
+ mPaddingBetweenElements;
@@ -486,45 +499,31 @@ public class StackScrollAlgorithm {
currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
yPositionInScrollView = yPositionInScrollViewAfterElement;
if (ambientState.isShadeExpanded() && topHeadsUpEntry != null
&& child != topHeadsUpEntry) {
childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() - mCollapsedSize;
}
childViewState.yTranslation += ambientState.getTopPadding()
+ ambientState.getPaddingOffset();
updateHeadsUpStates(resultState, algorithmState, ambientState);
+ ambientState.getStackTranslation();
}
updateHeadsUpStates(resultState, algorithmState, ambientState);
}
private void updateHeadsUpStates(StackScrollState resultState,
StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
TreeMap<String, HeadsUpManager.HeadsUpEntry> headsUpEntries =
ambientState.getHeadsUpEntries();
boolean hasPinnedHeadsUp = false;
for (String key: headsUpEntries.keySet()) {
ExpandableNotificationRow row = headsUpEntries.get(key).entry.row;
TreeSet<HeadsUpManager.HeadsUpEntry> headsUpEntries = ambientState.getSortedHeadsUpEntries();
for (HeadsUpManager.HeadsUpEntry entry: headsUpEntries) {
ExpandableNotificationRow row = entry.entry.row;
StackViewState childState = resultState.getViewStateForView(row);
if (!row.isInShade()) {
childState.yTranslation = Math.max(childState.yTranslation, 0);
hasPinnedHeadsUp = true;
childState.yTranslation = 0;
}
childState.height = Math.max(childState.height, row.getHeadsUpHeight());
// Ensure that the heads up is always visible even when scrolled of from the bottom
childState.yTranslation = Math.min(childState.yTranslation,
ambientState.getMaxHeadsUpTranslation() - childState.height);
}
if (hasPinnedHeadsUp && !ambientState.isShadeExpanded()) {
// Let's hide all normal views
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
StackViewState state = resultState.getViewStateForView(child);
boolean hideView = true;
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
hideView = !row.isHeadsUp();
}
if (hideView) {
state.alpha = 0.0f;
}
}
}
}
/**
@@ -555,7 +554,7 @@ public class StackScrollAlgorithm {
/**
* Clamp the yTranslation of the child up such that its end is at lest on the end of the top
* stack.get
* stack.
*
* @param childViewState the view state of the child
* @param childHeight the height of this child
@@ -566,9 +565,12 @@ public class StackScrollAlgorithm {
mCollapsedSize - childHeight);
}
private int getMaxAllowedChildHeight(View child) {
private int getMaxAllowedChildHeight(View child, AmbientState ambientState) {
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
if (ambientState != null && ambientState.getTopHeadsUpEntry() == child) {
return mCollapsedSize;
}
return row.getIntrinsicHeight();
} else if (child instanceof ExpandableView) {
ExpandableView expandableView = (ExpandableView) child;
@@ -695,7 +697,7 @@ public class StackScrollAlgorithm {
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
StackViewState childViewState = resultState.getViewStateForView(child);
int childHeight = getMaxAllowedChildHeight(child);
int childHeight = getMaxAllowedChildHeight(child, ambientState);
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
+ mPaddingBetweenElements;
@@ -850,7 +852,7 @@ public class StackScrollAlgorithm {
int oldBottom) {
if (mFirstChildWhileExpanding != null) {
mFirstChildMaxHeight = getMaxAllowedChildHeight(
mFirstChildWhileExpanding);
mFirstChildWhileExpanding, null);
} else {
mFirstChildMaxHeight = 0;
}
@@ -858,7 +860,7 @@ public class StackScrollAlgorithm {
}
});
} else {
mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding);
mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding, null);
}
}

View File

@@ -21,9 +21,11 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.graphics.Path;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -32,7 +34,6 @@ import com.android.systemui.statusbar.SpeedBumpView;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
/**
@@ -46,7 +47,7 @@ public class StackStateAnimator {
public static final int ANIMATION_DURATION_EXPAND_CLICKED = 360;
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 280;
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 220;
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 230;
public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54;
public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
@@ -75,12 +76,15 @@ public class StackStateAnimator {
private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
private final Interpolator mFastOutSlowInInterpolator;
private final Interpolator mHeadsUpAppearInterpolator;
private final int mGoToFullShadeAppearingTranslation;
public NotificationStackScrollLayout mHostLayout;
private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
new ArrayList<>();
private ArrayList<View> mNewAddChildren = new ArrayList<>();
private Set<Animator> mAnimatorSet = new HashSet<>();
private HashSet<View> mHeadsUpAppearChildren = new HashSet<>();
private HashSet<View> mHeadsUpDisappearChildren = new HashSet<>();
private HashSet<Animator> mAnimatorSet = new HashSet<>();
private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>();
private AnimationFilter mAnimationFilter = new AnimationFilter();
private long mCurrentLength;
@@ -88,11 +92,12 @@ public class StackStateAnimator {
/** The current index for the last child which was not added in this event set. */
private int mCurrentLastNotAddedIndex;
private ValueAnimator mTopOverScrollAnimator;
private ValueAnimator mBottomOverScrollAnimator;
private ExpandableNotificationRow mChildExpandingView;
private StackViewState mTmpState = new StackViewState();
private int mHeadsUpAppearHeightBottom;
private boolean mShadeExpanded;
public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
mHostLayout = hostLayout;
@@ -101,6 +106,10 @@ public class StackStateAnimator {
mGoToFullShadeAppearingTranslation =
hostLayout.getContext().getResources().getDimensionPixelSize(
R.dimen.go_to_full_shade_appearing_translation);
Path path = new Path();
path.moveTo(0, 0);
path.cubicTo(0.8f, 0, 0.8f, 1.2f, 1f, 1f);
mHeadsUpAppearInterpolator = new PathInterpolator(path);
}
public boolean isRunning() {
@@ -122,7 +131,8 @@ public class StackStateAnimator {
final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
StackViewState viewState = finalState.getViewStateForView(child);
if (viewState == null || child.getVisibility() == View.GONE) {
if (viewState == null || child.getVisibility() == View.GONE
|| applyWithoutAnimation(child, viewState, finalState)) {
continue;
}
@@ -133,11 +143,35 @@ public class StackStateAnimator {
// no child has preformed any animation, lets finish
onAnimationFinished();
}
mHeadsUpAppearChildren.clear();
mHeadsUpDisappearChildren.clear();
mNewEvents.clear();
mNewAddChildren.clear();
mChildExpandingView = null;
}
/**
* Determines if a view should not perform an animation and applies it directly.
*
* @return true if no animation should be performed
*/
private boolean applyWithoutAnimation(ExpandableView child, StackViewState viewState,
StackScrollState finalState) {
if (mShadeExpanded) {
return false;
}
if (getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null) {
// A Y translation animation is running
return false;
}
if (mHeadsUpDisappearChildren.contains(child) || mHeadsUpAppearChildren.contains(child)) {
// This is a heads up animation
return false;
}
finalState.applyState(child, viewState);
return true;
}
private int findLastNotAddedIndex(StackScrollState finalState) {
int childCount = mHostLayout.getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
@@ -619,7 +653,9 @@ public class StackStateAnimator {
ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
child.getTranslationY(), newEndValue);
animator.setInterpolator(mFastOutSlowInInterpolator);
Interpolator interpolator = mHeadsUpAppearChildren.contains(child) ?
mHeadsUpAppearInterpolator :mFastOutSlowInInterpolator;
animator.setInterpolator(interpolator);
long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
animator.setDuration(newDuration);
if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
@@ -836,8 +872,17 @@ public class StackStateAnimator {
// This item is added, initialize it's properties.
StackViewState viewState = finalState.getViewStateForView(changingView);
mTmpState.copyFrom(viewState);
mTmpState.yTranslation = -mTmpState.height;
if (event.headsUpFromBottom) {
mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
} else {
mTmpState.yTranslation = -mTmpState.height;
}
mHeadsUpAppearChildren.add(changingView);
finalState.applyState(changingView, mTmpState);
} else if (event.animationType == NotificationStackScrollLayout
.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
// This item is added, initialize it's properties.
mHeadsUpDisappearChildren.add(changingView);
}
mNewEvents.add(event);
}
@@ -903,4 +948,12 @@ public class StackStateAnimator {
return getChildTag(view, TAG_END_HEIGHT);
}
}
public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
}
public void setShadeExpanded(boolean shadeExpanded) {
mShadeExpanded = shadeExpanded;
}
}