Merge "[SystemUI] Add notification to window feature." into sc-v2-dev

This commit is contained in:
Winson Chung
2021-08-07 05:46:56 +00:00
committed by Android (Google) Code Review
15 changed files with 545 additions and 42 deletions

View File

@@ -671,4 +671,7 @@
1 - Override the setting to always bypass keyguard
2 - Override the setting to never bypass keyguard -->
<integer name="config_face_unlock_bypass_override">0</integer>
<!-- Flag to activate notification to contents feature -->
<bool name="config_notificationToContents">false</bool>
</resources>

View File

@@ -1594,4 +1594,6 @@
<!-- The padding between the icon and the text. -->
<dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen>
<dimen name="ongoing_call_chip_corner_radius">28dp</dimen>
<dimen name="drag_and_drop_icon_size">70dp</dimen>
</resources>

View File

@@ -25,6 +25,8 @@ import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.res.Resources;
import android.graphics.RectF;
import android.os.Handler;
@@ -73,6 +75,9 @@ public class SwipeHelper implements Gefingerpoken {
private final FlingAnimationUtils mFlingAnimationUtils;
private float mPagingTouchSlop;
private final float mSlopMultiplier;
private int mTouchSlop;
private float mTouchSlopMultiplier;
private final Callback mCallback;
private final int mSwipeDirection;
private final VelocityTracker mVelocityTracker;
@@ -105,6 +110,10 @@ public class SwipeHelper implements Gefingerpoken {
final int y = (int) mDownLocation[1] - mViewOffset[1];
mTouchedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
((ExpandableNotificationRow) mTouchedView).doLongClickCallback(x, y);
if (isAvailableToDragAndDrop(mTouchedView)) {
mCallback.onLongPressSent(mTouchedView);
}
}
}
}
@@ -126,6 +135,8 @@ public class SwipeHelper implements Gefingerpoken {
mVelocityTracker = VelocityTracker.obtain();
mPagingTouchSlop = viewConfiguration.getScaledPagingTouchSlop();
mSlopMultiplier = viewConfiguration.getScaledAmbiguousGestureMultiplier();
mTouchSlop = viewConfiguration.getScaledTouchSlop();
mTouchSlopMultiplier = viewConfiguration.getAmbiguousGestureMultiplier();
// Extra long-press!
mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
@@ -297,7 +308,9 @@ public class SwipeHelper implements Gefingerpoken {
mIsSwiping = false;
mSnappingChild = false;
mLongPressSent = false;
mCallback.onLongPressSent(null);
mVelocityTracker.clear();
cancelLongPress();
mTouchedView = mCallback.getChildAtPosition(ev);
if (mTouchedView != null) {
@@ -349,6 +362,7 @@ public class SwipeHelper implements Gefingerpoken {
mIsSwiping = false;
mTouchedView = null;
mLongPressSent = false;
mCallback.onLongPressSent(null);
mMenuRowIntercepting = false;
cancelLongPress();
if (captured) return true;
@@ -593,11 +607,7 @@ public class SwipeHelper implements Gefingerpoken {
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mLongPressSent && !mMenuRowIntercepting) {
return true;
}
if (!mIsSwiping && !mMenuRowIntercepting) {
if (!mIsSwiping && !mMenuRowIntercepting && !mLongPressSent) {
if (mCallback.getChildAtPosition(ev) != null) {
// We are dragging directly over a card, make sure that we also catch the gesture
// even if nobody else wants the touch event.
@@ -623,30 +633,40 @@ public class SwipeHelper implements Gefingerpoken {
if (absDelta >= getFalsingThreshold()) {
mTouchAboveFalsingThreshold = true;
}
// don't let items that can't be dismissed be dragged more than
// maxScrollDistance
if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissedInDirection(
mTouchedView,
delta > 0)) {
float size = getSize(mTouchedView);
float maxScrollDistance = MAX_SCROLL_SIZE_FRACTION * size;
if (absDelta >= size) {
delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
} else {
int startPosition = mCallback.getConstrainSwipeStartPosition();
if (absDelta > startPosition) {
int signedStartPosition =
(int) (startPosition * Math.signum(delta));
delta = signedStartPosition
+ maxScrollDistance * (float) Math.sin(
((delta - signedStartPosition) / size) * (Math.PI / 2));
if (mLongPressSent) {
if (absDelta >= getTouchSlop(ev)) {
if (mTouchedView instanceof ExpandableNotificationRow) {
((ExpandableNotificationRow) mTouchedView)
.doDragCallback(ev.getX(), ev.getY());
}
}
} else {
// don't let items that can't be dismissed be dragged more than
// maxScrollDistance
if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissedInDirection(
mTouchedView,
delta > 0)) {
float size = getSize(mTouchedView);
float maxScrollDistance = MAX_SCROLL_SIZE_FRACTION * size;
if (absDelta >= size) {
delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
} else {
int startPosition = mCallback.getConstrainSwipeStartPosition();
if (absDelta > startPosition) {
int signedStartPosition =
(int) (startPosition * Math.signum(delta));
delta = signedStartPosition
+ maxScrollDistance * (float) Math.sin(
((delta - signedStartPosition) / size) * (Math.PI / 2));
}
}
}
}
setTranslation(mTouchedView, mTranslation + delta);
updateSwipeProgressFromOffset(mTouchedView, mCanCurrViewBeDimissed);
onMoveUpdate(mTouchedView, ev, mTranslation + delta, delta);
setTranslation(mTouchedView, mTranslation + delta);
updateSwipeProgressFromOffset(mTouchedView, mCanCurrViewBeDimissed);
onMoveUpdate(mTouchedView, ev, mTranslation + delta, delta);
}
}
break;
case MotionEvent.ACTION_UP:
@@ -747,6 +767,29 @@ public class SwipeHelper implements Gefingerpoken {
mIsSwiping = false;
}
private float getTouchSlop(MotionEvent event) {
// Adjust the touch slop if another gesture may be being performed.
return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
? mTouchSlop * mTouchSlopMultiplier
: mTouchSlop;
}
private boolean isAvailableToDragAndDrop(View v) {
if (v.getResources().getBoolean(R.bool.config_notificationToContents)) {
if (v instanceof ExpandableNotificationRow) {
ExpandableNotificationRow enr = (ExpandableNotificationRow) v;
boolean canBubble = enr.getEntry().canBubble();
Notification notif = enr.getEntry().getSbn().getNotification();
PendingIntent dragIntent = notif.contentIntent != null ? notif.contentIntent
: notif.fullScreenIntent;
if (dragIntent != null && dragIntent.isActivity() && !canBubble) {
return true;
}
}
}
return false;
}
public interface Callback {
View getChildAtPosition(MotionEvent ev);
@@ -770,6 +813,13 @@ public class SwipeHelper implements Gefingerpoken {
void onDragCancelled(View v);
/**
* Called when the child is long pressed and available to start drag and drop.
*
* @param v the view that was long pressed.
*/
void onLongPressSent(View v);
/**
* Called when the child is snapped to a position.
*

View File

@@ -20,6 +20,7 @@ import android.content.Intent;
import android.service.notification.StatusBarNotification;
import android.view.View;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
@@ -37,6 +38,9 @@ public interface NotificationActivityStarter {
/** Called when the user clicks "Manage" or "History" in the Shade. */
void startHistoryIntent(View view, boolean showHistory);
/** Called when the user succeed to drop notification to proper target view. */
void onDragSuccess(NotificationEntry entry);
default boolean isCollapsingToShowActivityOverLockscreen() {
return false;
}

View File

@@ -43,6 +43,14 @@ public final class NotificationClicker implements View.OnClickListener {
private final Optional<Bubbles> mBubblesOptional;
private final NotificationActivityStarter mNotificationActivityStarter;
private ExpandableNotificationRow.OnDragSuccessListener mOnDragSuccessListener =
new ExpandableNotificationRow.OnDragSuccessListener() {
@Override
public void onDragSuccess(NotificationEntry entry) {
mNotificationActivityStarter.onDragSuccess(entry);
}
};
private NotificationClicker(
NotificationClickerLogger logger,
Optional<StatusBar> statusBarOptional,
@@ -111,8 +119,10 @@ public final class NotificationClicker implements View.OnClickListener {
if (notification.contentIntent != null || notification.fullScreenIntent != null
|| row.getEntry().isBubble()) {
row.setOnClickListener(this);
row.setOnDragSuccessListener(mOnDragSuccessListener);
} else {
row.setOnClickListener(null);
row.setOnDragSuccessListener(null);
}
}

View File

@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.MathUtils;
@@ -139,6 +140,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private boolean mIsHeadsUpAnimation;
private int mHeadsUpAddStartLocation;
private float mHeadsUpLocation;
/* In order to track headsup longpress coorindate. */
protected Point mTargetPoint;
private boolean mIsAppearing;
private boolean mDismissed;
private boolean mRefocusOnDismiss;
@@ -568,8 +571,19 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
final int actualHeight = getActualHeight();
float bottom = actualHeight * interpolatedFraction;
setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
bottom + mAppearAnimationTranslation);
if (mTargetPoint != null) {
int width = getWidth();
float fraction = 1 - mAppearAnimationFraction;
setOutlineRect(mTargetPoint.x * fraction,
mAnimationTranslationY
+ (mAnimationTranslationY - mTargetPoint.y) * fraction,
width - (width - mTargetPoint.x) * fraction,
actualHeight - (actualHeight - mTargetPoint.y) * fraction);
} else {
setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
bottom + mAppearAnimationTranslation);
}
}
private float getInterpolatedAppearAnimationFraction() {

View File

@@ -37,6 +37,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -260,6 +261,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
// Use #setLongPressPosition to optionally assign positional data with the long press.
private LongPressListener mLongPressListener;
private ExpandableNotificationRowDragController mDragController;
private boolean mGroupExpansionChanging;
/**
@@ -331,6 +334,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
};
private OnClickListener mOnClickListener;
private OnDragSuccessListener mOnDragSuccessListener;
private boolean mHeadsupDisappearRunning;
private View mChildAfterViewWhenDismissed;
private View mGroupParentWhenDismissed;
@@ -1083,6 +1087,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mLongPressListener = longPressListener;
}
public void setDragController(ExpandableNotificationRowDragController dragController) {
mDragController = dragController;
}
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
super.setOnClickListener(l);
@@ -1329,6 +1337,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void dismiss(boolean refocusOnDismiss) {
super.dismiss(refocusOnDismiss);
setLongPressListener(null);
setDragController(null);
mGroupParentWhenDismissed = mNotificationParent;
mChildAfterViewWhenDismissed = null;
mEntry.getIcons().getStatusBarIcon().setDismissed();
@@ -1637,6 +1646,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
onHeightReset();
requestLayout();
setTargetPoint(null);
}
public void showFeedbackIcon(boolean show, Pair<Integer, Integer> resIds) {
@@ -1727,6 +1738,29 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mTranslateableViews.remove(mGutsStub);
}
/**
* Called once when starting drag motion after opening notification guts,
* in case of notification that has {@link android.app.Notification#contentIntent}
* and it is to start an activity.
*/
public void doDragCallback(float x, float y) {
if (mDragController != null) {
setTargetPoint(new Point((int) x, (int) y));
mDragController.startDragAndDrop(this);
}
}
public void setOnDragSuccessListener(OnDragSuccessListener listener) {
mOnDragSuccessListener = listener;
}
/**
* Called when a notification is dropped on proper target window.
*/
public void dragAndDropSuccess() {
mOnDragSuccessListener.onDragSuccess(getEntry());
}
private void doLongClickCallback() {
doLongClickCallback(getWidth() / 2, getHeight() / 2);
}
@@ -3254,6 +3288,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
boolean onLongPress(View v, int x, int y, MenuItem item);
}
/**
* Called when notification drag and drop is finished successfully.
*/
public interface OnDragSuccessListener {
/**
* @param entry NotificationEntry that succeed to drop on proper target window.
*/
void onDragSuccess(NotificationEntry entry);
}
/**
* Equivalent to View.OnClickListener with coordinates
*/
@@ -3321,4 +3365,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
}
private void setTargetPoint(Point p) {
mTargetPoint = p;
}
public Point getTargetPoint() {
return mTargetPoint;
}
}

View File

@@ -25,6 +25,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -85,6 +86,8 @@ public class ExpandableNotificationRowController implements NodeController {
private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
private final Optional<BubblesManager> mBubblesManagerOptional;
private final ExpandableNotificationRowDragController mDragController;
@Inject
public ExpandableNotificationRowController(
ExpandableNotificationRow view,
@@ -109,7 +112,8 @@ public class ExpandableNotificationRowController implements NodeController {
FalsingManager falsingManager,
FalsingCollector falsingCollector,
PeopleNotificationIdentifier peopleNotificationIdentifier,
Optional<BubblesManager> bubblesManagerOptional) {
Optional<BubblesManager> bubblesManagerOptional,
ExpandableNotificationRowDragController dragController) {
mView = view;
mListContainer = listContainer;
mActivatableNotificationViewController = activatableNotificationViewController;
@@ -134,6 +138,7 @@ public class ExpandableNotificationRowController implements NodeController {
mFalsingCollector = falsingCollector;
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
mBubblesManagerOptional = bubblesManagerOptional;
mDragController = dragController;
}
/**
@@ -164,6 +169,10 @@ public class ExpandableNotificationRowController implements NodeController {
);
mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
if (mAllowLongPress) {
if (mView.getResources().getBoolean(R.bool.config_notificationToContents)) {
mView.setDragController(mDragController);
}
mView.setLongPressListener((v, x, y, item) -> {
if (mView.isSummaryWithChildren()) {
mView.expandNotification();

View File

@@ -0,0 +1,171 @@
/*
* Copyright (C) 2021 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.notification.row;
import android.annotation.NonNull;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.DragEvent;
import android.view.HapticFeedbackConstants;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.VisibleForTesting;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import javax.inject.Inject;
/**
* Controller for Notification to window.
*/
public class ExpandableNotificationRowDragController {
private static final String TAG = ExpandableNotificationRowDragController.class.getSimpleName();
private int mIconSize;
private final Context mContext;
private final HeadsUpManager mHeadsUpManager;
@Inject
public ExpandableNotificationRowDragController(Context context,
HeadsUpManager headsUpManager) {
mContext = context;
mHeadsUpManager = headsUpManager;
init();
}
private void init() {
mIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.drag_and_drop_icon_size);
}
/**
* Called when drag event beyond the touchslop,
* and start drag and drop.
*
* @param view notification that was long pressed and started to drag and drop.
*/
@VisibleForTesting
public void startDragAndDrop(View view) {
ExpandableNotificationRow enr = null;
if (view instanceof ExpandableNotificationRow) {
enr = (ExpandableNotificationRow) view;
}
StatusBarNotification sn = enr.getEntry().getSbn();
Notification notification = sn.getNotification();
final PendingIntent contentIntent = notification.contentIntent != null
? notification.contentIntent
: notification.fullScreenIntent;
Bitmap iconBitmap = getBitmapFromDrawable(
getPkgIcon(enr.getEntry().getSbn().getPackageName()));
final ImageView snapshot = new ImageView(mContext);
snapshot.setImageBitmap(iconBitmap);
snapshot.layout(0, 0, mIconSize, mIconSize);
ClipDescription clipDescription = new ClipDescription("Drag And Drop",
new String[]{ClipDescription.MIMETYPE_APPLICATION_ACTIVITY});
Intent dragIntent = new Intent();
dragIntent.putExtra("android.intent.extra.PENDING_INTENT", contentIntent);
dragIntent.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
ClipData.Item item = new ClipData.Item(dragIntent);
ClipData dragData = new ClipData(clipDescription, item);
View.DragShadowBuilder myShadow = new View.DragShadowBuilder(snapshot);
view.setOnDragListener(getDraggedViewDragListener());
view.startDragAndDrop(dragData, myShadow, null, View.DRAG_FLAG_GLOBAL);
}
private Drawable getPkgIcon(String pkgName) {
Drawable pkgicon = null;
PackageManager pm = mContext.getPackageManager();
ApplicationInfo info;
try {
info = pm.getApplicationInfo(
pkgName,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE);
if (info != null) {
pkgicon = pm.getApplicationIcon(info);
} else {
Log.d(TAG, " application info is null ");
pkgicon = pm.getDefaultActivityIcon();
}
} catch (PackageManager.NameNotFoundException e) {
Log.d(TAG, "can not find package with : " + pkgName);
pkgicon = pm.getDefaultActivityIcon();
}
return pkgicon;
}
private Bitmap getBitmapFromDrawable(@NonNull Drawable drawable) {
final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bmp);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bmp;
}
private View.OnDragListener getDraggedViewDragListener() {
return (view, dragEvent) -> {
switch (dragEvent.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow enr = (ExpandableNotificationRow) view;
if (enr.isPinned()) {
mHeadsUpManager.releaseAllImmediately();
} else {
Dependency.get(ShadeController.class).animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
}
}
return true;
case DragEvent.ACTION_DRAG_ENDED:
if (dragEvent.getResult()) {
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow enr = (ExpandableNotificationRow) view;
enr.dragAndDropSuccess();
}
}
return true;
}
return false;
};
}
}

View File

@@ -183,6 +183,8 @@ public class NotificationStackScrollLayoutController {
private int mBarState;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private View mLongPressedView;
private final NotificationListContainerImpl mNotificationListContainer =
new NotificationListContainerImpl();
@@ -491,6 +493,11 @@ public class NotificationStackScrollLayoutController {
return child;
}
@Override
public void onLongPressSent(View v) {
mLongPressedView = v;
}
@Override
public void onBeginDrag(View v) {
mFalsingCollector.onNotificationStartDismissing();
@@ -1435,6 +1442,10 @@ public class NotificationStackScrollLayoutController {
return mDynamicPrivacyController.isInLockedDownShade();
}
public boolean isLongPressInProgress() {
return mLongPressedView != null;
}
/**
* Set the dimmed state for all of the notification views.
*/
@@ -1689,17 +1700,23 @@ public class NotificationStackScrollLayoutController {
mView.handleEmptySpaceClick(ev);
NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
boolean longPressWantsIt = false;
if (mLongPressedView != null) {
longPressWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
}
boolean expandWantsIt = false;
if (!mSwipeHelper.isSwiping()
if (mLongPressedView == null && !mSwipeHelper.isSwiping()
&& !mView.getOnlyScrollingInThisMotion() && guts == null) {
expandWantsIt = mView.getExpandHelper().onInterceptTouchEvent(ev);
}
boolean scrollWantsIt = false;
if (!mSwipeHelper.isSwiping() && !mView.isExpandingNotification()) {
if (mLongPressedView == null && !mSwipeHelper.isSwiping()
&& !mView.isExpandingNotification()) {
scrollWantsIt = mView.onInterceptTouchEventScroll(ev);
}
boolean swipeWantsIt = false;
if (!mView.isBeingDragged()
if (mLongPressedView == null && !mView.isBeingDragged()
&& !mView.isExpandingNotification()
&& !mView.getExpandedInThisMotion()
&& !mView.getOnlyScrollingInThisMotion()
@@ -1727,7 +1744,7 @@ public class NotificationStackScrollLayoutController {
InteractionJankMonitor.getInstance().begin(mView,
CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
}
return swipeWantsIt || scrollWantsIt || expandWantsIt;
return swipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt;
}
@Override
@@ -1736,11 +1753,15 @@ public class NotificationStackScrollLayoutController {
boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
|| ev.getActionMasked() == MotionEvent.ACTION_UP;
mView.handleEmptySpaceClick(ev);
boolean longPressWantsIt = false;
if (guts != null && mLongPressedView != null) {
longPressWantsIt = mSwipeHelper.onTouchEvent(ev);
}
boolean expandWantsIt = false;
boolean onlyScrollingInThisMotion = mView.getOnlyScrollingInThisMotion();
boolean expandingNotification = mView.isExpandingNotification();
if (mView.getIsExpanded() && !mSwipeHelper.isSwiping() && !onlyScrollingInThisMotion
&& guts == null) {
if (mLongPressedView == null && mView.getIsExpanded()
&& !mSwipeHelper.isSwiping() && !onlyScrollingInThisMotion && guts == null) {
ExpandHelper expandHelper = mView.getExpandHelper();
if (isCancelOrUp) {
expandHelper.onlyObserveMovements(false);
@@ -1754,12 +1775,12 @@ public class NotificationStackScrollLayoutController {
}
}
boolean scrollerWantsIt = false;
if (mView.isExpanded() && !mSwipeHelper.isSwiping() && !expandingNotification
&& !mView.getDisallowScrollingInThisMotion()) {
if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
&& !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
scrollerWantsIt = mView.onScrollTouch(ev);
}
boolean horizontalSwipeWantsIt = false;
if (!mView.isBeingDragged()
if (mLongPressedView == null && !mView.isBeingDragged()
&& !expandingNotification
&& !mView.getExpandedInThisMotion()
&& !onlyScrollingInThisMotion
@@ -1785,7 +1806,7 @@ public class NotificationStackScrollLayoutController {
mView.setCheckForLeaveBehind(true);
}
traceJankOnTouchEvent(ev.getActionMasked(), scrollerWantsIt);
return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt;
return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || longPressWantsIt;
}
private void traceJankOnTouchEvent(int action, boolean scrollerWantsIt) {

View File

@@ -433,6 +433,7 @@ public class StackStateAnimator {
if (row.isDismissed()) {
needsAnimation = false;
}
NotificationEntry entry = row.getEntry();
StatusBarIconView icon = entry.getIcons().getStatusBarIcon();
final StatusBarIconView centeredIcon = entry.getIcons().getCenteredIcon();
@@ -442,7 +443,8 @@ public class StackStateAnimator {
if (icon.getParent() != null) {
icon.getLocationOnScreen(mTmpLocation);
float iconPosition = mTmpLocation[0] - icon.getTranslationX()
+ ViewState.getFinalTranslationX(icon) + icon.getWidth() * 0.25f;
+ ViewState.getFinalTranslationX(icon)
+ icon.getWidth() * 0.25f;
mHostLayout.getLocationOnScreen(mTmpLocation);
targetLocation = iconPosition - mTmpLocation[0];
}

View File

@@ -3947,7 +3947,9 @@ public class NotificationPanelViewController extends PanelViewController {
if (mStatusBar.isBouncerShowing()) {
return true;
}
if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
if (mBar.panelEnabled()
&& !mNotificationStackScrollLayoutController.isLongPressInProgress()
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
return true;
@@ -3999,6 +4001,7 @@ public class NotificationPanelViewController extends PanelViewController {
return true;
}
if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
&& !mNotificationStackScrollLayoutController.isLongPressInProgress()
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
}

View File

@@ -71,6 +71,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowDragController;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -402,6 +403,53 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mIsCollapsingToShowActivityOverLockscreen = false;
}
/**
* Called when a notification is dropped on proper target window.
* Intent that is included in this entry notification,
* will be sent by {@link ExpandableNotificationRowDragController}
*
* @param entry notification entry that is dropped.
*/
@Override
public void onDragSuccess(NotificationEntry entry) {
// this method is not responsible for intent sending.
// will focus follow operation only after drag-and-drop that notification.
NotificationVisibility.NotificationLocation location =
NotificationLogger.getNotificationLocation(entry);
final NotificationVisibility nv = NotificationVisibility.obtain(entry.getKey(),
entry.getRanking().getRank(), getVisibleNotificationsCount(), true, location);
// retrieve the group summary to remove with this entry before we tell NMS the
// notification was clicked to avoid a race condition
final boolean shouldAutoCancel = shouldAutoCancel(entry.getSbn());
final NotificationEntry summaryToRemove = shouldAutoCancel
? mOnUserInteractionCallback.getGroupSummaryToDismiss(entry) : null;
String notificationKey = entry.getKey();
// inform NMS that the notification was clicked
mClickNotifier.onNotificationClick(notificationKey, nv);
if (shouldAutoCancel || mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
notificationKey)) {
// Immediately remove notification from visually showing.
// We have to post the removal to the UI thread for synchronization.
mMainThreadHandler.post(() -> {
final Runnable removeNotification = () ->
mOnUserInteractionCallback.onDismiss(
entry, REASON_CLICK, summaryToRemove);
if (mPresenter.isCollapsing()) {
// To avoid lags we're only performing the remove
// after the shade is collapsed
mShadeController.addPostCollapseAction(removeNotification);
} else {
removeNotification.run();
}
});
}
mIsCollapsingToShowActivityOverLockscreen = false;
}
private void expandBubbleStackOnMainThread(NotificationEntry entry) {
if (!mBubblesManagerOptional.isPresent()) {
return;

View File

@@ -0,0 +1,114 @@
/*
* Copyright (C) 2021 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.notification.row;
import static android.view.DragEvent.ACTION_DRAG_STARTED;
import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.DragEvent;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase {
private ExpandableNotificationRow mRow;
private ExpandableNotificationRow mGroupRow;
private ExpandableNotificationRowDragController mController;
private NotificationTestHelper mNotificationTestHelper;
private NotificationGutsManager mGutsManager = mock(NotificationGutsManager.class);
private HeadsUpManager mHeadsUpManager = mock(HeadsUpManager.class);
private NotificationMenuRow mMenuRow = mock(NotificationMenuRow.class);
private NotificationMenuRowPlugin.MenuItem mMenuItem =
mock(NotificationMenuRowPlugin.MenuItem.class);
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
mDependency.injectMockDependency(ShadeController.class);
mNotificationTestHelper = new NotificationTestHelper(
mContext,
mDependency,
TestableLooper.get(this));
mRow = mNotificationTestHelper.createRow();
mGroupRow = mNotificationTestHelper.createGroup(4);
when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
mController = new ExpandableNotificationRowDragController(mContext, mHeadsUpManager);
}
@Test
public void testDoStartDragHeadsUpNotif_startDragAndDrop() throws Exception {
ExpandableNotificationRowDragController controller = createSpyController();
mRow.setDragController(controller);
mRow.setHeadsUp(true);
mRow.setPinned(true);
mRow.doLongClickCallback(0, 0);
mRow.doDragCallback(0, 0);
verify(controller).startDragAndDrop(mRow);
// Simulate the drag start
mRow.dispatchDragEvent(DragEvent.obtain(ACTION_DRAG_STARTED, 0, 0, 0, 0, null, null, null,
null, null, false));
verify(mHeadsUpManager, times(1)).releaseAllImmediately();
}
@Test
public void testDoStartDragNotif() throws Exception {
ExpandableNotificationRowDragController controller = createSpyController();
mRow.setDragController(controller);
mDependency.get(ShadeController.class).instantExpandNotificationsPanel();
mRow.doDragCallback(0, 0);
verify(controller).startDragAndDrop(mRow);
// Simulate the drag start
mRow.dispatchDragEvent(DragEvent.obtain(ACTION_DRAG_STARTED, 0, 0, 0, 0, null, null, null,
null, null, false));
verify(mDependency.get(ShadeController.class)).animateCollapsePanels(0, true);
}
private ExpandableNotificationRowDragController createSpyController() {
return spy(mController);
}
}

View File

@@ -265,7 +265,8 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
new FalsingManagerFake(),
new FalsingCollectorFake(),
mPeopleNotificationIdentifier,
Optional.of(mock(BubblesManager.class))
Optional.of(mock(BubblesManager.class)),
mock(ExpandableNotificationRowDragController.class)
));
when(mNotificationRowComponentBuilder.activatableNotificationView(any()))