Merge "Expose notification guts on menu shown"

This commit is contained in:
Evan Laird
2019-04-04 15:33:53 +00:00
committed by Android (Google) Code Review
10 changed files with 189 additions and 37 deletions

View File

@@ -14,7 +14,9 @@
package com.android.systemui.plugins.statusbar;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Point;
import android.service.notification.StatusBarNotification;
import android.view.MotionEvent;
import android.view.View;
@@ -84,6 +86,38 @@ public interface NotificationMenuRowPlugin extends Plugin {
public void setMenuItems(ArrayList<MenuItem> items);
/**
* If this returns {@code true}, then the menu row will bind and fade in the notification guts
* view for the menu item it holds.
*
* @see #menuItemToExposeOnSnap()
* @return whether or not to immediately expose the notification guts
*/
default boolean shouldShowGutsOnSnapOpen() {
return false;
}
/**
* When #shouldShowGutsOnExpose is true, this method must return the menu item to expose on
* #onSnapOpen. Otherwise we will fall back to the default behavior of fading in the menu row
*
* @return the {@link MenuItem} containing the NotificationGuts that should be exposed
*/
@Nullable
default MenuItem menuItemToExposeOnSnap() {
return null;
}
/**
* Get the origin for the circular reveal animation when expanding the notification guts. Only
* used when #shouldShowGutsOnSnapOpen is true
* @return the x,y coordinates for the start of the animation
*/
@Nullable
default Point getRevealAnimationOrigin() {
return new Point(0, 0);
}
public void setMenuClickListener(OnMenuEventListener listener);
public void setAppName(String appName);

View File

@@ -265,7 +265,9 @@ public class SwipeHelper implements Gefingerpoken {
public boolean onInterceptTouchEvent(final MotionEvent ev) {
if (mCurrView instanceof ExpandableNotificationRow) {
NotificationMenuRowPlugin nmr = ((ExpandableNotificationRow) mCurrView).getProvider();
mMenuRowIntercepting = nmr.onInterceptTouchEvent(mCurrView, ev);
if (nmr != null) {
mMenuRowIntercepting = nmr.onInterceptTouchEvent(mCurrView, ev);
}
}
final int action = ev.getAction();
@@ -487,6 +489,7 @@ public class SwipeHelper implements Gefingerpoken {
mSnappingChild = false;
if (!wasCancelled) {
updateSwipeProgressFromOffset(animView, canBeDismissed);
onChildSnappedBack(animView, targetLeft);
mCallback.onChildSnappedBack(animView, targetLeft);
}
}
@@ -499,6 +502,13 @@ public class SwipeHelper implements Gefingerpoken {
anim.start();
}
/**
* Give the swipe helper itself a chance to do something on snap back so NSSL doesn't have
* to tell us what to do
*/
protected void onChildSnappedBack(View animView, float targetLeft) {
}
/**
* Called to update the snap back animation.
*/

View File

@@ -1147,10 +1147,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) {
boolean existed = mMenuRow.getMenuView() != null;
boolean existed = mMenuRow != null && mMenuRow.getMenuView() != null;
if (existed) {
removeView(mMenuRow.getMenuView());
}
if (plugin == null) {
return;
}
mMenuRow = plugin;
if (mMenuRow.shouldUseDefaultMenuItems()) {
ArrayList<MenuItem> items = new ArrayList<>();
@@ -1173,7 +1176,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
/**
* Get a handle to a NotificationMenuRowPlugin whose menu view has been added to our hierarchy,
* or null if there is no menu row
*
* @return a {@link NotificationMenuRowPlugin}, or null
*/
@Nullable
public NotificationMenuRowPlugin createMenu() {
if (mMenuRow == null) {
return null;
}
if (mMenuRow.getMenuView() == null) {
mMenuRow.createMenu(this, mStatusBarNotification);
mMenuRow.setAppName(mAppName);
@@ -1184,6 +1198,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mMenuRow;
}
@Nullable
public NotificationMenuRowPlugin getProvider() {
return mMenuRow;
}
@@ -1211,7 +1226,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mGuts.setVisibility(oldGuts.getVisibility());
addView(mGuts, index);
}
View oldMenu = mMenuRow.getMenuView();
View oldMenu = mMenuRow == null ? null : mMenuRow.getMenuView();
if (oldMenu != null) {
int menuIndex = indexOfChild(oldMenu);
removeView(oldMenu);
@@ -1230,7 +1245,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mMenuRow.getMenuView() != null) {
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
mMenuRow.onConfigurationChanged();
}
}
@@ -1728,7 +1743,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void setAppOpsOnClickListener(ExpandableNotificationRow.OnAppOpsClickListener l) {
mOnAppOpsClickListener = v -> {
createMenu();
MenuItem menuItem = getProvider().getAppOpsMenuItem(mContext);
NotificationMenuRowPlugin provider = getProvider();
if (provider == null) {
return;
}
MenuItem menuItem = provider.getAppOpsMenuItem(mContext);
if (menuItem != null) {
l.onClick(this, v.getWidth() / 2, v.getHeight() / 2, menuItem);
}
@@ -1790,7 +1809,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void doLongClickCallback(int x, int y) {
createMenu();
MenuItem menuItem = getProvider().getLongpressMenuItem(mContext);
NotificationMenuRowPlugin provider = getProvider();
MenuItem menuItem = null;
if (provider != null) {
menuItem = provider.getLongpressMenuItem(mContext);
}
doLongClickCallback(x, y, menuItem);
}
@@ -1844,7 +1867,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
getEntry().expandedIcon.setScrollX(0);
}
mMenuRow.resetMenu();
if (mMenuRow != null) {
mMenuRow.resetMenu();
}
}
void onGutsOpened() {
@@ -1921,7 +1946,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
getEntry().expandedIcon.setScrollX((int) -translationX);
}
if (mMenuRow.getMenuView() != null) {
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
mMenuRow.onParentTranslationUpdate(translationX);
}
}
@@ -1973,7 +1998,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mNotificationTranslationFinished = true;
}
if (!cancelled && leftTarget == 0) {
mMenuRow.resetMenu();
if (mMenuRow != null) {
mMenuRow.resetMenu();
}
mTranslateAnim = null;
}
}
@@ -1982,7 +2009,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return translateAnim;
}
public void inflateGuts() {
void ensureGutsInflated() {
if (mGuts == null) {
mGutsStub.inflate();
}
@@ -2438,7 +2465,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (intrinsicBefore != getIntrinsicHeight() && intrinsicBefore != 0) {
notifyHeightChanged(true /* needsAnimation */);
}
if (mMenuRow.getMenuView() != null) {
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
mMenuRow.onParentHeightUpdate();
}
updateContentShiftHeight();
@@ -2641,7 +2668,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public long performRemoveAnimation(long duration, long delay, float translationDirection,
boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable,
AnimatorListenerAdapter animationListener) {
if (mMenuRow.isMenuVisible()) {
if (mMenuRow != null && mMenuRow.isMenuVisible()) {
Animator anim = getTranslateViewAnimator(0f, null /* listener */);
if (anim != null) {
anim.addListener(new AnimatorListenerAdapter() {
@@ -2712,7 +2739,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mGuts != null) {
mGuts.setActualHeight(height);
}
if (mMenuRow.getMenuView() != null) {
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
mMenuRow.onParentHeightUpdate();
}
}
@@ -2977,8 +3004,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
default:
if (action == R.id.action_snooze) {
NotificationMenuRowPlugin provider = getProvider();
if (provider == null) {
if (provider == null && mMenuRow != null) {
provider = createMenu();
} else {
return false;
}
MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext());
if (snoozeMenu != null) {
@@ -3109,7 +3138,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/** Sets whether dismiss gestures are right-to-left (instead of left-to-right). */
public void setDismissRtl(boolean dismissRtl) {
mMenuRow.setDismissRtl(dismissRtl);
if (mMenuRow != null) {
mMenuRow.setDismissRtl(dismissRtl);
}
}
private static class NotificationViewState extends ExpandableViewState {

View File

@@ -114,7 +114,7 @@ public class NotificationGuts extends FrameLayout {
public void onHeightChanged(NotificationGuts guts);
}
interface OnSettingsClickListener {
private interface OnSettingsClickListener {
void onClick(View v, int appUid);
}
@@ -271,6 +271,8 @@ public class NotificationGuts extends FrameLayout {
double horz = Math.max(getWidth() - x, x);
double vert = Math.max(getHeight() - y, y);
float r = (float) Math.hypot(horz, vert);
// Make sure we'll be visible after the circular reveal
setAlpha(1f);
// Circular reveal originating at (x, y)
Animator a = ViewAnimationUtils.createCircularReveal(this, x, y, 0, r);
a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);

View File

@@ -165,8 +165,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
}
}
public boolean bindGuts(final ExpandableNotificationRow row) {
row.inflateGuts();
private boolean bindGuts(final ExpandableNotificationRow row) {
row.ensureGutsInflated();
return bindGuts(row, mGutsMenuItem);
}
@@ -386,7 +386,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
return false;
}
row.inflateGuts();
row.ensureGutsInflated();
NotificationGuts guts = row.getGuts();
mNotificationGutsExposed = guts;
if (!bindGuts(row, menuItem)) {

View File

@@ -25,6 +25,7 @@ import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
@@ -79,6 +80,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
private OnMenuEventListener mMenuListener;
private boolean mDismissRtl;
private boolean mIsForeground;
private final boolean mIsUsingNewInterruptionModel;
private ValueAnimator mFadeAnimator;
private boolean mAnimating;
@@ -116,6 +118,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
mHandler = new Handler(Looper.getMainLooper());
mLeftMenuItems = new ArrayList<>();
mRightMenuItems = new ArrayList<>();
mIsUsingNewInterruptionModel = NotificationUtils.useNewInterruptionModel(mContext);
}
@Override
@@ -252,13 +255,13 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
mSnoozeItem = createSnoozeItem(mContext);
}
mAppOpsItem = createAppOpsItem(mContext);
if (NotificationUtils.useNewInterruptionModel(mContext)) {
if (mIsUsingNewInterruptionModel) {
mInfoItem = createInfoItem(mContext, !mParent.getEntry().isHighPriority());
} else {
mInfoItem = createInfoItem(mContext);
}
if (!NotificationUtils.useNewInterruptionModel(mContext)) {
if (!mIsUsingNewInterruptionModel) {
if (!isForeground) {
mRightMenuItems.add(mSnoozeItem);
}
@@ -268,10 +271,6 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
} else {
ArrayList<MenuItem> menuItems = mDismissRtl ? mLeftMenuItems : mRightMenuItems;
menuItems.add(mInfoItem);
menuItems.add(mAppOpsItem);
if (!isForeground) {
menuItems.add(mSnoozeItem);
}
}
populateMenuViews();
@@ -617,6 +616,29 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
// TODO -- handle / allow custom menu items!
}
@Override
public boolean shouldShowGutsOnSnapOpen() {
return mIsUsingNewInterruptionModel;
}
@Override
public MenuItem menuItemToExposeOnSnap() {
return mIsUsingNewInterruptionModel ? mInfoItem : null;
}
@Override
public Point getRevealAnimationOrigin() {
View v = mInfoItem.getMenuView();
int menuX = v.getLeft() + v.getPaddingLeft() + (v.getWidth() / 2);
int menuY = v.getTop() + v.getPaddingTop() + (v.getHeight() / 2);
if (isMenuOnLeft()) {
return new Point(menuX, menuY);
} else {
menuX = mParent.getRight() - menuX;
return new Point(menuX, menuY);
}
}
static MenuItem createSnoozeItem(Context context) {
Resources res = context.getResources();
NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(context)

View File

@@ -38,6 +38,7 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
@@ -1691,8 +1692,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
ExpandableNotificationRow child = entry.getRow();
boolean animate = mIsExpanded || isPinnedHeadsUp(child);
// If the child is showing the notification menu snap to that
float targetLeft = child.getProvider().isMenuVisible() ? child.getTranslation() : 0;
mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
if (child.getProvider() != null) {
float targetLeft = child.getProvider().isMenuVisible() ? child.getTranslation() : 0;
mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
}
}
@Override
@@ -6143,8 +6146,24 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
.setCategory(MetricsEvent.ACTION_REVEAL_GEAR)
.setType(MetricsEvent.TYPE_ACTION));
mHeadsUpManager.setMenuShown(notificationRow.getEntry(), true);
mSwipeHelper.onMenuShown(row);
// Check to see if we want to go directly to the notfication guts
NotificationMenuRowPlugin provider = notificationRow.getProvider();
if (provider.shouldShowGutsOnSnapOpen()) {
MenuItem item = provider.menuItemToExposeOnSnap();
if (item != null) {
Point origin = provider.getRevealAnimationOrigin();
mGutsManager.openGuts(row, origin.x, origin.y, item);
} else {
Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no "
+ "menu item in menuItemtoExposeOnSnap. Skipping.");
}
// Close the menu row since we went directly to the guts
resetExposedMenuView(false, true);
}
}
mSwipeHelper.onMenuShown(row);
}
};
@@ -6275,11 +6294,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
mAmbientState.onDragFinished(animView);
updateContinuousShadowDrawing();
updateContinuousBackgroundDrawing();
NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
if (menuRow != null && targetLeft == 0) {
menuRow.resetMenu();
mSwipeHelper.clearCurrentMenuRow();
}
}
@Override

View File

@@ -93,7 +93,17 @@ class NotificationSwipeHelper extends SwipeHelper
protected Handler getHandler() { return mHandler; }
@VisibleForTesting
protected Runnable getFalsingCheck() { return mFalsingCheck; };
protected Runnable getFalsingCheck() {
return mFalsingCheck;
}
@Override
protected void onChildSnappedBack(View animView, float targetLeft) {
if (mCurrMenuRow != null && targetLeft == 0) {
mCurrMenuRow.resetMenu();
clearCurrentMenuRow();
}
}
@Override
public void onDownUpdate(View currView, MotionEvent ev) {
@@ -117,8 +127,10 @@ class NotificationSwipeHelper extends SwipeHelper
protected void initializeRow(ExpandableNotificationRow row) {
if (row.getEntry().hasFinishedInitialization()) {
mCurrMenuRow = row.createMenu();
mCurrMenuRow.setMenuClickListener(mMenuListener);
mCurrMenuRow.onTouchStart();
if (mCurrMenuRow != null) {
mCurrMenuRow.setMenuClickListener(mMenuListener);
mCurrMenuRow.onTouchStart();
}
}
}

View File

@@ -186,7 +186,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
when(row.getWindowToken()).thenReturn(new Binder());
when(row.getGuts()).thenReturn(guts);
doNothing().when(row).inflateGuts();
doNothing().when(row).ensureGutsInflated();
NotificationEntry realEntry = realRow.getEntry();
NotificationEntry entry = spy(realEntry);

View File

@@ -14,6 +14,8 @@
package com.android.systemui.statusbar.notification.row;
import static android.provider.Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -26,6 +28,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.NotificationChannel;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -39,6 +42,7 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -61,6 +65,13 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
when(mRow.getEntry()).thenReturn(entry);
}
@After
public void tearDown() {
Settings.Secure.putInt(mContext.getContentResolver(),
NOTIFICATION_NEW_INTERRUPTION_MODEL, 0);
}
@Test
public void testAttachDetach() {
NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
@@ -89,6 +100,9 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
@Test
public void testNoAppOpsInSlowSwipe() {
Settings.Secure.putInt(mContext.getContentResolver(),
NOTIFICATION_NEW_INTERRUPTION_MODEL, 0);
NotificationMenuRow row = new NotificationMenuRow(mContext);
row.createMenu(mRow, null);
@@ -97,6 +111,19 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
assertEquals(2, container.getChildCount());
}
@Test
public void testNoAppOpsInSlowSwipe_newInterruptionModel() {
Settings.Secure.putInt(mContext.getContentResolver(),
NOTIFICATION_NEW_INTERRUPTION_MODEL, 1);
NotificationMenuRow row = new NotificationMenuRow(mContext);
row.createMenu(mRow, null);
ViewGroup container = (ViewGroup) row.getMenuView();
// in the new interruption model there is only the blocking item
assertEquals(1, container.getChildCount());
}
@Test
public void testIsSnappedAndOnSameSide() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));