Merge "AOD: Update wakeup animation" into oc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
40b34460ef
@@ -36,6 +36,8 @@ public interface DozeHost {
|
||||
void abortPulsing();
|
||||
void extendPulse();
|
||||
|
||||
void setAnimateWakeup(boolean animateWakeup);
|
||||
|
||||
interface Callback {
|
||||
default void onNotificationHeadsUp() {}
|
||||
default void onPowerSaveChanged(boolean active) {}
|
||||
|
||||
@@ -95,6 +95,19 @@ public class DozeUi implements DozeMachine.Part {
|
||||
unscheduleTimeTick();
|
||||
break;
|
||||
}
|
||||
mHost.setAnimateWakeup(shouldAnimateWakeup(newState));
|
||||
}
|
||||
|
||||
private boolean shouldAnimateWakeup(DozeMachine.State state) {
|
||||
switch (state) {
|
||||
case DOZE_AOD:
|
||||
case DOZE_REQUEST_PULSE:
|
||||
case DOZE_PULSING:
|
||||
case DOZE_PULSE_DONE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleTimeTick() {
|
||||
|
||||
@@ -50,7 +50,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
|
||||
|
||||
private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220;
|
||||
private static final int ACTIVATE_ANIMATION_LENGTH = 220;
|
||||
private static final int DARK_ANIMATION_LENGTH = 170;
|
||||
private static final long DARK_ANIMATION_LENGTH = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
|
||||
|
||||
/**
|
||||
* The amount of width, which is kept in the end when performing a disappear animation (also
|
||||
@@ -418,7 +418,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
|
||||
}
|
||||
mDark = dark;
|
||||
updateBackground();
|
||||
updateBackgroundTint(fade);
|
||||
updateBackgroundTint(false);
|
||||
if (!dark && fade && !shouldHideBackground()) {
|
||||
fadeInFromDark(delay);
|
||||
}
|
||||
@@ -555,23 +555,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
|
||||
final View background = mDimmed ? mBackgroundDimmed : mBackgroundNormal;
|
||||
background.setAlpha(0f);
|
||||
mBackgroundVisibilityUpdater.onAnimationUpdate(null);
|
||||
background.setPivotX(mBackgroundDimmed.getWidth() / 2f);
|
||||
background.setPivotY(getActualHeight() / 2f);
|
||||
background.setScaleX(DARK_EXIT_SCALE_START);
|
||||
background.setScaleY(DARK_EXIT_SCALE_START);
|
||||
background.animate()
|
||||
.alpha(1f)
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.setDuration(DARK_ANIMATION_LENGTH)
|
||||
.setStartDelay(delay)
|
||||
.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
|
||||
.setInterpolator(Interpolators.ALPHA_IN)
|
||||
.setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
// Jump state if we are cancelled
|
||||
background.setScaleX(1f);
|
||||
background.setScaleY(1f);
|
||||
background.setAlpha(1f);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1368,6 +1368,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
@Override
|
||||
public void setDark(boolean dark, boolean fade, long delay) {
|
||||
super.setDark(dark, fade, delay);
|
||||
if (!mIsHeadsUp) {
|
||||
// Only fade the showing view of the pulsing notification.
|
||||
fade = false;
|
||||
}
|
||||
final NotificationContentView showing = getShowingLayout();
|
||||
if (showing != null) {
|
||||
showing.setDark(dark, fade, delay);
|
||||
|
||||
@@ -427,7 +427,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
if (iconState != null) {
|
||||
iconState.scaleX = newSize / icon.getHeight() / icon.getIconScale();
|
||||
iconState.scaleY = iconState.scaleX;
|
||||
iconState.hidden = transitionAmount == 0.0f;
|
||||
iconState.hidden = transitionAmount == 0.0f && !iconState.isAnimating(icon);
|
||||
iconState.alpha = alpha;
|
||||
iconState.yTranslation = iconYTranslation;
|
||||
if (stayingInShelf) {
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
|
||||
import com.android.systemui.statusbar.StatusBarIconView;
|
||||
import com.android.systemui.statusbar.stack.AnimationFilter;
|
||||
import com.android.systemui.statusbar.stack.AnimationProperties;
|
||||
import com.android.systemui.statusbar.stack.StackStateAnimator;
|
||||
import com.android.systemui.statusbar.stack.ViewState;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -87,6 +88,16 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
|
||||
return mAnimationFilter;
|
||||
}
|
||||
}.setDuration(200).setDelay(50);
|
||||
|
||||
private static final AnimationProperties UNDARK_PROPERTIES = new AnimationProperties() {
|
||||
private AnimationFilter mAnimationFilter = new AnimationFilter()
|
||||
.animateX();
|
||||
|
||||
@Override
|
||||
public AnimationFilter getAnimationFilter() {
|
||||
return mAnimationFilter;
|
||||
}
|
||||
}.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP);
|
||||
public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5;
|
||||
|
||||
private boolean mShowAllIcons = true;
|
||||
@@ -404,11 +415,14 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
|
||||
|
||||
public void setDark(boolean dark, boolean fade, long delay) {
|
||||
mDark = dark;
|
||||
mDisallowNextAnimation = true;
|
||||
mDisallowNextAnimation |= !fade;
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View view = getChildAt(i);
|
||||
if (view instanceof StatusBarIconView) {
|
||||
((StatusBarIconView) view).setDark(dark, fade, delay);
|
||||
if (!dark && fade) {
|
||||
getIconState((StatusBarIconView) view).justUndarkened = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -465,6 +479,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
|
||||
public boolean useFullTransitionAmount;
|
||||
public boolean useLinearTransitionAmount;
|
||||
public boolean translateContent;
|
||||
public boolean justUndarkened;
|
||||
public int iconColor = StatusBarIconView.NO_COLOR;
|
||||
public boolean noAnimations;
|
||||
|
||||
@@ -474,7 +489,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
|
||||
StatusBarIconView icon = (StatusBarIconView) view;
|
||||
boolean animate = false;
|
||||
AnimationProperties animationProperties = null;
|
||||
boolean animationsAllowed = mAnimationsEnabled && !mDisallowNextAnimation
|
||||
boolean animationsAllowed = (mAnimationsEnabled || justUndarkened)
|
||||
&& !mDisallowNextAnimation
|
||||
&& !noAnimations;
|
||||
if (animationsAllowed) {
|
||||
if (justAdded) {
|
||||
@@ -486,6 +502,9 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
|
||||
animationProperties = ADD_ICON_PROPERTIES;
|
||||
animate = true;
|
||||
}
|
||||
} else if (justUndarkened) {
|
||||
animationProperties = UNDARK_PROPERTIES;
|
||||
animate = true;
|
||||
} else if (visibleState != icon.getVisibleState()) {
|
||||
animationProperties = DOT_ANIMATION_PROPERTIES;
|
||||
animate = true;
|
||||
@@ -536,6 +555,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
|
||||
}
|
||||
justAdded = false;
|
||||
needsCannedAnimation = false;
|
||||
justUndarkened = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4355,7 +4355,7 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
|
||||
private void updateDozingState() {
|
||||
Trace.beginSection("StatusBar#updateDozingState");
|
||||
boolean animate = !mDozing && mDozeScrimController.isPulsing();
|
||||
boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup();
|
||||
mNotificationPanel.setDozing(mDozing, animate);
|
||||
mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
|
||||
mScrimController.setDozing(mDozing);
|
||||
@@ -5007,6 +5007,7 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
|
||||
private final class DozeServiceHost implements DozeHost {
|
||||
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
|
||||
private boolean mAnimateWakeup;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
@@ -5114,6 +5115,14 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
mDozeScrimController.extendPulse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAnimateWakeup(boolean animateWakeup) {
|
||||
mAnimateWakeup = animateWakeup;
|
||||
}
|
||||
|
||||
private boolean shouldAnimateWakeup() {
|
||||
return mAnimateWakeup;
|
||||
}
|
||||
}
|
||||
|
||||
// Begin Extra BaseStatusBar methods.
|
||||
|
||||
@@ -21,7 +21,7 @@ import android.util.Property;
|
||||
import android.view.View;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Filters the animations for only a certain type of properties.
|
||||
@@ -30,6 +30,7 @@ public class AnimationFilter {
|
||||
boolean animateAlpha;
|
||||
boolean animateX;
|
||||
boolean animateY;
|
||||
ArraySet<View> animateYViews = new ArraySet<>();
|
||||
boolean animateZ;
|
||||
boolean animateHeight;
|
||||
boolean animateTopInset;
|
||||
@@ -39,9 +40,7 @@ public class AnimationFilter {
|
||||
public boolean animateShadowAlpha;
|
||||
boolean hasDelays;
|
||||
boolean hasGoToFullShadeEvent;
|
||||
boolean hasDarkEvent;
|
||||
boolean hasHeadsUpDisappearClickEvent;
|
||||
int darkAnimationOriginIndex;
|
||||
private ArraySet<Property> mAnimatedProperties = new ArraySet<>();
|
||||
|
||||
public AnimationFilter animateAlpha() {
|
||||
@@ -105,6 +104,15 @@ public class AnimationFilter {
|
||||
return this;
|
||||
}
|
||||
|
||||
public AnimationFilter animateY(View view) {
|
||||
animateYViews.add(view);
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean shouldAnimateY(View view) {
|
||||
return animateY || animateYViews.contains(view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines multiple filters into {@code this} filter, using or as the operand .
|
||||
*
|
||||
@@ -120,11 +128,6 @@ public class AnimationFilter {
|
||||
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE) {
|
||||
hasGoToFullShadeEvent = true;
|
||||
}
|
||||
if (ev.animationType ==
|
||||
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_DARK) {
|
||||
hasDarkEvent = true;
|
||||
darkAnimationOriginIndex = ev.darkAnimationOriginIndex;
|
||||
}
|
||||
if (ev.animationType == NotificationStackScrollLayout.AnimationEvent
|
||||
.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
|
||||
hasHeadsUpDisappearClickEvent = true;
|
||||
@@ -136,6 +139,7 @@ public class AnimationFilter {
|
||||
animateAlpha |= filter.animateAlpha;
|
||||
animateX |= filter.animateX;
|
||||
animateY |= filter.animateY;
|
||||
animateYViews.addAll(filter.animateYViews);
|
||||
animateZ |= filter.animateZ;
|
||||
animateHeight |= filter.animateHeight;
|
||||
animateTopInset |= filter.animateTopInset;
|
||||
@@ -151,6 +155,7 @@ public class AnimationFilter {
|
||||
animateAlpha = false;
|
||||
animateX = false;
|
||||
animateY = false;
|
||||
animateYViews.clear();
|
||||
animateZ = false;
|
||||
animateHeight = false;
|
||||
animateShadowAlpha = false;
|
||||
@@ -160,10 +165,7 @@ public class AnimationFilter {
|
||||
animateHideSensitive = false;
|
||||
hasDelays = false;
|
||||
hasGoToFullShadeEvent = false;
|
||||
hasDarkEvent = false;
|
||||
hasHeadsUpDisappearClickEvent = false;
|
||||
darkAnimationOriginIndex =
|
||||
NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
|
||||
mAnimatedProperties.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ import android.view.ViewTreeObserver;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.OverScroller;
|
||||
@@ -3119,7 +3120,11 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
|
||||
private void generateDarkEvent() {
|
||||
if (mDarkNeedsAnimation) {
|
||||
AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
|
||||
AnimationEvent ev = new AnimationEvent(null,
|
||||
AnimationEvent.ANIMATION_TYPE_DARK,
|
||||
new AnimationFilter()
|
||||
.animateDark()
|
||||
.animateY(mShelf));
|
||||
ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
|
||||
mAnimationEvents.add(ev);
|
||||
startBackgroundFadeIn();
|
||||
@@ -3701,18 +3706,7 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
|
||||
private void startBackgroundFadeIn() {
|
||||
ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, BACKGROUND_FADE, 0f, 1f);
|
||||
int maxLength;
|
||||
if (mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE
|
||||
|| mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
|
||||
maxLength = getNotGoneChildCount() - 1;
|
||||
} else {
|
||||
maxLength = Math.max(mDarkAnimationOriginIndex,
|
||||
getNotGoneChildCount() - mDarkAnimationOriginIndex - 1);
|
||||
}
|
||||
maxLength = Math.max(0, maxLength);
|
||||
long delay = maxLength * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_DARK;
|
||||
fadeAnimator.setStartDelay(delay);
|
||||
fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
|
||||
fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP);
|
||||
fadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
|
||||
fadeAnimator.start();
|
||||
}
|
||||
@@ -4563,9 +4557,7 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
.animateZ(),
|
||||
|
||||
// ANIMATION_TYPE_DARK
|
||||
new AnimationFilter()
|
||||
.animateDark()
|
||||
.hasDelays(),
|
||||
null, // Unused
|
||||
|
||||
// ANIMATION_TYPE_GO_TO_FULL_SHADE
|
||||
new AnimationFilter()
|
||||
@@ -4674,7 +4666,7 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
StackStateAnimator.ANIMATION_DURATION_STANDARD,
|
||||
|
||||
// ANIMATION_TYPE_DARK
|
||||
StackStateAnimator.ANIMATION_DURATION_STANDARD,
|
||||
StackStateAnimator.ANIMATION_DURATION_WAKEUP,
|
||||
|
||||
// ANIMATION_TYPE_GO_TO_FULL_SHADE
|
||||
StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
|
||||
@@ -4740,12 +4732,20 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
this(view, type, LENGTHS[type]);
|
||||
}
|
||||
|
||||
AnimationEvent(View view, int type, AnimationFilter filter) {
|
||||
this(view, type, LENGTHS[type], filter);
|
||||
}
|
||||
|
||||
AnimationEvent(View view, int type, long length) {
|
||||
this(view, type, length, FILTERS[type]);
|
||||
}
|
||||
|
||||
AnimationEvent(View view, int type, long length, AnimationFilter filter) {
|
||||
eventStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
changingView = view;
|
||||
animationType = type;
|
||||
filter = FILTERS[type];
|
||||
this.length = length;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,6 +40,7 @@ import java.util.Stack;
|
||||
public class StackStateAnimator {
|
||||
|
||||
public static final int ANIMATION_DURATION_STANDARD = 360;
|
||||
public static final int ANIMATION_DURATION_WAKEUP = 200;
|
||||
public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
|
||||
public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
|
||||
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
|
||||
@@ -49,7 +50,6 @@ public class StackStateAnimator {
|
||||
public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
|
||||
public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
|
||||
public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
|
||||
public static final int ANIMATION_DELAY_PER_ELEMENT_DARK = 24;
|
||||
public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
|
||||
public static final int ANIMATION_DELAY_HEADS_UP = 120;
|
||||
|
||||
@@ -219,9 +219,6 @@ public class StackStateAnimator {
|
||||
|
||||
private long calculateChildAnimationDelay(ExpandableViewState viewState,
|
||||
StackScrollState finalState) {
|
||||
if (mAnimationFilter.hasDarkEvent) {
|
||||
return calculateDelayDark(viewState);
|
||||
}
|
||||
if (mAnimationFilter.hasGoToFullShadeEvent) {
|
||||
return calculateDelayGoToFullShade(viewState);
|
||||
}
|
||||
@@ -278,20 +275,6 @@ public class StackStateAnimator {
|
||||
return minDelay;
|
||||
}
|
||||
|
||||
private long calculateDelayDark(ExpandableViewState viewState) {
|
||||
int referenceIndex;
|
||||
if (mAnimationFilter.darkAnimationOriginIndex ==
|
||||
NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
|
||||
referenceIndex = 0;
|
||||
} else if (mAnimationFilter.darkAnimationOriginIndex ==
|
||||
NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
|
||||
referenceIndex = mHostLayout.getNotGoneChildCount() - 1;
|
||||
} else {
|
||||
referenceIndex = mAnimationFilter.darkAnimationOriginIndex;
|
||||
}
|
||||
return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
|
||||
}
|
||||
|
||||
private long calculateDelayGoToFullShade(ExpandableViewState viewState) {
|
||||
int shelfIndex = mShelf.getNotGoneIndex();
|
||||
float index = viewState.notGoneIndex;
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.app.Notification;
|
||||
import android.util.Property;
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
@@ -28,6 +29,7 @@ import android.view.animation.Interpolator;
|
||||
import com.android.systemui.Interpolators;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.statusbar.ExpandableView;
|
||||
import com.android.systemui.statusbar.NotificationShelf;
|
||||
import com.android.systemui.statusbar.notification.PropertyAnimator;
|
||||
import com.android.systemui.statusbar.policy.HeadsUpManager;
|
||||
|
||||
@@ -223,7 +225,7 @@ public class ViewState {
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isAnimating(View view) {
|
||||
public boolean isAnimating(View view) {
|
||||
if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_X)) {
|
||||
return true;
|
||||
}
|
||||
@@ -540,7 +542,7 @@ public class ViewState {
|
||||
}
|
||||
ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
|
||||
AnimationFilter filter = properties.getAnimationFilter();
|
||||
if (!filter.animateY) {
|
||||
if (!filter.shouldAnimateY(child)) {
|
||||
// just a local update was performed
|
||||
if (previousAnimator != null) {
|
||||
// we need to increase all animation keyframes of the previous animator by the
|
||||
|
||||
@@ -24,8 +24,9 @@ import android.app.PendingIntent;
|
||||
*/
|
||||
class DozeHostFake implements DozeHost {
|
||||
Callback callback;
|
||||
private boolean pulseAborted;
|
||||
private boolean pulseExtended;
|
||||
boolean pulseAborted;
|
||||
boolean pulseExtended;
|
||||
boolean animateWakeup;
|
||||
|
||||
@Override
|
||||
public void addCallback(@NonNull Callback callback) {
|
||||
@@ -81,4 +82,9 @@ class DozeHostFake implements DozeHost {
|
||||
public void extendPulse() {
|
||||
pulseExtended = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAnimateWakeup(boolean animateWakeup) {
|
||||
this.animateWakeup = animateWakeup;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user