diff --git a/packages/SystemUI/res/layout/notification_settings_icon_row.xml b/packages/SystemUI/res/layout/notification_settings_icon_row.xml new file mode 100644 index 0000000000000..74f6f9d8b4bf0 --- /dev/null +++ b/packages/SystemUI/res/layout/notification_settings_icon_row.xml @@ -0,0 +1,38 @@ + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml index 62fdd42648414..045ede3995d89 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_row.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml @@ -23,6 +23,14 @@ android:clickable="true" > + + @color/system_accent_color + + #ff757575 + #eeeeee @*android:color/material_deep_teal_500 diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index b8044ba53db8c..8c93e2a8722e5 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -78,6 +78,15 @@ 48dp + + 24dp + + + 30dp + + + 20dp + 17dip diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 33b43fec6fc1f..33f3c30fcc12d 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -69,9 +69,9 @@ public class SwipeHelper implements Gefingerpoken { private float mPerpendicularInitialTouchPos; private boolean mDragging; private View mCurrView; - private View mCurrAnimView; private boolean mCanCurrViewBeDimissed; private float mDensityScale; + private float mTranslation = 0; private boolean mLongPressSent; private LongPressListener mLongPressListener; @@ -121,7 +121,7 @@ public class SwipeHelper implements Gefingerpoken { return mSwipeDirection == X ? ev.getY() : ev.getX(); } - private float getTranslation(View v) { + protected float getTranslation(View v) { return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY(); } @@ -130,7 +130,7 @@ public class SwipeHelper implements Gefingerpoken { vt.getYVelocity(); } - private ObjectAnimator createTranslationAnimation(View v, float newPos) { + protected ObjectAnimator createTranslationAnimation(View v, float newPos) { ObjectAnimator anim = ObjectAnimator.ofFloat(v, mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos); return anim; @@ -141,7 +141,17 @@ public class SwipeHelper implements Gefingerpoken { vt.getXVelocity(); } - private void setTranslation(View v, float translate) { + protected Animator getViewTranslationAnimator(View v, float target, + AnimatorUpdateListener listener) { + ObjectAnimator anim = createTranslationAnimation(v, target); + anim.addUpdateListener(listener); + return anim; + } + + protected void setTranslation(View v, float translate) { + if (v == null) { + return; + } if (mSwipeDirection == X) { v.setTranslationX(translate); } else { @@ -237,15 +247,16 @@ public class SwipeHelper implements Gefingerpoken { mTouchAboveFalsingThreshold = false; mDragging = false; mLongPressSent = false; - mCurrView = mCallback.getChildAtPosition(ev); mVelocityTracker.clear(); + mCurrView = mCallback.getChildAtPosition(ev); + if (mCurrView != null) { - mCurrAnimView = mCallback.getChildContentView(mCurrView); + onDownUpdate(mCurrView); mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView); mVelocityTracker.addMovement(ev); mInitialTouchPos = getPos(ev); mPerpendicularInitialTouchPos = getPerpendicularPos(ev); - + mTranslation = getTranslation(mCurrView); if (mLongPressListener != null) { if (mWatchLongPress == null) { mWatchLongPress = new Runnable() { @@ -268,7 +279,6 @@ public class SwipeHelper implements Gefingerpoken { } mHandler.postDelayed(mWatchLongPress, mLongPressTimeout); } - } break; @@ -283,8 +293,8 @@ public class SwipeHelper implements Gefingerpoken { && Math.abs(delta) > Math.abs(deltaPerpendicular)) { mCallback.onBeginDrag(mCurrView); mDragging = true; - mInitialTouchPos = getPos(ev) - getTranslation(mCurrAnimView); - + mInitialTouchPos = getPos(ev); + mTranslation = getTranslation(mCurrView); removeLongPressCallback(); } } @@ -295,7 +305,6 @@ public class SwipeHelper implements Gefingerpoken { final boolean captured = (mDragging || mLongPressSent); mDragging = false; mCurrView = null; - mCurrAnimView = null; mLongPressSent = false; removeLongPressCallback(); if (captured) return true; @@ -320,12 +329,11 @@ public class SwipeHelper implements Gefingerpoken { * @param useAccelerateInterpolator Should an accelerating Interpolator be used * @param fixedDuration If not 0, this exact duration will be taken */ - public void dismissChild(final View view, float velocity, final Runnable endAction, + public void dismissChild(final View animView, float velocity, final Runnable endAction, long delay, boolean useAccelerateInterpolator, long fixedDuration) { - final View animView = mCallback.getChildContentView(view); - final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view); + final boolean canBeDismissed = mCallback.canChildBeDismissed(animView); float newPos; - boolean isLayoutRtl = view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + boolean isLayoutRtl = animView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; if (velocity < 0 || (velocity == 0 && getTranslation(animView) < 0) @@ -355,7 +363,13 @@ public class SwipeHelper implements Gefingerpoken { if (!mDisableHwLayers) { animView.setLayerType(View.LAYER_TYPE_HARDWARE, null); } - ObjectAnimator anim = createTranslationAnimation(animView, newPos); + AnimatorUpdateListener updateListener = new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed); + } + }; + + Animator anim = getViewTranslationAnimator(animView, newPos, updateListener); if (useAccelerateInterpolator) { anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); } else { @@ -367,8 +381,8 @@ public class SwipeHelper implements Gefingerpoken { } anim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { - updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed); - mCallback.onChildDismissed(view); + updateSwipeProgressFromOffset(animView, canBeDismissed); + mCallback.onChildDismissed(animView); if (endAction != null) { endAction.run(); } @@ -377,11 +391,6 @@ public class SwipeHelper implements Gefingerpoken { } } }); - anim.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed); - } - }); prepareDismissAnimation(animView, anim); anim.start(); } @@ -393,21 +402,21 @@ public class SwipeHelper implements Gefingerpoken { // Do nothing } - public void snapChild(final View view, float velocity) { - final View animView = mCallback.getChildContentView(view); - final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(animView); - ObjectAnimator anim = createTranslationAnimation(animView, 0); + public void snapChild(final View animView, final float targetLeft, float velocity) { + final boolean canBeDismissed = mCallback.canChildBeDismissed(animView); + AnimatorUpdateListener updateListener = new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed); + } + }; + + Animator anim = getViewTranslationAnimator(animView, targetLeft, updateListener); int duration = SNAP_ANIM_LEN; anim.setDuration(duration); - anim.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed); - } - }); anim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animator) { - updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed); - mCallback.onChildSnappedBack(animView); + updateSwipeProgressFromOffset(animView, canBeDismissed); + mCallback.onChildSnappedBack(animView, targetLeft); } }); prepareSnapBackAnimation(animView, anim); @@ -421,6 +430,28 @@ public class SwipeHelper implements Gefingerpoken { // Do nothing } + /** + * Called when there's a down event. + */ + public void onDownUpdate(View currView) { + // Do nothing + } + + /** + * Called on a move event. + */ + protected void onMoveUpdate(View view, float totalTranslation, float delta) { + // Do nothing + } + + /** + * Called in {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} when the current + * view is being animated to dismiss or snap. + */ + public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) { + updateSwipeProgressFromOffset(animView, canBeDismissed); + } + public boolean onTouchEvent(MotionEvent ev) { if (mLongPressSent) { return true; @@ -456,17 +487,18 @@ public class SwipeHelper implements Gefingerpoken { // don't let items that can't be dismissed be dragged more than // maxScrollDistance if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) { - float size = getSize(mCurrAnimView); - float maxScrollDistance = 0.15f * size; + float size = getSize(mCurrView); + float maxScrollDistance = 0.25f * size; if (absDelta >= size) { delta = delta > 0 ? maxScrollDistance : -maxScrollDistance; } else { delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2)); } } - setTranslation(mCurrAnimView, delta); - updateSwipeProgressFromOffset(mCurrAnimView, mCanCurrViewBeDimissed); + setTranslation(mCurrView, mTranslation + delta); + updateSwipeProgressFromOffset(mCurrView, mCanCurrViewBeDimissed); + onMoveUpdate(mCurrView, mTranslation + delta, delta); } break; case MotionEvent.ACTION_UP: @@ -478,12 +510,13 @@ public class SwipeHelper implements Gefingerpoken { float velocity = getVelocity(mVelocityTracker); float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker); + float translation = getTranslation(mCurrView); // Decide whether to dismiss the current view boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH && - Math.abs(getTranslation(mCurrAnimView)) > 0.4 * getSize(mCurrAnimView); + Math.abs(translation) > 0.4 * getSize(mCurrView); boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) && (Math.abs(velocity) > Math.abs(perpendicularVelocity)) && - (velocity > 0) == (getTranslation(mCurrAnimView) > 0); + (velocity > 0) == (translation > 0); boolean falsingDetected = mCallback.isAntiFalsingNeeded(); if (mFalsingManager.isClassiferEnabled()) { @@ -502,7 +535,7 @@ public class SwipeHelper implements Gefingerpoken { } else { // snappity mCallback.onDragCancelled(mCurrView); - snapChild(mCurrView, velocity); + snapChild(mCurrView, 0 /* leftTarget */, velocity); } } break; @@ -518,8 +551,6 @@ public class SwipeHelper implements Gefingerpoken { public interface Callback { View getChildAtPosition(MotionEvent ev); - View getChildContentView(View v); - boolean canChildBeDismissed(View v); boolean isAntiFalsingNeeded(); @@ -530,7 +561,13 @@ public class SwipeHelper implements Gefingerpoken { void onDragCancelled(View v); - void onChildSnappedBack(View animView); + /** + * Called when the child is snapped to a position. + * + * @param animView the view that was snapped. + * @param targetLeft the left position the view was snapped to. + */ + void onChildSnappedBack(View animView, float targetLeft); /** * Updates the swipe progress on a child. diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index fe504fefef260..3f0630d70c943 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -494,7 +494,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { * onChildDismissed() calls. */ @Override - public void onChildSnappedBack(View v) { + public void onChildSnappedBack(View v, float targetLeft) { TaskView tv = (TaskView) v; // Re-enable clipping with the stack @@ -516,11 +516,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { // Do nothing } - @Override - public View getChildContentView(View v) { - return v; - } - @Override public boolean isAntiFalsingNeeded() { return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 2bebac2c51369..411fd084c6b27 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -103,6 +103,7 @@ import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.PreviewInflater; import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; +import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.GearDisplayedListener; import com.android.systemui.statusbar.stack.StackStateAnimator; import java.util.ArrayList; @@ -115,7 +116,7 @@ import static com.android.keyguard.KeyguardHostView.OnDismissAction; public abstract class BaseStatusBar extends SystemUI implements CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener, ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment, - ExpandableNotificationRow.OnExpandClickListener { + ExpandableNotificationRow.OnExpandClickListener, GearDisplayedListener { public static final String TAG = "StatusBar"; public static final boolean DEBUG = false; public static final boolean MULTIUSER_DEBUG = false; @@ -220,6 +221,7 @@ public abstract class BaseStatusBar extends SystemUI implements // which notification is currently being longpress-examined by the user private NotificationGuts mNotificationGutsExposed; + private ExpandableNotificationRow mNotificationGearDisplayed; private KeyboardShortcuts mKeyboardShortcuts; @@ -1008,6 +1010,10 @@ public abstract class BaseStatusBar extends SystemUI implements guts.bindImportance(sbn, row, mNotificationData.getImportance(sbn.getKey())); } + protected GearDisplayedListener getGearDisplayedListener() { + return this; + } + protected SwipeHelper.LongPressListener getNotificationLongClicker() { return new SwipeHelper.LongPressListener() { @Override @@ -1020,7 +1026,7 @@ public abstract class BaseStatusBar extends SystemUI implements return false; } - ExpandableNotificationRow row = (ExpandableNotificationRow) v; + final ExpandableNotificationRow row = (ExpandableNotificationRow) v; bindGuts(row); // Assume we are a status_bar_notification_row @@ -1052,6 +1058,14 @@ public abstract class BaseStatusBar extends SystemUI implements = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r); a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); + a.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + // Move the notification view back over the gear + row.resetTranslation(); + } + }); a.start(); guts.setExposed(true); mStackScroller.onHeightChanged(null, true /* needsAnimation */); @@ -1063,6 +1077,11 @@ public abstract class BaseStatusBar extends SystemUI implements }; } + @Override + public void onGearDisplayed(ExpandableNotificationRow row) { + mNotificationGearDisplayed = row; + } + public void dismissPopups() { dismissPopups(-1, -1); } @@ -1095,6 +1114,11 @@ public abstract class BaseStatusBar extends SystemUI implements v.setExposed(false); mStackScroller.onHeightChanged(null, true /* needsAnimation */); } + + if (mNotificationGearDisplayed != null) { + mNotificationGearDisplayed.resetTranslation(); + mNotificationGearDisplayed = null; + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 7422902e593e9..94511da949db5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -16,6 +16,12 @@ package com.android.systemui.statusbar; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; import android.app.Notification; import android.content.Context; import android.graphics.drawable.AnimatedVectorDrawable; @@ -45,6 +51,7 @@ import com.android.systemui.statusbar.stack.StackScrollState; import com.android.systemui.statusbar.stack.StackStateAnimator; import com.android.systemui.statusbar.stack.StackViewState; +import java.util.ArrayList; import java.util.List; public class ExpandableNotificationRow extends ActivatableNotificationView { @@ -87,6 +94,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { */ private boolean mOnKeyguard; + private AnimatorSet mTranslateAnim; + private ArrayList mTranslateableViews; private NotificationContentView mPublicLayout; private NotificationContentView mPrivateLayout; private int mMaxExpandHeight; @@ -96,6 +105,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private ExpansionLogger mLogger; private String mLoggingKey; private boolean mWasReset; + private NotificationSettingsIconRow mSettingsIconRow; private NotificationGuts mGuts; private NotificationData.Entry mEntry; private StatusBarNotification mStatusBarNotification; @@ -108,6 +118,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private boolean mChildrenExpanded; private boolean mIsSummaryWithChildren; private NotificationChildrenContainer mChildrenContainer; + private ViewStub mSettingsIconRowStub; private ViewStub mGutsStub; private boolean mIsSystemChildExpanded; private boolean mIsPinned; @@ -527,6 +538,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mGuts.setVisibility(oldGuts.getVisibility()); addView(mGuts, index); } + if (mSettingsIconRow != null) { + View oldSettings = mSettingsIconRow; + int settingsIndex = indexOfChild(oldSettings); + removeView(oldSettings); + mSettingsIconRow = (NotificationSettingsIconRow) LayoutInflater.from(mContext).inflate( + R.layout.notification_settings_icon_row, this, false); + mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this); + mSettingsIconRow.setVisibility(oldSettings.getVisibility()); + addView(mSettingsIconRow, settingsIndex); + + } mPrivateLayout.reInflateViews(); mPublicLayout.reInflateViews(); } @@ -573,6 +595,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mPublicLayout.reset(mIsHeadsUp); mPrivateLayout.reset(mIsHeadsUp); resetHeight(); + resetTranslation(); logExpansionEvent(false, wasExpanded); } @@ -596,6 +619,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mPrivateLayout.setExpandClickListener(mExpandClickListener); mPrivateLayout.setContainingNotification(this); mPublicLayout.setExpandClickListener(mExpandClickListener); + mSettingsIconRowStub = (ViewStub) findViewById(R.id.settings_icon_row_stub); + mSettingsIconRowStub.setOnInflateListener(new ViewStub.OnInflateListener() { + @Override + public void onInflate(ViewStub stub, View inflated) { + mSettingsIconRow = (NotificationSettingsIconRow) inflated; + mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this); + } + }); mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub); mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() { @Override @@ -603,6 +634,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mGuts = (NotificationGuts) inflated; mGuts.setClipTopAmount(getClipTopAmount()); mGuts.setActualHeight(getActualHeight()); + mTranslateableViews.add(mGuts); mGutsStub = null; } }); @@ -613,9 +645,89 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public void onInflate(ViewStub stub, View inflated) { mChildrenContainer = (NotificationChildrenContainer) inflated; mChildrenContainer.setNotificationParent(ExpandableNotificationRow.this); + mTranslateableViews.add(mChildrenContainer); } }); mVetoButton = findViewById(R.id.veto); + + // Add the views that we translate to reveal the gear + mTranslateableViews = new ArrayList(); + for (int i = 0; i < getChildCount(); i++) { + mTranslateableViews.add(getChildAt(i)); + } + // Remove views that don't translate + mTranslateableViews.remove(mVetoButton); + mTranslateableViews.remove(mSettingsIconRowStub); + mTranslateableViews.remove(mChildrenContainerStub); + mTranslateableViews.remove(mGutsStub); + } + + public void setTranslationForOutline(float translationX) { + setOutlineRect(false, translationX, getTop(), getRight() + translationX, getBottom()); + } + + public void resetTranslation() { + if (mTranslateableViews != null) { + for (int i = 0; i < mTranslateableViews.size(); i++) { + mTranslateableViews.get(i).setTranslationX(0); + } + setTranslationForOutline(0); + } + if (mSettingsIconRow != null) { + mSettingsIconRow.resetState(); + } + } + + public void animateTranslateNotification(final float leftTarget) { + if (mTranslateAnim != null) { + mTranslateAnim.cancel(); + } + AnimatorSet set = new AnimatorSet(); + if (mTranslateableViews != null) { + for (int i = 0; i < mTranslateableViews.size(); i++) { + final View animView = mTranslateableViews.get(i); + final ObjectAnimator translateAnim = ObjectAnimator.ofFloat( + animView, "translationX", leftTarget); + if (i == 0) { + translateAnim.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + setTranslationForOutline((float) animation.getAnimatedValue()); + } + }); + } + translateAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator anim) { + if (mSettingsIconRow != null && leftTarget == 0) { + mSettingsIconRow.resetState(); + } + mTranslateAnim = null; + } + }); + set.play(translateAnim); + } + } + mTranslateAnim = set; + set.start(); + } + + public float getSpaceForGear() { + if (mSettingsIconRow != null) { + return mSettingsIconRow.getSpaceForGear(); + } + return 0; + } + + public NotificationSettingsIconRow getSettingsRow() { + if (mSettingsIconRow == null) { + mSettingsIconRowStub.inflate(); + } + return mSettingsIconRow; + } + + public ArrayList getContentViews() { + return mTranslateableViews; } public void inflateGuts() { @@ -772,7 +884,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { if (mIsSummaryWithChildren) { mChildrenContainer.updateGroupOverflow(); } - notifyHeightChanged(false /* needsAnimation */); + notifyHeightChanged(false /* needsAnimation */); } } } @@ -1097,6 +1209,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(), mNotificationHeader); addView(mNotificationHeader, indexOfChild(mChildrenContainer) + 1); + mTranslateableViews.add(mNotificationHeader); } else { header.reapply(getContext(), mNotificationHeader); mNotificationHeaderWrapper.notifyContentUpdated(mEntry.notification); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java index 44c6a5d36c646..782a38cc4023d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java @@ -81,8 +81,13 @@ public abstract class ExpandableOutlineView extends ExpandableView { } protected void setOutlineRect(float left, float top, float right, float bottom) { + setOutlineRect(true, left, top, right, bottom); + } + + protected void setOutlineRect(boolean clipToOutline, float left, float top, float right, + float bottom) { mCustomOutline = true; - setClipToOutline(true); + setClipToOutline(clipToOutline); mOutlineRect.set((int) left, (int) top, (int) right, (int) bottom); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java new file mode 100644 index 0000000000000..8fcd455a39da4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2016 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.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.android.systemui.Interpolators; +import com.android.systemui.R; + +public class NotificationSettingsIconRow extends FrameLayout implements View.OnClickListener { + + public interface SettingsIconRowListener { + /** + * Called when the gear behind a notification is touched. + */ + public void onGearTouched(ExpandableNotificationRow row); + } + + private ExpandableNotificationRow mParent; + private AlphaOptimizedImageView mGearIcon; + private float mHorizSpaceForGear; + private SettingsIconRowListener mListener; + + private ValueAnimator mFadeAnimator; + private boolean mSettingsFadedIn = false; + private boolean mAnimating = false; + private boolean mOnLeft = true; + + public NotificationSettingsIconRow(Context context) { + this(context, null); + } + + public NotificationSettingsIconRow(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mGearIcon = (AlphaOptimizedImageView) findViewById(R.id.gear_icon); + mGearIcon.setOnClickListener(this); + + final float iconMargin = + ((ViewGroup.MarginLayoutParams) mGearIcon.getLayoutParams()).getMarginStart(); + final float iconWidth = + getResources().getDimensionPixelOffset(R.dimen.notification_gear_size); + mHorizSpaceForGear = (iconWidth + iconMargin * 2); + resetState(); + } + + public void setGearListener(SettingsIconRowListener listener) { + mListener = listener; + } + + public void setNotificationRowParent(ExpandableNotificationRow parent) { + mParent = parent; + } + + public ExpandableNotificationRow getNotificationParent() { + return mParent; + } + + public void resetState() { + setGearAlpha(0f); + mAnimating = false; + setIconLocation(true /* on left */); + } + + private void setGearAlpha(float alpha) { + if (alpha == 0) { + mSettingsFadedIn = false; // Can fade in again once it's gone. + mGearIcon.setVisibility(View.INVISIBLE); + } else { + if (alpha == 1) { + mSettingsFadedIn = true; + } + mGearIcon.setVisibility(View.VISIBLE); + } + mGearIcon.setAlpha(alpha); + } + + /** + * Returns the horizontal space in pixels required to display the gear behind a notification. + */ + public float getSpaceForGear() { + return mHorizSpaceForGear; + } + + /** + * Indicates whether the gear is visible at 1 alpha. Does not indicate + * if entire view is visible. + */ + public boolean isVisible() { + return mSettingsFadedIn; + } + + public void cancelFadeAnimator() { + if (mFadeAnimator != null) { + mFadeAnimator.cancel(); + } + } + + public void updateSettingsIcons(final float transX, final float size) { + if (mAnimating || (mGearIcon.getAlpha() == 0)) { + // Don't adjust when animating or settings aren't visible + return; + } + setIconLocation(transX > 0 /* fromLeft */); + final float fadeThreshold = size * 0.3f; + final float absTrans = Math.abs(transX); + float desiredAlpha = 0; + + // if ((fromLeft && transX <= fadeThreshold) || (!fromLeft && absTrans <= fadeThreshold)) { + if (absTrans <= fadeThreshold) { + desiredAlpha = 1; + } else { + desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold)); + } + setGearAlpha(desiredAlpha); + } + + public void fadeInSettings(final boolean fromLeft, final float transX, + final float notiThreshold) { + setIconLocation(transX > 0 /* fromLeft */); + mFadeAnimator = ValueAnimator.ofFloat(mGearIcon.getAlpha(), 1); + mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + final float absTrans = Math.abs(transX); + + boolean pastGear = (fromLeft && transX <= notiThreshold) + || (!fromLeft && absTrans <= notiThreshold); + if (pastGear && !mSettingsFadedIn) { + setGearAlpha((float) animation.getAnimatedValue()); + } + } + }); + mFadeAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + mAnimating = false; + mSettingsFadedIn = false; + } + + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + mAnimating = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mAnimating = false; + mSettingsFadedIn = true; + } + }); + mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN); + mFadeAnimator.setDuration(200); + mFadeAnimator.start(); + } + + @Override + public void onClick(View v) { + mListener.onGearTouched(mParent); + } + + private void setIconLocation(boolean onLeft) { + if (onLeft == mOnLeft) { + // Same side? Do nothing. + return; + } + mGearIcon.setTranslationX(onLeft ? 0 : (getWidth() - mHorizSpaceForGear)); + mOnLeft = onLeft; + } +} 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 47d3093c433a4..032957f50ff40 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -746,6 +746,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById( R.id.notification_stack_scroller); mStackScroller.setLongPressListener(getNotificationLongClicker()); + mStackScroller.setGearDisplayedListener(getGearDisplayedListener()); mStackScroller.setPhoneStatusBar(this); mStackScroller.setGroupManager(mGroupManager); mStackScroller.setHeadsUpManager(mHeadsUpManager); 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 bf4245b5e7edb..f078b53a9e10f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -18,10 +18,12 @@ package com.android.systemui.statusbar.stack; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.TimeAnimator; import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; @@ -32,6 +34,7 @@ import android.graphics.PointF; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; +import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; @@ -56,6 +59,8 @@ import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.NotificationOverflowContainer; +import com.android.systemui.statusbar.NotificationSettingsIconRow; +import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener; import com.android.systemui.statusbar.StackScrollerDecorView; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -72,7 +77,8 @@ import java.util.HashSet; */ public class NotificationStackScrollLayout extends ViewGroup implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter, - ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener { + ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener, + SettingsIconRowListener { public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; private static final String TAG = "StackScroller"; @@ -207,6 +213,11 @@ public class NotificationStackScrollLayout extends ViewGroup */ private int mMaxScrollAfterExpand; private SwipeHelper.LongPressListener mLongPressListener; + private GearDisplayedListener mGearDisplayedListener; + + private NotificationSettingsIconRow mCurrIconRow; + private View mTranslatingParentView; + private View mGearExposedView; /** * Should in this touch motion only be scrolling allowed? It's true when the scroller was @@ -304,8 +315,7 @@ public class NotificationStackScrollLayout extends ViewGroup minHeight, maxHeight); mExpandHelper.setEventSource(this); mExpandHelper.setScrollAdapter(this); - - mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext()); + mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext()); mSwipeHelper.setLongPressListener(mLongPressListener); mStackScrollAlgorithm = new StackScrollAlgorithm(context); initView(context); @@ -320,6 +330,13 @@ public class NotificationStackScrollLayout extends ViewGroup mBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); } + @Override + public void onGearTouched(ExpandableNotificationRow row) { + if (mLongPressListener != null) { + mLongPressListener.onLongPress(row, 0, 0); + } + } + @Override protected void onDraw(Canvas canvas) { canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, mBackgroundPaint); @@ -630,6 +647,10 @@ public class NotificationStackScrollLayout extends ViewGroup mLongPressListener = listener; } + public void setGearDisplayedListener(GearDisplayedListener listener) { + mGearDisplayedListener = listener; + } + public void setQsContainer(ViewGroup qsContainer) { mQsContainer = qsContainer; } @@ -666,7 +687,7 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - public void onChildSnappedBack(View animView) { + public void onChildSnappedBack(View animView, float targetLeft) { mAmbientState.onDragFinished(animView); if (!mDragAnimPendingChildren.contains(animView)) { if (mAnimationsEnabled) { @@ -678,6 +699,13 @@ public class NotificationStackScrollLayout extends ViewGroup // We start the swipe and snap back in the same frame, we don't want any animation mDragAnimPendingChildren.remove(animView); } + + if (targetLeft == 0 && mCurrIconRow != null) { + mCurrIconRow.resetState(); + if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) { + mGearExposedView = null; + } + } } @Override @@ -686,7 +714,7 @@ public class NotificationStackScrollLayout extends ViewGroup mScrimController.setTopHeadsUpDragAmount(animView, Math.min(Math.abs(swipeProgress - 1.0f), 1.0f)); } - return false; + return true; // Don't fade out the notification } public void onBeginDrag(View v) { @@ -726,8 +754,21 @@ public class NotificationStackScrollLayout extends ViewGroup return mPhoneStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f; } + @Override public View getChildAtPosition(MotionEvent ev) { - return getChildAtPosition(ev.getX(), ev.getY()); + View child = getChildAtPosition(ev.getX(), ev.getY()); + if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + ExpandableNotificationRow parent = row.getNotificationParent(); + if (mGearExposedView != null && parent != null + && parent.areChildrenExpanded() && mGearExposedView == parent) { + // In this case the group is expanded and showing the gear for the + // group, further interaction should apply to the group, not any + // child notifications so we use the parent of the child. + child = row.getNotificationParent(); + } + } + return child; } public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) { @@ -841,10 +882,6 @@ public class NotificationStackScrollLayout extends ViewGroup return mScrollingEnabled; } - public View getChildContentView(View v) { - return v; - } - public boolean canChildBeDismissed(View v) { return StackScrollAlgorithm.canChildBeDismissed(v); } @@ -3008,6 +3045,9 @@ public class NotificationStackScrollLayout extends ViewGroup disableClipOptimization(); } handleDismissAllClipping(); + if (mCurrIconRow != null & mCurrIconRow.isVisible()) { + mCurrIconRow.getNotificationParent().animateTranslateNotification(0 /* left target */); + } } private void handleDismissAllClipping() { @@ -3268,6 +3308,247 @@ public class NotificationStackScrollLayout extends ViewGroup public void flingTopOverscroll(float velocity, boolean open); } + /** + * A listener that is notified when the gear is shown behind a notification. + */ + public interface GearDisplayedListener { + void onGearDisplayed(ExpandableNotificationRow row); + } + + private class NotificationSwipeHelper extends SwipeHelper { + private static final int MOVE_STATE_LEFT = -1; + private static final int MOVE_STATE_UNDEFINED = 0; + private static final int MOVE_STATE_RIGHT = 1; + + private static final long GEAR_SHOW_DELAY = 60; + + private ArrayList mTranslatingViews = new ArrayList<>(); + private CheckForDrag mCheckForDrag; + private Handler mHandler; + private int mMoveState = MOVE_STATE_UNDEFINED; + + public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) { + super(swipeDirection, callback, context); + mHandler = new Handler(); + } + + @Override + public void onDownUpdate(View currView) { + // Set the active view + mTranslatingParentView = currView; + + // Reset check for drag gesture + mCheckForDrag = null; + + // Slide back any notifications that might be showing a gear + resetExposedGearView(); + + if (currView instanceof ExpandableNotificationRow) { + // Set the listener for the current row's gear + mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow(); + mCurrIconRow.setGearListener(NotificationStackScrollLayout.this); + + // And the translating children + mTranslatingViews = ((ExpandableNotificationRow) currView).getContentViews(); + } + mMoveState = MOVE_STATE_UNDEFINED; + } + + @Override + public void onMoveUpdate(View view, float translation, float delta) { + final int newMoveState = (delta < 0) ? MOVE_STATE_RIGHT : MOVE_STATE_LEFT; + if (mMoveState != MOVE_STATE_UNDEFINED && mMoveState != newMoveState) { + // Changed directions, make sure we check for drag again. + mCheckForDrag = null; + } + mMoveState = newMoveState; + + if (view instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) view).setTranslationForOutline(translation); + if (!isPinnedHeadsUp(view)) { + // Only show the gear if we're not a heads up view. + checkForDrag(); + if (mCurrIconRow != null) { + mCurrIconRow.updateSettingsIcons(translation, getSize(view)); + } + } + } + } + + @Override + public void dismissChild(final View view, float velocity) { + cancelCheckForDrag(); + super.dismissChild(view, velocity); + } + + @Override + public void snapChild(final View animView, final float targetLeft, float velocity) { + final float snapBackThreshold = getSpaceForGear(animView); + final float translation = getTranslation(animView); + final boolean fromLeft = translation > 0; + final float absTrans = Math.abs(translation); + final float notiThreshold = getSize(mTranslatingParentView) * 0.4f; + + boolean pastGear = (fromLeft && translation >= snapBackThreshold * 0.4f + && translation <= notiThreshold) || + (!fromLeft && absTrans >= snapBackThreshold * 0.4f + && absTrans <= notiThreshold); + + if (pastGear && !isPinnedHeadsUp(animView)) { + // bouncity + final float target = fromLeft ? snapBackThreshold : -snapBackThreshold; + mGearExposedView = mTranslatingParentView; + if (mGearDisplayedListener != null + && (animView instanceof ExpandableNotificationRow)) { + mGearDisplayedListener.onGearDisplayed((ExpandableNotificationRow) animView); + } + super.snapChild(animView, target, velocity); + } else { + super.snapChild(animView, 0, velocity); + } + } + + @Override + public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) { + if (mDismissAllInProgress) { + // When dismissing all, we translate the entire view instead. + super.onTranslationUpdate(animView, value, canBeDismissed); + return; + } + if (animView instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) animView).setTranslationForOutline(value); + } + if (mCurrIconRow != null) { + mCurrIconRow.updateSettingsIcons(value, getSize(animView)); + } + } + + @Override + public Animator getViewTranslationAnimator(View v, float target, + AnimatorUpdateListener listener) { + if (mDismissAllInProgress) { + // When dismissing all, we translate the entire view instead. + return super.getViewTranslationAnimator(v, target, listener); + } + ArrayList animators = new ArrayList(); + for (int i = 0; i < mTranslatingViews.size(); i++) { + ObjectAnimator anim = createTranslationAnimation(mTranslatingViews.get(i), target); + animators.add(anim); + if (i == 0 && listener != null) { + anim.addUpdateListener(listener); + } + } + AnimatorSet set = new AnimatorSet(); + set.playTogether(animators); + return set; + } + + @Override + public void setTranslation(View v, float translate) { + if (mDismissAllInProgress) { + // When dismissing all, we translate the entire view instead. + super.setTranslation(v, translate); + return; + } + // Translate the group of views + for (int i = 0; i < mTranslatingViews.size(); i++) { + if (mTranslatingViews.get(i) != null) { + super.setTranslation(mTranslatingViews.get(i), translate); + } + } + } + + @Override + public float getTranslation(View v) { + if (mDismissAllInProgress) { + // When dismissing all, we translate the entire view instead. + return super.getTranslation(v); + } + // All of the views in the list should have same translation, just use first one. + if (mTranslatingViews.size() > 0) { + return super.getTranslation(mTranslatingViews.get(0)); + } + return 0; + } + + + /** + * Returns the horizontal space in pixels required to display the gear behind a + * notification. + */ + private float getSpaceForGear(View view) { + if (view instanceof ExpandableNotificationRow) { + return ((ExpandableNotificationRow) view).getSpaceForGear(); + } + return 0; + } + + private void checkForDrag() { + if (mCheckForDrag == null) { + mCheckForDrag = new CheckForDrag(); + mHandler.postDelayed(mCheckForDrag, GEAR_SHOW_DELAY); + } + } + + private void cancelCheckForDrag() { + if (mCurrIconRow != null) { + mCurrIconRow.cancelFadeAnimator(); + } + mHandler.removeCallbacks(mCheckForDrag); + mCheckForDrag = null; + } + + private final class CheckForDrag implements Runnable { + @Override + public void run() { + final float translation = getTranslation(mTranslatingParentView); + final float absTransX = Math.abs(translation); + final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView); + final float notiThreshold = getSize(mTranslatingParentView) * 0.4f; + if (mCurrIconRow != null && absTransX >= bounceBackToGearWidth * 0.4 + && absTransX < notiThreshold) { + // Show icon + mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation, + notiThreshold); + } else { + // Allow more to be posted if this wasn't a drag. + mCheckForDrag = null; + } + } + } + + private void resetExposedGearView() { + if (mGearExposedView == null || mGearExposedView == mTranslatingParentView) { + // If no gear is showing or it's showing for this view we do nothing. + return; + } + + final View prevGearExposedView = mGearExposedView; + mGearExposedView = null; + + AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animator) { + if (prevGearExposedView instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) prevGearExposedView).getSettingsRow() + .resetState(); + } + } + }; + AnimatorUpdateListener updateListener = new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + if (prevGearExposedView instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) prevGearExposedView) + .setTranslationForOutline((float) animation.getAnimatedValue()); + } + } + }; + Animator set = getViewTranslationAnimator(prevGearExposedView, 0, updateListener); + set.addListener(listener); + set.start(); + } + } + static class AnimationEvent { static AnimationFilter[] FILTERS = new AnimationFilter[] {