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 1 - Override the setting to always bypass keyguard
2 - Override the setting to never bypass keyguard --> 2 - Override the setting to never bypass keyguard -->
<integer name="config_face_unlock_bypass_override">0</integer> <integer name="config_face_unlock_bypass_override">0</integer>
<!-- Flag to activate notification to contents feature -->
<bool name="config_notificationToContents">false</bool>
</resources> </resources>

View File

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

View File

@@ -25,6 +25,8 @@ import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener; import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.RectF; import android.graphics.RectF;
import android.os.Handler; import android.os.Handler;
@@ -73,6 +75,9 @@ public class SwipeHelper implements Gefingerpoken {
private final FlingAnimationUtils mFlingAnimationUtils; private final FlingAnimationUtils mFlingAnimationUtils;
private float mPagingTouchSlop; private float mPagingTouchSlop;
private final float mSlopMultiplier; private final float mSlopMultiplier;
private int mTouchSlop;
private float mTouchSlopMultiplier;
private final Callback mCallback; private final Callback mCallback;
private final int mSwipeDirection; private final int mSwipeDirection;
private final VelocityTracker mVelocityTracker; private final VelocityTracker mVelocityTracker;
@@ -105,6 +110,10 @@ public class SwipeHelper implements Gefingerpoken {
final int y = (int) mDownLocation[1] - mViewOffset[1]; final int y = (int) mDownLocation[1] - mViewOffset[1];
mTouchedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); mTouchedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
((ExpandableNotificationRow) mTouchedView).doLongClickCallback(x, y); ((ExpandableNotificationRow) mTouchedView).doLongClickCallback(x, y);
if (isAvailableToDragAndDrop(mTouchedView)) {
mCallback.onLongPressSent(mTouchedView);
}
} }
} }
} }
@@ -126,6 +135,8 @@ public class SwipeHelper implements Gefingerpoken {
mVelocityTracker = VelocityTracker.obtain(); mVelocityTracker = VelocityTracker.obtain();
mPagingTouchSlop = viewConfiguration.getScaledPagingTouchSlop(); mPagingTouchSlop = viewConfiguration.getScaledPagingTouchSlop();
mSlopMultiplier = viewConfiguration.getScaledAmbiguousGestureMultiplier(); mSlopMultiplier = viewConfiguration.getScaledAmbiguousGestureMultiplier();
mTouchSlop = viewConfiguration.getScaledTouchSlop();
mTouchSlopMultiplier = viewConfiguration.getAmbiguousGestureMultiplier();
// Extra long-press! // Extra long-press!
mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f); mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
@@ -297,7 +308,9 @@ public class SwipeHelper implements Gefingerpoken {
mIsSwiping = false; mIsSwiping = false;
mSnappingChild = false; mSnappingChild = false;
mLongPressSent = false; mLongPressSent = false;
mCallback.onLongPressSent(null);
mVelocityTracker.clear(); mVelocityTracker.clear();
cancelLongPress();
mTouchedView = mCallback.getChildAtPosition(ev); mTouchedView = mCallback.getChildAtPosition(ev);
if (mTouchedView != null) { if (mTouchedView != null) {
@@ -349,6 +362,7 @@ public class SwipeHelper implements Gefingerpoken {
mIsSwiping = false; mIsSwiping = false;
mTouchedView = null; mTouchedView = null;
mLongPressSent = false; mLongPressSent = false;
mCallback.onLongPressSent(null);
mMenuRowIntercepting = false; mMenuRowIntercepting = false;
cancelLongPress(); cancelLongPress();
if (captured) return true; if (captured) return true;
@@ -593,11 +607,7 @@ public class SwipeHelper implements Gefingerpoken {
@Override @Override
public boolean onTouchEvent(MotionEvent ev) { public boolean onTouchEvent(MotionEvent ev) {
if (mLongPressSent && !mMenuRowIntercepting) { if (!mIsSwiping && !mMenuRowIntercepting && !mLongPressSent) {
return true;
}
if (!mIsSwiping && !mMenuRowIntercepting) {
if (mCallback.getChildAtPosition(ev) != null) { if (mCallback.getChildAtPosition(ev) != null) {
// We are dragging directly over a card, make sure that we also catch the gesture // We are dragging directly over a card, make sure that we also catch the gesture
// even if nobody else wants the touch event. // even if nobody else wants the touch event.
@@ -623,30 +633,40 @@ public class SwipeHelper implements Gefingerpoken {
if (absDelta >= getFalsingThreshold()) { if (absDelta >= getFalsingThreshold()) {
mTouchAboveFalsingThreshold = true; mTouchAboveFalsingThreshold = true;
} }
// don't let items that can't be dismissed be dragged more than
// maxScrollDistance if (mLongPressSent) {
if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissedInDirection( if (absDelta >= getTouchSlop(ev)) {
mTouchedView, if (mTouchedView instanceof ExpandableNotificationRow) {
delta > 0)) { ((ExpandableNotificationRow) mTouchedView)
float size = getSize(mTouchedView); .doDragCallback(ev.getX(), ev.getY());
float maxScrollDistance = MAX_SCROLL_SIZE_FRACTION * size; }
if (absDelta >= size) { }
delta = delta > 0 ? maxScrollDistance : -maxScrollDistance; } else {
} else { // don't let items that can't be dismissed be dragged more than
int startPosition = mCallback.getConstrainSwipeStartPosition(); // maxScrollDistance
if (absDelta > startPosition) { if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissedInDirection(
int signedStartPosition = mTouchedView,
(int) (startPosition * Math.signum(delta)); delta > 0)) {
delta = signedStartPosition float size = getSize(mTouchedView);
+ maxScrollDistance * (float) Math.sin( float maxScrollDistance = MAX_SCROLL_SIZE_FRACTION * size;
((delta - signedStartPosition) / size) * (Math.PI / 2)); 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); setTranslation(mTouchedView, mTranslation + delta);
updateSwipeProgressFromOffset(mTouchedView, mCanCurrViewBeDimissed); updateSwipeProgressFromOffset(mTouchedView, mCanCurrViewBeDimissed);
onMoveUpdate(mTouchedView, ev, mTranslation + delta, delta); onMoveUpdate(mTouchedView, ev, mTranslation + delta, delta);
}
} }
break; break;
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
@@ -747,6 +767,29 @@ public class SwipeHelper implements Gefingerpoken {
mIsSwiping = false; 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 { public interface Callback {
View getChildAtPosition(MotionEvent ev); View getChildAtPosition(MotionEvent ev);
@@ -770,6 +813,13 @@ public class SwipeHelper implements Gefingerpoken {
void onDragCancelled(View v); 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. * 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.service.notification.StatusBarNotification;
import android.view.View; import android.view.View;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 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. */ /** Called when the user clicks "Manage" or "History" in the Shade. */
void startHistoryIntent(View view, boolean showHistory); 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() { default boolean isCollapsingToShowActivityOverLockscreen() {
return false; return false;
} }

View File

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

View File

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

View File

@@ -37,6 +37,7 @@ import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.Point;
import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.ColorDrawable; 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. // Use #setLongPressPosition to optionally assign positional data with the long press.
private LongPressListener mLongPressListener; private LongPressListener mLongPressListener;
private ExpandableNotificationRowDragController mDragController;
private boolean mGroupExpansionChanging; private boolean mGroupExpansionChanging;
/** /**
@@ -331,6 +334,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} }
}; };
private OnClickListener mOnClickListener; private OnClickListener mOnClickListener;
private OnDragSuccessListener mOnDragSuccessListener;
private boolean mHeadsupDisappearRunning; private boolean mHeadsupDisappearRunning;
private View mChildAfterViewWhenDismissed; private View mChildAfterViewWhenDismissed;
private View mGroupParentWhenDismissed; private View mGroupParentWhenDismissed;
@@ -1083,6 +1087,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mLongPressListener = longPressListener; mLongPressListener = longPressListener;
} }
public void setDragController(ExpandableNotificationRowDragController dragController) {
mDragController = dragController;
}
@Override @Override
public void setOnClickListener(@Nullable OnClickListener l) { public void setOnClickListener(@Nullable OnClickListener l) {
super.setOnClickListener(l); super.setOnClickListener(l);
@@ -1329,6 +1337,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void dismiss(boolean refocusOnDismiss) { public void dismiss(boolean refocusOnDismiss) {
super.dismiss(refocusOnDismiss); super.dismiss(refocusOnDismiss);
setLongPressListener(null); setLongPressListener(null);
setDragController(null);
mGroupParentWhenDismissed = mNotificationParent; mGroupParentWhenDismissed = mNotificationParent;
mChildAfterViewWhenDismissed = null; mChildAfterViewWhenDismissed = null;
mEntry.getIcons().getStatusBarIcon().setDismissed(); mEntry.getIcons().getStatusBarIcon().setDismissed();
@@ -1637,6 +1646,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} }
onHeightReset(); onHeightReset();
requestLayout(); requestLayout();
setTargetPoint(null);
} }
public void showFeedbackIcon(boolean show, Pair<Integer, Integer> resIds) { public void showFeedbackIcon(boolean show, Pair<Integer, Integer> resIds) {
@@ -1727,6 +1738,29 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mTranslateableViews.remove(mGutsStub); 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() { private void doLongClickCallback() {
doLongClickCallback(getWidth() / 2, getHeight() / 2); doLongClickCallback(getWidth() / 2, getHeight() / 2);
} }
@@ -3254,6 +3288,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
boolean onLongPress(View v, int x, int y, MenuItem item); 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 * 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 androidx.annotation.NonNull;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -85,6 +86,8 @@ public class ExpandableNotificationRowController implements NodeController {
private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
private final Optional<BubblesManager> mBubblesManagerOptional; private final Optional<BubblesManager> mBubblesManagerOptional;
private final ExpandableNotificationRowDragController mDragController;
@Inject @Inject
public ExpandableNotificationRowController( public ExpandableNotificationRowController(
ExpandableNotificationRow view, ExpandableNotificationRow view,
@@ -109,7 +112,8 @@ public class ExpandableNotificationRowController implements NodeController {
FalsingManager falsingManager, FalsingManager falsingManager,
FalsingCollector falsingCollector, FalsingCollector falsingCollector,
PeopleNotificationIdentifier peopleNotificationIdentifier, PeopleNotificationIdentifier peopleNotificationIdentifier,
Optional<BubblesManager> bubblesManagerOptional) { Optional<BubblesManager> bubblesManagerOptional,
ExpandableNotificationRowDragController dragController) {
mView = view; mView = view;
mListContainer = listContainer; mListContainer = listContainer;
mActivatableNotificationViewController = activatableNotificationViewController; mActivatableNotificationViewController = activatableNotificationViewController;
@@ -134,6 +138,7 @@ public class ExpandableNotificationRowController implements NodeController {
mFalsingCollector = falsingCollector; mFalsingCollector = falsingCollector;
mPeopleNotificationIdentifier = peopleNotificationIdentifier; mPeopleNotificationIdentifier = peopleNotificationIdentifier;
mBubblesManagerOptional = bubblesManagerOptional; mBubblesManagerOptional = bubblesManagerOptional;
mDragController = dragController;
} }
/** /**
@@ -164,6 +169,10 @@ public class ExpandableNotificationRowController implements NodeController {
); );
mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
if (mAllowLongPress) { if (mAllowLongPress) {
if (mView.getResources().getBoolean(R.bool.config_notificationToContents)) {
mView.setDragController(mDragController);
}
mView.setLongPressListener((v, x, y, item) -> { mView.setLongPressListener((v, x, y, item) -> {
if (mView.isSummaryWithChildren()) { if (mView.isSummaryWithChildren()) {
mView.expandNotification(); 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 int mBarState;
private HeadsUpAppearanceController mHeadsUpAppearanceController; private HeadsUpAppearanceController mHeadsUpAppearanceController;
private View mLongPressedView;
private final NotificationListContainerImpl mNotificationListContainer = private final NotificationListContainerImpl mNotificationListContainer =
new NotificationListContainerImpl(); new NotificationListContainerImpl();
@@ -491,6 +493,11 @@ public class NotificationStackScrollLayoutController {
return child; return child;
} }
@Override
public void onLongPressSent(View v) {
mLongPressedView = v;
}
@Override @Override
public void onBeginDrag(View v) { public void onBeginDrag(View v) {
mFalsingCollector.onNotificationStartDismissing(); mFalsingCollector.onNotificationStartDismissing();
@@ -1435,6 +1442,10 @@ public class NotificationStackScrollLayoutController {
return mDynamicPrivacyController.isInLockedDownShade(); return mDynamicPrivacyController.isInLockedDownShade();
} }
public boolean isLongPressInProgress() {
return mLongPressedView != null;
}
/** /**
* Set the dimmed state for all of the notification views. * Set the dimmed state for all of the notification views.
*/ */
@@ -1689,17 +1700,23 @@ public class NotificationStackScrollLayoutController {
mView.handleEmptySpaceClick(ev); mView.handleEmptySpaceClick(ev);
NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
boolean longPressWantsIt = false;
if (mLongPressedView != null) {
longPressWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
}
boolean expandWantsIt = false; boolean expandWantsIt = false;
if (!mSwipeHelper.isSwiping() if (mLongPressedView == null && !mSwipeHelper.isSwiping()
&& !mView.getOnlyScrollingInThisMotion() && guts == null) { && !mView.getOnlyScrollingInThisMotion() && guts == null) {
expandWantsIt = mView.getExpandHelper().onInterceptTouchEvent(ev); expandWantsIt = mView.getExpandHelper().onInterceptTouchEvent(ev);
} }
boolean scrollWantsIt = false; boolean scrollWantsIt = false;
if (!mSwipeHelper.isSwiping() && !mView.isExpandingNotification()) { if (mLongPressedView == null && !mSwipeHelper.isSwiping()
&& !mView.isExpandingNotification()) {
scrollWantsIt = mView.onInterceptTouchEventScroll(ev); scrollWantsIt = mView.onInterceptTouchEventScroll(ev);
} }
boolean swipeWantsIt = false; boolean swipeWantsIt = false;
if (!mView.isBeingDragged() if (mLongPressedView == null && !mView.isBeingDragged()
&& !mView.isExpandingNotification() && !mView.isExpandingNotification()
&& !mView.getExpandedInThisMotion() && !mView.getExpandedInThisMotion()
&& !mView.getOnlyScrollingInThisMotion() && !mView.getOnlyScrollingInThisMotion()
@@ -1727,7 +1744,7 @@ public class NotificationStackScrollLayoutController {
InteractionJankMonitor.getInstance().begin(mView, InteractionJankMonitor.getInstance().begin(mView,
CUJ_NOTIFICATION_SHADE_SCROLL_FLING); CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
} }
return swipeWantsIt || scrollWantsIt || expandWantsIt; return swipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt;
} }
@Override @Override
@@ -1736,11 +1753,15 @@ public class NotificationStackScrollLayoutController {
boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
|| ev.getActionMasked() == MotionEvent.ACTION_UP; || ev.getActionMasked() == MotionEvent.ACTION_UP;
mView.handleEmptySpaceClick(ev); mView.handleEmptySpaceClick(ev);
boolean longPressWantsIt = false;
if (guts != null && mLongPressedView != null) {
longPressWantsIt = mSwipeHelper.onTouchEvent(ev);
}
boolean expandWantsIt = false; boolean expandWantsIt = false;
boolean onlyScrollingInThisMotion = mView.getOnlyScrollingInThisMotion(); boolean onlyScrollingInThisMotion = mView.getOnlyScrollingInThisMotion();
boolean expandingNotification = mView.isExpandingNotification(); boolean expandingNotification = mView.isExpandingNotification();
if (mView.getIsExpanded() && !mSwipeHelper.isSwiping() && !onlyScrollingInThisMotion if (mLongPressedView == null && mView.getIsExpanded()
&& guts == null) { && !mSwipeHelper.isSwiping() && !onlyScrollingInThisMotion && guts == null) {
ExpandHelper expandHelper = mView.getExpandHelper(); ExpandHelper expandHelper = mView.getExpandHelper();
if (isCancelOrUp) { if (isCancelOrUp) {
expandHelper.onlyObserveMovements(false); expandHelper.onlyObserveMovements(false);
@@ -1754,12 +1775,12 @@ public class NotificationStackScrollLayoutController {
} }
} }
boolean scrollerWantsIt = false; boolean scrollerWantsIt = false;
if (mView.isExpanded() && !mSwipeHelper.isSwiping() && !expandingNotification if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
&& !mView.getDisallowScrollingInThisMotion()) { && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
scrollerWantsIt = mView.onScrollTouch(ev); scrollerWantsIt = mView.onScrollTouch(ev);
} }
boolean horizontalSwipeWantsIt = false; boolean horizontalSwipeWantsIt = false;
if (!mView.isBeingDragged() if (mLongPressedView == null && !mView.isBeingDragged()
&& !expandingNotification && !expandingNotification
&& !mView.getExpandedInThisMotion() && !mView.getExpandedInThisMotion()
&& !onlyScrollingInThisMotion && !onlyScrollingInThisMotion
@@ -1785,7 +1806,7 @@ public class NotificationStackScrollLayoutController {
mView.setCheckForLeaveBehind(true); mView.setCheckForLeaveBehind(true);
} }
traceJankOnTouchEvent(ev.getActionMasked(), scrollerWantsIt); traceJankOnTouchEvent(ev.getActionMasked(), scrollerWantsIt);
return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt; return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || longPressWantsIt;
} }
private void traceJankOnTouchEvent(int action, boolean scrollerWantsIt) { private void traceJankOnTouchEvent(int action, boolean scrollerWantsIt) {

View File

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

View File

@@ -3947,7 +3947,9 @@ public class NotificationPanelViewController extends PanelViewController {
if (mStatusBar.isBouncerShowing()) { if (mStatusBar.isBouncerShowing()) {
return true; 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, 1);
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1); mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
return true; return true;
@@ -3999,6 +4001,7 @@ public class NotificationPanelViewController extends PanelViewController {
return true; return true;
} }
if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp() if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
&& !mNotificationStackScrollLayoutController.isLongPressInProgress()
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1); 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.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 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.notification.row.OnUserInteractionCallback;
import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -402,6 +403,53 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mIsCollapsingToShowActivityOverLockscreen = false; 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) { private void expandBubbleStackOnMainThread(NotificationEntry entry) {
if (!mBubblesManagerOptional.isPresent()) { if (!mBubblesManagerOptional.isPresent()) {
return; 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 FalsingManagerFake(),
new FalsingCollectorFake(), new FalsingCollectorFake(),
mPeopleNotificationIdentifier, mPeopleNotificationIdentifier,
Optional.of(mock(BubblesManager.class)) Optional.of(mock(BubblesManager.class)),
mock(ExpandableNotificationRowDragController.class)
)); ));
when(mNotificationRowComponentBuilder.activatableNotificationView(any())) when(mNotificationRowComponentBuilder.activatableNotificationView(any()))