Merge "Initial implementation of dismiss gesture + inline settings icon" into nyc-dev

This commit is contained in:
Mady Mellor
2016-02-19 22:06:31 +00:00
committed by Android (Google) Code Review
12 changed files with 785 additions and 64 deletions

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 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.
-->
<com.android.systemui.statusbar.NotificationSettingsIconRow
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@+id/gear_icon"
android:layout_width="@dimen/notification_gear_size"
android:layout_height="@dimen/notification_gear_size"
android:layout_marginTop="@dimen/notification_gear_top_margin"
android:layout_marginStart="@dimen/notification_gear_side_margin"
android:layout_marginEnd="@dimen/notification_gear_side_margin"
android:src="@drawable/ic_settings"
android:tint="@color/notification_gear_color"
android:visibility="gone"
android:alpha="0"
android:background="?android:attr/selectableItemBackgroundBorderless"
/>
</com.android.systemui.statusbar.NotificationSettingsIconRow>

View File

@@ -23,6 +23,14 @@
android:clickable="true"
>
<ViewStub
android:layout="@layout/notification_settings_icon_row"
android:id="@+id/settings_icon_row_stub"
android:inflatedId="@+id/notification_settings_icon_row"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<com.android.systemui.statusbar.NotificationBackgroundView
android:id="@+id/backgroundNormal"
android:layout_width="match_parent"

View File

@@ -102,6 +102,9 @@
<!-- The color of the circle around the primary user in the user switcher -->
<color name="current_user_border_color">@color/system_accent_color</color>
<!-- The color of the gear shown behind a notification -->
<color name="notification_gear_color">#ff757575</color>
<!-- The "inside" of a notification, reached via longpress -->
<color name="notification_guts_bg_color">#eeeeee</color>
<color name="notification_guts_slider_color">@*android:color/material_deep_teal_500</color>

View File

@@ -78,6 +78,15 @@
<!-- Minimum layouted height of a notification in the statusbar-->
<dimen name="min_notification_layout_height">48dp</dimen>
<!-- Size of gear icon displayed behind a notification -->
<dimen name="notification_gear_size">24dp</dimen>
<!-- The space above the gear icon displayed behind a notification -->
<dimen name="notification_gear_top_margin">30dp</dimen>
<!-- The space on either side of the gear icon displayed behind a notification -->
<dimen name="notification_gear_side_margin">20dp</dimen>
<!-- size at which Notification icons will be drawn in the status bar -->
<dimen name="status_bar_icon_drawing_size">17dip</dimen>

View File

@@ -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.

View File

@@ -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;

View File

@@ -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

View File

@@ -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<View> 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<View>();
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<View> 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);

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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<View> 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<Animator> animators = new ArrayList<Animator>();
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[] {