summaryrefslogtreecommitdiffstats
path: root/packages/SystemUI/src
diff options
context:
space:
mode:
authorSelim Cinek <cinek@google.com>2014-05-14 10:23:33 +0200
committerSelim Cinek <cinek@google.com>2014-05-16 23:19:28 +0200
commitc27437b7fd04e682ae2abdf0727a99bf5c6e409d (patch)
tree772dffd985ed65c7e35fc8c7108640bc404e66fd /packages/SystemUI/src
parent4455f54b5ba98da345c64de1614b0704022d272b (diff)
downloadframeworks_base-c27437b7fd04e682ae2abdf0727a99bf5c6e409d.zip
frameworks_base-c27437b7fd04e682ae2abdf0727a99bf5c6e409d.tar.gz
frameworks_base-c27437b7fd04e682ae2abdf0727a99bf5c6e409d.tar.bz2
Implemented visual speed-bump for notifications.
The separation between the important and the less important notifications has now a visual representation. Bug: 14607473 Change-Id: I8baa0a08924ec041be2884a2834139477313ab40
Diffstat (limited to 'packages/SystemUI/src')
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotView.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsAlgorithm.java80
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsLayout.java136
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsState.java139
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java265
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java44
13 files changed, 821 insertions, 10 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 6090948..c1228d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -877,6 +877,7 @@ public abstract class BaseStatusBar extends SystemUI implements
entry.row = row;
entry.row.setHeightRange(mRowMinHeight, mRowMaxHeight);
entry.row.setOnActivatedListener(this);
+ entry.row.setIsBelowSpeedBump(isBelowSpeedBump(entry.notification));
entry.expanded = contentViewLocal;
entry.expandedPublic = publicViewLocal;
entry.setBigContentView(bigContentViewLocal);
@@ -1039,8 +1040,8 @@ public abstract class BaseStatusBar extends SystemUI implements
if (DEBUG) {
Log.d(TAG, "addNotificationViews: added at " + pos);
}
- updateRowStates();
updateNotificationIcons();
+ updateRowStates();
}
private void addNotificationViews(IBinder key, StatusBarNotification notification) {
@@ -1060,6 +1061,7 @@ public abstract class BaseStatusBar extends SystemUI implements
mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
int n = mNotificationData.size();
int visibleNotifications = 0;
+ int speedBumpIndex = -1;
boolean onKeyguard = mState == StatusBarState.KEYGUARD;
for (int i = n-1; i >= 0; i--) {
NotificationData.Entry entry = mNotificationData.get(i);
@@ -1087,6 +1089,10 @@ public abstract class BaseStatusBar extends SystemUI implements
entry.row.setVisibility(View.VISIBLE);
visibleNotifications++;
}
+ if (entry.row.getVisibility() != View.GONE && speedBumpIndex == -1
+ && entry.row.isBelowSpeedBump() ) {
+ speedBumpIndex = n - 1 - i;
+ }
}
if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) {
@@ -1094,6 +1100,8 @@ public abstract class BaseStatusBar extends SystemUI implements
} else {
mKeyguardIconOverflowContainer.setVisibility(View.GONE);
}
+
+ mStackScroller.updateSpeedBumpIndex(speedBumpIndex);
}
private boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
@@ -1309,9 +1317,19 @@ public abstract class BaseStatusBar extends SystemUI implements
} else {
entry.row.setOnClickListener(null);
}
+ boolean wasBelow = entry.row.isBelowSpeedBump();
+ boolean nowBelow = isBelowSpeedBump(notification);
+ if (wasBelow != nowBelow) {
+ entry.row.setIsBelowSpeedBump(nowBelow);
+ }
entry.row.notifyContentUpdated();
}
+ private boolean isBelowSpeedBump(StatusBarNotification notification) {
+ return notification.getNotification().priority ==
+ Notification.PRIORITY_MIN;
+ }
+
protected void notifyHeadsUpScreenOn(boolean screenOn) {
if (!screenOn && mInterruptingNotificationEntry != null) {
mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 39f2bb9..f6c80fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -52,6 +52,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
private NotificationContentView mPublicLayout;
private NotificationContentView mPrivateLayout;
private int mMaxExpandHeight;
+ private boolean mIsBelowSpeedBump;
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -244,6 +245,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mPrivateLayout.setClipTopAmount(clipTopAmount);
}
+ public boolean isBelowSpeedBump() {
+ return mIsBelowSpeedBump;
+ }
+
+ public void setIsBelowSpeedBump(boolean isBelow) {
+ this.mIsBelowSpeedBump = isBelow;
+ }
+
public void notifyContentUpdated() {
mPrivateLayout.notifyContentUpdated();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 4bd0e1c..061396d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -40,11 +40,15 @@ public abstract class ExpandableView extends FrameLayout {
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (!mActualHeightInitialized && mActualHeight == 0) {
- mActualHeight = getHeight();
+ mActualHeight = getInitialHeight();
}
mActualHeightInitialized = true;
}
+ protected int getInitialHeight() {
+ return getHeight();
+ }
+
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (filterMotionEvent(ev)) {
@@ -146,6 +150,10 @@ public abstract class ExpandableView extends FrameLayout {
}
}
+ public boolean isTransparent() {
+ return false;
+ }
+
/**
* A listener notifying when {@link #getActualHeight} changes.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotView.java
new file mode 100644
index 0000000..3ca021a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotView.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * An single dot of the {@link com.android.systemui.statusbar.SpeedBumpDotsLayout}
+ */
+public class SpeedBumpDotView extends View {
+
+ private final Paint mPaint = new Paint();
+
+ public SpeedBumpDotView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mPaint.setAntiAlias(true);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ float radius = getWidth() / 2.0f;
+ canvas.drawCircle(radius, radius, radius, mPaint);
+ }
+
+ public void setColor(int color) {
+ mPaint.setColor(color);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsAlgorithm.java
new file mode 100644
index 0000000..cac6327
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsAlgorithm.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.view.View;
+import com.android.systemui.R;
+
+/**
+ * The Algorithm of the {@link com.android.systemui.statusbar.SpeedBumpDotsLayout} which can be
+ * queried for {@link * com.android.systemui.statusbar.SpeedBumpDotsState}
+ */
+public class SpeedBumpDotsAlgorithm {
+
+ private final float mDotRadius;
+
+ public SpeedBumpDotsAlgorithm(Context context) {
+ mDotRadius = context.getResources().getDimensionPixelSize(R.dimen.speed_bump_dots_height)
+ / 2.0f;
+ }
+
+ public void getState(SpeedBumpDotsState resultState) {
+
+ // First reset the current state and ensure that every View has a ViewState
+ resultState.resetViewStates();
+
+ SpeedBumpDotsLayout hostView = resultState.getHostView();
+ boolean currentlyVisible = hostView.isCurrentlyVisible();
+ resultState.setActiveState(currentlyVisible
+ ? SpeedBumpDotsState.SHOWN
+ : SpeedBumpDotsState.HIDDEN);
+ int hostWidth = hostView.getWidth();
+ float layoutWidth = hostWidth - 2 * mDotRadius;
+ int childCount = hostView.getChildCount();
+ float paddingBetween = layoutWidth / (childCount - 1);
+ float centerY = hostView.getHeight() / 2.0f;
+ for (int i = 0; i < childCount; i++) {
+ View child = hostView.getChildAt(i);
+ SpeedBumpDotsState.ViewState viewState = resultState.getViewStateForView(child);
+ if (currentlyVisible) {
+ float xTranslation = i * paddingBetween;
+ viewState.xTranslation = xTranslation;
+ viewState.yTranslation = calculateYTranslation(hostView, centerY, xTranslation,
+ layoutWidth);
+ } else {
+ viewState.xTranslation = layoutWidth / 2;
+ viewState.yTranslation = centerY - mDotRadius;
+ }
+ viewState.alpha = currentlyVisible ? 1.0f : 0.0f;
+ viewState.scale = currentlyVisible ? 1.0f : 0.5f;
+ }
+ }
+
+ private float calculateYTranslation(SpeedBumpDotsLayout hostView, float centerY,
+ float xTranslation, float layoutWidth) {
+ float t = hostView.getAnimationProgress();
+ if (t == 0.0f || t == 1.0f) {
+ return centerY - mDotRadius;
+ }
+ float damping = (0.5f -Math.abs(0.5f - t)) * 1.3f;
+ float partialOffset = xTranslation / layoutWidth;
+ float indentFactor = (float) (Math.sin((t + partialOffset * 1.5f) * - Math.PI) * damping);
+ return (1.0f - indentFactor) * centerY - mDotRadius;
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsLayout.java
new file mode 100644
index 0000000..ddf5215
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsLayout.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.animation.TimeAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import com.android.systemui.R;
+
+/**
+ * A layout with a certain number of dots which are integrated in the
+ * {@link com.android.systemui.statusbar.SpeedBumpView}
+ */
+public class SpeedBumpDotsLayout extends ViewGroup {
+
+ private static final float DOT_CLICK_ANIMATION_LENGTH = 300;
+ private final int mDotSize;
+ private final SpeedBumpDotsAlgorithm mAlgorithm = new SpeedBumpDotsAlgorithm(getContext());
+ private final SpeedBumpDotsState mCurrentState = new SpeedBumpDotsState(this);
+ private boolean mIsCurrentlyVisible = true;
+ private final ValueAnimator mClickAnimator;
+ private float mAnimationProgress;
+ private ValueAnimator.AnimatorUpdateListener mClickUpdateListener
+ = new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mAnimationProgress = animation.getAnimatedFraction();
+ updateChildren();
+ }
+ };
+
+ public SpeedBumpDotsLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mDotSize = getResources().getDimensionPixelSize(R.dimen.speed_bump_dots_height);
+ createDots(context, attrs);
+ mClickAnimator = TimeAnimator.ofFloat(0, DOT_CLICK_ANIMATION_LENGTH);
+ mClickAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+ mClickAnimator.addUpdateListener(mClickUpdateListener);
+ }
+
+ private void createDots(Context context, AttributeSet attrs) {
+ SpeedBumpDotView blueDot = new SpeedBumpDotView(context, attrs);
+ blueDot.setColor(getResources().getColor(R.color.speed_bump_dot_blue));
+ addView(blueDot);
+
+ SpeedBumpDotView redDot = new SpeedBumpDotView(context, attrs);
+ redDot.setColor(getResources().getColor(R.color.speed_bump_dot_red));
+ addView(redDot);
+
+ SpeedBumpDotView yellowDot = new SpeedBumpDotView(context, attrs);
+ yellowDot.setColor(getResources().getColor(R.color.speed_bump_dot_yellow));
+ addView(yellowDot);
+
+ SpeedBumpDotView greenDot = new SpeedBumpDotView(context, attrs);
+ greenDot.setColor(getResources().getColor(R.color.speed_bump_dot_green));
+ addView(greenDot);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(mDotSize,
+ MeasureSpec.getMode(widthMeasureSpec));
+ int childHeightSpec = MeasureSpec.makeMeasureSpec(mDotSize,
+ MeasureSpec.getMode(heightMeasureSpec));
+ measureChildren(childWidthSpec, childHeightSpec);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ child.layout(0, 0, mDotSize, mDotSize);
+ }
+ if (changed) {
+ updateChildren();
+ }
+ }
+
+ private void updateChildren() {
+ mAlgorithm.getState(mCurrentState);
+ mCurrentState.apply();
+ }
+
+ public void performVisibilityAnimation(boolean visible) {
+ if (mClickAnimator.isRunning()) {
+ mClickAnimator.cancel();
+ }
+ mIsCurrentlyVisible = visible;
+ mAlgorithm.getState(mCurrentState);
+ mCurrentState.animateToState();
+ }
+
+ public void setInvisible() {
+ mIsCurrentlyVisible = false;
+ mAlgorithm.getState(mCurrentState);
+ mCurrentState.apply();
+ }
+
+ public boolean isCurrentlyVisible() {
+ return mIsCurrentlyVisible;
+ }
+
+ public void performDotClickAnimation() {
+ if (mClickAnimator.isRunning()) {
+ // don't perform an animation if it's running already
+ return;
+ }
+ mClickAnimator.start();
+ }
+
+
+ public float getAnimationProgress() {
+ return mAnimationProgress;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsState.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsState.java
new file mode 100644
index 0000000..06a7f95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpDotsState.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A state of a {@link com.android.systemui.statusbar.SpeedBumpDotsLayout}
+ */
+public class SpeedBumpDotsState {
+
+ public static final int HIDDEN = 1;
+ public static final int SHOWN = 2;
+ private static final int VISIBILITY_ANIMATION_DELAY_PER_ELEMENT = 80;
+
+ private final SpeedBumpDotsLayout mHostView;
+ private final HashMap<View, ViewState> mStateMap = new HashMap<View, ViewState>();
+ private final Interpolator mFastOutSlowInInterpolator;
+ private int mActiveState = 0;
+
+ public SpeedBumpDotsState(SpeedBumpDotsLayout hostLayout) {
+ mHostView = hostLayout;
+ mFastOutSlowInInterpolator = AnimationUtils
+ .loadInterpolator(hostLayout.getContext(),
+ android.R.interpolator.fast_out_slow_in);
+ }
+
+ public SpeedBumpDotsLayout getHostView() {
+ return mHostView;
+ }
+
+ public void resetViewStates() {
+ int numChildren = mHostView.getChildCount();
+ for (int i = 0; i < numChildren; i++) {
+ View child = mHostView.getChildAt(i);
+ ViewState viewState = mStateMap.get(child);
+ if (viewState == null) {
+ viewState = new ViewState();
+ mStateMap.put(child, viewState);
+ }
+ }
+ }
+
+ public ViewState getViewStateForView(View requestedView) {
+ return mStateMap.get(requestedView);
+ }
+
+ public void apply() {
+ int childCount = mHostView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mHostView.getChildAt(i);
+ ViewState viewState = mStateMap.get(child);
+ float translationX = child.getTranslationX();
+ float translationY = child.getTranslationY();
+ float scale = child.getScaleX();
+ float alpha = child.getAlpha();
+ if (translationX != viewState.xTranslation) {
+ child.setTranslationX(viewState.xTranslation);
+ }
+ if (translationY != viewState.yTranslation) {
+ child.setTranslationY(viewState.yTranslation);
+ }
+ if (scale != viewState.scale) {
+ child.setScaleX(viewState.scale);
+ child.setScaleY(viewState.scale);
+ }
+ if (alpha != viewState.alpha) {
+ child.setAlpha(viewState.alpha);
+ }
+ }
+ }
+
+ public void animateToState() {
+ int childCount = mHostView.getChildCount();
+ int middleIndex = (childCount - 1) / 2;
+ long delayPerElement = VISIBILITY_ANIMATION_DELAY_PER_ELEMENT;
+ boolean isAppearing = getActiveState() == SHOWN;
+ boolean isDisappearing = getActiveState() == HIDDEN;
+ for (int i = 0; i < childCount; i++) {
+ int delayIndex;
+ if (i <= middleIndex) {
+ delayIndex = i * 2;
+ } else {
+ int distToMiddle = i - middleIndex;
+ delayIndex = (childCount - 1) - (distToMiddle - 1) * 2;
+ }
+ long startDelay = 0;
+ if (isAppearing || isDisappearing) {
+ if (isDisappearing) {
+ delayIndex = childCount - 1 - delayIndex;
+ }
+ startDelay = delayIndex * delayPerElement;
+ }
+ View child = mHostView.getChildAt(i);
+ ViewState viewState = mStateMap.get(child);
+ child.animate().setInterpolator(mFastOutSlowInInterpolator)
+ .setStartDelay(startDelay)
+ .alpha(viewState.alpha).withLayer()
+ .translationX(viewState.xTranslation)
+ .translationY(viewState.yTranslation)
+ .scaleX(viewState.scale).scaleY(viewState.scale);
+ }
+ }
+
+ public int getActiveState() {
+ return mActiveState;
+ }
+
+ public void setActiveState(int mActiveState) {
+ this.mActiveState = mActiveState;
+ }
+
+ public static class ViewState {
+ float xTranslation;
+ float yTranslation;
+ float alpha;
+ float scale;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
new file mode 100644
index 0000000..8ae503a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Outline;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.TextView;
+import com.android.systemui.R;
+
+/**
+ * The view representing the separation between important and less important notifications
+ */
+public class SpeedBumpView extends ExpandableView implements View.OnClickListener {
+
+ private final int mCollapsedHeight;
+ private final int mDotsHeight;
+ private final int mTextPaddingInset;
+ private SpeedBumpDotsLayout mDots;
+ private View mLineLeft;
+ private View mLineRight;
+ private boolean mIsExpanded;
+ private boolean mDividerVisible = true;
+ private ValueAnimator mCurrentAnimator;
+ private final Interpolator mFastOutSlowInInterpolator;
+ private float mCenterX;
+ private TextView mExplanationText;
+ private boolean mExplanationTextVisible = false;
+ private AnimatorListenerAdapter mHideExplanationListener = new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mCancelled) {
+ mExplanationText.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mCancelled = false;
+ }
+ };
+ private Animator.AnimatorListener mAnimationFinishedListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentAnimator = null;
+ }
+ };
+
+ public SpeedBumpView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mCollapsedHeight = getResources()
+ .getDimensionPixelSize(R.dimen.speed_bump_height_collapsed);
+ mTextPaddingInset = getResources().getDimensionPixelSize(
+ R.dimen.speed_bump_text_padding_inset);
+ mDotsHeight = getResources().getDimensionPixelSize(R.dimen.speed_bump_dots_height);
+ setOnClickListener(this);
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
+ android.R.interpolator.fast_out_slow_in);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mDots = (SpeedBumpDotsLayout) findViewById(R.id.speed_bump_dots_layout);
+ mLineLeft = findViewById(R.id.speedbump_line_left);
+ mLineRight = findViewById(R.id.speedbump_line_right);
+ mExplanationText = (TextView) findViewById(R.id.speed_bump_text);
+ resetExplanationText();
+
+ }
+
+ @Override
+ protected int getInitialHeight() {
+ return mCollapsedHeight;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return getActualHeight();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ Outline outline = new Outline();
+ mCenterX = getWidth() / 2;
+ float centerY = getHeight() / 2;
+ // TODO: hide outline better
+ // Temporary workaround to hide outline on a transparent view
+ int outlineLeft = (int) (mCenterX - getResources().getDisplayMetrics().densityDpi * 8);
+ int outlineTop = (int) (centerY - mDotsHeight / 2);
+ outline.setOval(outlineLeft, outlineTop, outlineLeft + mDotsHeight,
+ outlineTop + mDotsHeight);
+ setOutline(outline);
+ mLineLeft.setPivotX(mLineLeft.getWidth());
+ mLineLeft.setPivotY(mLineLeft.getHeight() / 2);
+ mLineRight.setPivotX(0);
+ mLineRight.setPivotY(mLineRight.getHeight() / 2);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+ int height = mCollapsedHeight + mExplanationText.getMeasuredHeight() - mTextPaddingInset;
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mCurrentAnimator != null) {
+ return;
+ }
+ int startValue = mIsExpanded ? getMaxHeight() : mCollapsedHeight;
+ int endValue = mIsExpanded ? mCollapsedHeight : getMaxHeight();
+ mCurrentAnimator = ValueAnimator.ofInt(startValue, endValue);
+ mCurrentAnimator.setInterpolator(mFastOutSlowInInterpolator);
+ mCurrentAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ setActualHeight((int) animation.getAnimatedValue());
+ }
+ });
+ mCurrentAnimator.addListener(mAnimationFinishedListener);
+ mCurrentAnimator.start();
+ mIsExpanded = !mIsExpanded;
+ mDots.performDotClickAnimation();
+ animateExplanationTextInternal(mIsExpanded);
+ }
+
+ private void animateExplanationTextInternal(boolean visible) {
+ if (mExplanationTextVisible != visible) {
+ float translationY = 0.0f;
+ float scale = 0.5f;
+ float alpha = 0.0f;
+ boolean needsHideListener = true;
+ if (visible) {
+ mExplanationText.setVisibility(VISIBLE);
+ translationY = mDots.getBottom() - mTextPaddingInset;
+ scale = 1.0f;
+ alpha = 1.0f;
+ needsHideListener = false;
+ }
+ mExplanationText.animate().setInterpolator(mFastOutSlowInInterpolator)
+ .alpha(alpha)
+ .scaleX(scale)
+ .scaleY(scale)
+ .translationY(translationY)
+ .setListener(needsHideListener ? mHideExplanationListener : null)
+ .withLayer();
+ mExplanationTextVisible = visible;
+ }
+ }
+
+ @Override
+ public boolean isTransparent() {
+ return true;
+ }
+
+ public void performVisibilityAnimation(boolean nowVisible) {
+ animateDivider(nowVisible);
+
+ // Animate explanation Text
+ if (mIsExpanded) {
+ animateExplanationTextInternal(nowVisible);
+ }
+ }
+
+ public void animateDivider(boolean nowVisible) {
+ if (nowVisible != mDividerVisible) {
+ // Animate dividers
+ float endValue = nowVisible ? 1.0f : 0.0f;
+ float endTranslationXLeft = nowVisible ? 0.0f : mCenterX - mLineLeft.getRight();
+ float endTranslationXRight = nowVisible ? 0.0f : mCenterX - mLineRight.getLeft();
+ mLineLeft.animate()
+ .alpha(endValue)
+ .withLayer()
+ .scaleX(endValue)
+ .scaleY(endValue)
+ .translationX(endTranslationXLeft)
+ .setInterpolator(mFastOutSlowInInterpolator);
+ mLineRight.animate()
+ .alpha(endValue)
+ .withLayer()
+ .scaleX(endValue)
+ .scaleY(endValue)
+ .translationX(endTranslationXRight)
+ .setInterpolator(mFastOutSlowInInterpolator);
+
+ // Animate dots
+ mDots.performVisibilityAnimation(nowVisible);
+ mDividerVisible = nowVisible;
+ }
+ }
+
+ public void setInvisible() {
+ float endTranslationXLeft = mCenterX - mLineLeft.getRight();
+ float endTranslationXRight = mCenterX - mLineRight.getLeft();
+ mLineLeft.setAlpha(0.0f);
+ mLineLeft.setScaleX(0.0f);
+ mLineLeft.setScaleY(0.0f);
+ mLineLeft.setTranslationX(endTranslationXLeft);
+ mLineRight.setAlpha(0.0f);
+ mLineRight.setScaleX(0.0f);
+ mLineRight.setScaleY(0.0f);
+ mLineRight.setTranslationX(endTranslationXRight);
+ mDots.setInvisible();
+ resetExplanationText();
+
+ mDividerVisible = false;
+ }
+
+ public void collapse() {
+ if (mIsExpanded) {
+ setActualHeight(mCollapsedHeight);
+ mIsExpanded = false;
+ }
+ resetExplanationText();
+ }
+
+ public void animateExplanationText(boolean nowVisible) {
+ if (mIsExpanded) {
+ animateExplanationTextInternal(nowVisible);
+ }
+ }
+
+ private void resetExplanationText() {
+ mExplanationText.setTranslationY(0);
+ mExplanationText.setVisibility(INVISIBLE);
+ mExplanationText.setAlpha(0.0f);
+ mExplanationText.setScaleX(0.5f);
+ mExplanationText.setScaleY(0.5f);
+ mExplanationTextVisible = false;
+ }
+
+ public boolean isExpanded() {
+ return mIsExpanded;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index b9f5ab2..cd985f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -143,8 +143,9 @@ public class NotificationPanelView extends PanelView implements
}
}
- public void animateNextTopPaddingChange() {
+ public void animateToFullShade() {
mAnimateNextTopPaddingChange = true;
+ mNotificationStackScroller.goToFullShade();
requestLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index d3b5f96..4e1ffa5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -106,6 +106,7 @@ import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.NotificationOverflowContainer;
import com.android.systemui.statusbar.SignalClusterView;
+import com.android.systemui.statusbar.SpeedBumpView;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -634,6 +635,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mKeyguardIconOverflowContainer.setOnClickListener(mOverflowClickListener);
mStackScroller.addView(mKeyguardIconOverflowContainer);
+ SpeedBumpView speedBump = (SpeedBumpView) LayoutInflater.from(mContext).inflate(
+ R.layout.status_bar_notification_speed_bump, mStackScroller, false);
+ mStackScroller.setSpeedBumpView(speedBump);
+
mExpandedContents = mStackScroller;
mHeader = (StatusBarHeaderView) mStatusBarWindow.findViewById(R.id.header);
@@ -1150,7 +1155,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
ArrayList<View> toRemove = new ArrayList<View>();
for (int i=0; i< mStackScroller.getChildCount(); i++) {
View child = mStackScroller.getChildAt(i);
- if (!toShow.contains(child) && child != mKeyguardIconOverflowContainer) {
+ if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
toRemove.add(child);
}
}
@@ -2722,7 +2727,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
setBarState(StatusBarState.SHADE);
if (mLeaveOpenOnKeyguardHide) {
mLeaveOpenOnKeyguardHide = false;
- mNotificationPanel.animateNextTopPaddingChange();
+ mNotificationPanel.animateToFullShade();
} else {
instantCollapseNotificationPanel();
}
@@ -2894,7 +2899,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mLeaveOpenOnKeyguardHide = true;
showBouncer();
} else {
- mNotificationPanel.animateNextTopPaddingChange();
+ mNotificationPanel.animateToFullShade();
setBarState(StatusBarState.SHADE_LOCKED);
updateKeyguardState();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index deab757..b21e12c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -30,6 +30,7 @@ public class AmbientState {
private View mActivatedChild;
private float mOverScrollTopAmount;
private float mOverScrollBottomAmount;
+ private int mSpeedBumpIndex = -1;
public int getScrollY() {
return mScrollY;
@@ -86,4 +87,12 @@ public class AmbientState {
public float getOverScrollAmount(boolean top) {
return top ? mOverScrollTopAmount : mOverScrollBottomAmount;
}
+
+ public int getSpeedBumpIndex() {
+ return mSpeedBumpIndex;
+ }
+
+ public void setSpeedBumpIndex(int speedBumpIndex) {
+ mSpeedBumpIndex = speedBumpIndex;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index fbb6695..f125c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -39,6 +39,7 @@ import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.SpeedBumpView;
import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
import com.android.systemui.statusbar.policy.ScrollAdapter;
@@ -126,6 +127,8 @@ public class NotificationStackScrollLayout extends ViewGroup
private boolean mActivateNeedsAnimation;
private boolean mIsExpanded = true;
private boolean mChildrenUpdateRequested;
+ private SpeedBumpView mSpeedBumpView;
+ private boolean mIsExpansionChanging;
private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
= new ViewTreeObserver.OnPreDrawListener() {
@Override
@@ -244,6 +247,22 @@ public class NotificationStackScrollLayout extends ViewGroup
requestChildrenUpdate();
}
+ public void updateSpeedBumpIndex(int newIndex) {
+ int currentIndex = indexOfChild(mSpeedBumpView);
+
+ // If we are currently layouted before the new speed bump index, we have to decrease it.
+ boolean validIndex = newIndex > 0;
+ if (newIndex > getChildCount() - 1) {
+ validIndex = false;
+ newIndex = -1;
+ }
+ if (validIndex && currentIndex != newIndex) {
+ changeViewPosition(mSpeedBumpView, newIndex);
+ }
+ updateSpeedBump(validIndex);
+ mAmbientState.setSpeedBumpIndex(newIndex);
+ }
+
public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
mListener = listener;
}
@@ -1044,6 +1063,10 @@ public class NotificationStackScrollLayout extends ViewGroup
mCurrentStackScrollState.removeViewStateForView(child);
mStackScrollAlgorithm.notifyChildrenChanged(this);
updateScrollStateForRemovedChild(child);
+ generateRemoveAnimation(child);
+ }
+
+ private void generateRemoveAnimation(View child) {
if (mIsExpanded) {
if (!mChildrenToAddAnimated.contains(child)) {
@@ -1120,7 +1143,9 @@ public class NotificationStackScrollLayout extends ViewGroup
*/
public void changeViewPosition(View child, int newIndex) {
if (child != null && child.getParent() == this) {
- // TODO: handle this
+ removeView(child);
+ addView(child, newIndex);
+ // TODO: handle events
}
}
@@ -1362,10 +1387,12 @@ public class NotificationStackScrollLayout extends ViewGroup
}
public void onExpansionStarted() {
+ mIsExpansionChanging = true;
mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
}
public void onExpansionStopped() {
+ mIsExpansionChanging = false;
mStackScrollAlgorithm.onExpansionStopped();
}
@@ -1374,6 +1401,7 @@ public class NotificationStackScrollLayout extends ViewGroup
mStackScrollAlgorithm.setIsExpanded(isExpanded);
if (!isExpanded) {
mOwnScrollY = 0;
+ mSpeedBumpView.collapse();
}
}
@@ -1432,6 +1460,34 @@ public class NotificationStackScrollLayout extends ViewGroup
}
}
+ public void setSpeedBumpView(SpeedBumpView speedBumpView) {
+ mSpeedBumpView = speedBumpView;
+ addView(speedBumpView);
+ }
+
+ private void updateSpeedBump(boolean visible) {
+ int newVisibility = visible ? VISIBLE : GONE;
+ int oldVisibility = mSpeedBumpView.getVisibility();
+ if (newVisibility != oldVisibility) {
+ mSpeedBumpView.setVisibility(newVisibility);
+ if (visible) {
+ mSpeedBumpView.collapse();
+ // Make invisible to ensure that the appear animation is played.
+ mSpeedBumpView.setInvisible();
+ if (!mIsExpansionChanging) {
+ generateAddAnimation(mSpeedBumpView);
+ }
+ } else {
+ mSpeedBumpView.performVisibilityAnimation(false);
+ generateRemoveAnimation(mSpeedBumpView);
+ }
+ }
+ }
+
+ public void goToFullShade() {
+ updateSpeedBump(true);
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 8fc26d8..011411c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -23,6 +23,7 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.SpeedBumpView;
import java.util.HashMap;
import java.util.Map;
@@ -167,11 +168,48 @@ public class StackScrollState {
clipHeight,
(int) (newHeight - (previousNotificationStart - newYTranslation)));
- previousNotificationStart = newYTranslation + child.getClipTopAmount();
- previousNotificationEnd = newNotificationEnd;
- previousNotificationIsSwiped = child.getTranslationX() != 0;
+ if (!child.isTransparent()) {
+ // Only update the previous values if we are not transparent,
+ // otherwise we would clip to a transparent view.
+ previousNotificationStart = newYTranslation + child.getClipTopAmount();
+ previousNotificationEnd = newNotificationEnd;
+ previousNotificationIsSwiped = child.getTranslationX() != 0;
+ }
+
+ if(child instanceof SpeedBumpView) {
+ performSpeedBumpAnimation(i, (SpeedBumpView) child, newNotificationEnd,
+ newYTranslation);
+ }
+ }
+ }
+ }
+
+ private void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, float speedBumpEnd,
+ float speedBumpStart) {
+ View nextChild = getNextChildNotGone(i);
+ if (nextChild != null) {
+ ViewState nextState = getViewStateForView(nextChild);
+ boolean startIsAboveNext = nextState.yTranslation > speedBumpStart;
+ speedBump.animateDivider(startIsAboveNext);
+
+ // handle expanded case
+ if (speedBump.isExpanded()) {
+ boolean endIsAboveNext = nextState.yTranslation > speedBumpEnd;
+ speedBump.animateExplanationText(endIsAboveNext);
+ }
+
+ }
+ }
+
+ private View getNextChildNotGone(int childIndex) {
+ int childCount = mHostView.getChildCount();
+ for (int i = childIndex + 1; i < childCount; i++) {
+ View child = mHostView.getChildAt(i);
+ if (child.getVisibility() != View.GONE) {
+ return child;
}
}
+ return null;
}
/**