diff options
14 files changed, 851 insertions, 762 deletions
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java new file mode 100644 index 000000000000..8dde35782be5 --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowPlugin.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017 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.plugins.statusbar; + +import android.content.Context; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; + +import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.annotations.DependsOn; +import com.android.systemui.plugins.annotations.ProvidesInterface; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; + +@ProvidesInterface(action = NotificationMenuRowPlugin.ACTION, + version = NotificationMenuRowPlugin.VERSION) +@DependsOn(target = OnMenuEventListener.class) +@DependsOn(target = MenuItem.class) +@DependsOn(target = NotificationSwipeActionHelper.class) +@DependsOn(target = SnoozeOption.class) +public interface NotificationMenuRowPlugin extends Plugin { + + public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW"; + public static final int VERSION = 1; + + @ProvidesInterface(version = OnMenuEventListener.VERSION) + public interface OnMenuEventListener { + public static final int VERSION = 1; + public void onMenuClicked(View row, int x, int y, MenuItem menu); + + public void onMenuReset(View row); + + public void onMenuShown(View row); + } + + @ProvidesInterface(version = MenuItem.VERSION) + public interface MenuItem { + public static final int VERSION = 1; + public View getMenuView(); + + public View getGutsView(); + + public String getContentDescription(); + } + + /** + * @return a list of items to populate the menu 'behind' a notification. + */ + public ArrayList<MenuItem> getMenuItems(Context context); + + /** + * @return the {@link MenuItem} to display when a notification is long pressed. + */ + public MenuItem getLongpressMenuItem(Context context); + + public void setMenuItems(ArrayList<MenuItem> items); + + public void setMenuClickListener(OnMenuEventListener listener); + + public void setSwipeActionHelper(NotificationSwipeActionHelper listener); + + public void setAppName(String appName); + + public void createMenu(ViewGroup parent); + + public View getMenuView(); + + public boolean isMenuVisible(); + + public void resetMenu(); + + public void onTranslationUpdate(float translation); + + public void onHeightUpdate(); + + public boolean onTouchEvent(View view, MotionEvent ev, float velocity); + + public default boolean useDefaultMenuItems() { + return false; + } +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java deleted file mode 100644 index 529c42154098..000000000000 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationMenuRowProvider.java +++ /dev/null @@ -1,95 +0,0 @@ - -package com.android.systemui.plugins.statusbar; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.service.notification.SnoozeCriterion; -import android.service.notification.StatusBarNotification; -import android.view.View; - -import java.util.ArrayList; - -import com.android.systemui.plugins.Plugin; -import com.android.systemui.plugins.annotations.ProvidesInterface; - -@ProvidesInterface(action = NotificationMenuRowProvider.ACTION, - version = NotificationMenuRowProvider.VERSION) -public interface NotificationMenuRowProvider extends Plugin { - - public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW"; - - public static final int VERSION = 1; - - /** - * Returns a list of items to populate the menu 'behind' a notification. - */ - public ArrayList<MenuItem> getMenuItems(Context context); - - public interface OnMenuClickListener { - public void onMenuClicked(View row, int x, int y, MenuItem menu); - - public void onMenuReset(View row); - } - - public interface GutsInteractionListener { - public void onInteraction(View view); - - public void closeGuts(View view); - } - - public interface GutsContent { - public void setInteractionListener(GutsInteractionListener listener); - - public View getContentView(); - - public boolean handleCloseControls(boolean save); - - public boolean willBeRemoved(); - } - - public interface SnoozeGutsContent extends GutsContent { - public void setSnoozeListener(SnoozeListener listener); - - public void setStatusBarNotification(StatusBarNotification sbn); - } - - public interface SnoozeListener { - public void snoozeNotification(StatusBarNotification sbn, SnoozeOption snoozeOption); - } - - public static class MenuItem { - public Drawable icon; - public String menuDescription; - public View menuView; - public GutsContent gutsContent; - - public MenuItem(Drawable i, String s, GutsContent content) { - icon = i; - menuDescription = s; - gutsContent = content; - } - - public View getGutsView() { - return gutsContent.getContentView(); - } - - public boolean onTouch(View v, int x, int y) { - return false; - } - } - - public static class SnoozeOption { - public SnoozeCriterion criterion; - public int snoozeForMinutes; - public CharSequence description; - public CharSequence confirmation; - - public SnoozeOption(SnoozeCriterion crit, int minsToSnoozeFor, CharSequence desc, - CharSequence confirm) { - criterion = crit; - snoozeForMinutes = minsToSnoozeFor; - description = desc; - confirmation = confirm; - } - } -} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java new file mode 100644 index 000000000000..4ce1e36bde3a --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 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.plugins.statusbar; + +import com.android.systemui.plugins.annotations.DependsOn; +import com.android.systemui.plugins.annotations.ProvidesInterface; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; + +import android.service.notification.SnoozeCriterion; +import android.service.notification.StatusBarNotification; +import android.view.MotionEvent; +import android.view.View; + +@ProvidesInterface(version = NotificationSwipeActionHelper.VERSION) +@DependsOn(target = SnoozeOption.class) +public interface NotificationSwipeActionHelper { + public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_SWIPE_ACTION"; + + public static final int VERSION = 1; + + /** + * Call this to dismiss a notification. + */ + public void dismiss(View animView, float velocity); + + /** + * Call this to snap a notification to provided {@code targetLeft}. + */ + public void snap(View animView, float velocity, float targetLeft); + + /** + * Call this to snooze a notification based on the provided {@link SnoozeOption}. + */ + public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption); + + public float getMinDismissVelocity(); + + public boolean isDismissGesture(MotionEvent ev); + + public boolean swipedFarEnough(float translation, float viewSize); + + public boolean swipedFastEnough(float translation, float velocity); + + @ProvidesInterface(version = SnoozeOption.VERSION) + public static class SnoozeOption { + public static final int VERSION = 1; + public int snoozeForMinutes; + public SnoozeCriterion criterion; + public CharSequence description; + public CharSequence confirmation; + + public SnoozeOption(SnoozeCriterion crit, int minsToSnoozeFor, CharSequence desc, + CharSequence confirm) { + criterion = crit; + snoozeForMinutes = minsToSnoozeFor; + description = desc; + confirmation = confirm; + } + } +} diff --git a/packages/SystemUI/res/layout/notification_menu_row.xml b/packages/SystemUI/res/layout/notification_menu_row.xml deleted file mode 100644 index 12bcf8176561..000000000000 --- a/packages/SystemUI/res/layout/notification_menu_row.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2016, The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<com.android.systemui.statusbar.NotificationMenuRow - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:systemui="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:visibility="invisible"/>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml index d62cc184889c..7f3708788d95 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_row.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml @@ -23,13 +23,7 @@ android:clickable="true" > - <ViewStub - android:layout="@layout/notification_menu_row" - android:id="@+id/menu_row_stub" - android:inflatedId="@+id/notification_menu_row" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> + <!-- Menu displayed behind notification added here programmatically --> <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundNormal" diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index a95713fb017e..5a04108df807 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -32,9 +32,10 @@ import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import com.android.systemui.classifier.FalsingManager; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; +import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.FlingAnimationUtils; -import com.android.systemui.statusbar.NotificationMenuRow; import java.util.HashMap; @@ -267,7 +268,7 @@ public class SwipeHelper implements Gefingerpoken { mCurrView = mCallback.getChildAtPosition(ev); if (mCurrView != null) { - onDownUpdate(mCurrView); + onDownUpdate(mCurrView, ev); mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView); mVelocityTracker.addMovement(ev); mInitialTouchPos = getPos(ev); @@ -285,8 +286,12 @@ public class SwipeHelper implements Gefingerpoken { mCurrView.getLocationOnScreen(mTmpPos); final int x = (int) ev.getRawX() - mTmpPos[0]; final int y = (int) ev.getRawY() - mTmpPos[1]; - mLongPressListener.onLongPress(mCurrView, x, y, - NotificationMenuRow.getLongpressMenuItem(mContext)); + MenuItem menuItem = null; + if (mCurrView instanceof ExpandableNotificationRow) { + menuItem = ((ExpandableNotificationRow) mCurrView) + .getProvider().getLongpressMenuItem(mContext); + } + mLongPressListener.onLongPress(mCurrView, x, y, menuItem); } } }; @@ -479,14 +484,14 @@ public class SwipeHelper implements Gefingerpoken { /** * Called when there's a down event. */ - public void onDownUpdate(View currView) { + public void onDownUpdate(View currView, MotionEvent ev) { // Do nothing } /** * Called on a move event. */ - protected void onMoveUpdate(View view, float totalTranslation, float delta) { + protected void onMoveUpdate(View view, MotionEvent ev, float totalTranslation, float delta) { // Do nothing } @@ -580,7 +585,7 @@ public class SwipeHelper implements Gefingerpoken { setTranslation(mCurrView, mTranslation + delta); updateSwipeProgressFromOffset(mCurrView, mCanCurrViewBeDimissed); - onMoveUpdate(mCurrView, mTranslation + delta, delta); + onMoveUpdate(mCurrView, ev, mTranslation + delta, delta); } break; case MotionEvent.ACTION_UP: @@ -635,7 +640,7 @@ public class SwipeHelper implements Gefingerpoken { return DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView); } - protected boolean isDismissGesture(MotionEvent ev) { + public boolean isDismissGesture(MotionEvent ev) { boolean falsingDetected = mCallback.isAntiFalsingNeeded(); if (mFalsingManager.isClassiferEnabled()) { falsingDetected = falsingDetected && mFalsingManager.isFalseTouch(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 00968ee8d0e4..9176f57c35cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -43,6 +43,7 @@ import android.view.ViewStub; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Chronometer; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RemoteViews; @@ -50,10 +51,15 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.NotificationColorUtil; import com.android.internal.widget.CachingIconView; +import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.classifier.FalsingManager; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.plugins.PluginManager; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; +import com.android.systemui.statusbar.NotificationGuts.GutsContent; import com.android.systemui.statusbar.notification.HybridNotificationView; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationInflater; @@ -71,10 +77,13 @@ import com.android.systemui.statusbar.stack.StackScrollState; import java.util.ArrayList; import java.util.List; -public class ExpandableNotificationRow extends ActivatableNotificationView { +public class ExpandableNotificationRow extends ActivatableNotificationView + implements PluginListener<NotificationMenuRowPlugin> { private static final int DEFAULT_DIVIDER_ALPHA = 0x29; private static final int COLORED_DIVIDER_ALPHA = 0x7B; + private static final int MENU_VIEW_INDEX = 0; + private final NotificationInflater mNotificationInflater; private int mIconTransformContentShift; private int mIconTransformContentShiftNoIcon; @@ -129,7 +138,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private int mNotificationColor; private ExpansionLogger mLogger; private String mLoggingKey; - private NotificationMenuRow mMenuRow; private NotificationGuts mGuts; private NotificationData.Entry mEntry; private StatusBarNotification mStatusBarNotification; @@ -141,7 +149,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private boolean mChildrenExpanded; private boolean mIsSummaryWithChildren; private NotificationChildrenContainer mChildrenContainer; - private ViewStub mMenuRowStub; + private NotificationMenuRowPlugin mMenuRow; private ViewStub mGutsStub; private boolean mIsSystemChildExpanded; private boolean mIsPinned; @@ -422,7 +430,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public void setAppName(String appName) { mAppName = appName; - if (mMenuRow != null) { + if (mMenuRow != null && mMenuRow.getMenuView() != null) { mMenuRow.setAppName(mAppName); } } @@ -496,7 +504,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { @Override protected boolean handleSlideBack() { - if (mMenuRow != null && mMenuRow.isVisible()) { + if (mMenuRow != null && mMenuRow.isMenuVisible()) { animateTranslateNotification(0 /* targetLeft */); return true; } @@ -725,12 +733,65 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } public void setGutsView(MenuItem item) { - if (mGuts != null) { - item.gutsContent.setInteractionListener(mGuts); - mGuts.setGutsContent(item.gutsContent); + if (mGuts != null && item.getGutsView() instanceof GutsContent) { + ((GutsContent) item.getGutsView()).setGutsParent(mGuts); + mGuts.setGutsContent((GutsContent) item.getGutsView()); } } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + Dependency.get(PluginManager.class).addPluginListener(this, + NotificationMenuRowPlugin.class, false /* Allow multiple */); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + Dependency.get(PluginManager.class).removePluginListener(this); + } + + @Override + public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) { + boolean existed = mMenuRow.getMenuView() != null; + if (existed) { + removeView(mMenuRow.getMenuView()); + } + mMenuRow = plugin; + if (mMenuRow.useDefaultMenuItems()) { + mMenuRow.setMenuItems(NotificationMenuRow.getDefaultMenuItems(mContext)); + } + if (existed) { + createMenu(); + } + } + + @Override + public void onPluginDisconnected(NotificationMenuRowPlugin plugin) { + boolean existed = mMenuRow.getMenuView() != null; + mMenuRow = new NotificationMenuRow(mContext); // Back to default + if (existed) { + createMenu(); + } + } + + public NotificationMenuRowPlugin createMenu() { + if (mMenuRow.getMenuView() == null) { + mMenuRow.createMenu(this); + mMenuRow.setAppName(mAppName); + FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT); + addView(mMenuRow.getMenuView(), MENU_VIEW_INDEX, lp); + } + return mMenuRow; + } + + + public NotificationMenuRowPlugin getProvider() { + return mMenuRow; + } + public void onDensityOrFontScaleChanged() { initDimens(); if (mIsSummaryWithChildren) { @@ -747,17 +808,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mGuts.setVisibility(oldGuts.getVisibility()); addView(mGuts, index); } - if (mMenuRow != null) { - View oldMenu = mMenuRow; + View oldMenu = mMenuRow.getMenuView(); + if (oldMenu != null) { int menuIndex = indexOfChild(oldMenu); removeView(oldMenu); - mMenuRow = (NotificationMenuRow) LayoutInflater.from(mContext).inflate( - R.layout.notification_menu_row, this, false); - mMenuRow.setNotificationRowParent(ExpandableNotificationRow.this); + mMenuRow.createMenu(ExpandableNotificationRow.this); mMenuRow.setAppName(mAppName); - mMenuRow.setVisibility(oldMenu.getVisibility()); - addView(mMenuRow, menuIndex); - + addView(mMenuRow.getMenuView(), menuIndex); } for (NotificationContentView l : mLayouts) { l.reInflateViews(); @@ -1061,6 +1118,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { super(context, attrs); mFalsingManager = FalsingManager.getInstance(context); mNotificationInflater = new NotificationInflater(this); + mMenuRow = new NotificationMenuRow(mContext); initDimens(); } @@ -1113,15 +1171,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { l.setExpandClickListener(mExpandClickListener); l.setContainingNotification(this); } - mMenuRowStub = (ViewStub) findViewById(R.id.menu_row_stub); - mMenuRowStub.setOnInflateListener(new ViewStub.OnInflateListener() { - @Override - public void onInflate(ViewStub stub, View inflated) { - mMenuRow = (NotificationMenuRow) inflated; - mMenuRow.setNotificationRowParent(ExpandableNotificationRow.this); - mMenuRow.setAppName(mAppName); - } - }); mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub); mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() { @Override @@ -1145,13 +1194,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } }); - // Add the views that we translate to reveal the gear + // Add the views that we translate to reveal the menu mTranslateableViews = new ArrayList<View>(); for (int i = 0; i < getChildCount(); i++) { mTranslateableViews.add(getChildAt(i)); } // Remove views that don't translate - mTranslateableViews.remove(mMenuRowStub); mTranslateableViews.remove(mChildrenContainerStub); mTranslateableViews.remove(mGutsStub); } @@ -1166,9 +1214,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } } invalidateOutline(); - if (mMenuRow != null) { - mMenuRow.resetState(true /* notify */); - } + mMenuRow.resetMenu(); } public void animateTranslateNotification(final float leftTarget) { @@ -1194,8 +1240,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } } invalidateOutline(); - if (mMenuRow != null) { - mMenuRow.updateMenuAlpha(translationX, getMeasuredWidth()); + if (mMenuRow.getMenuView() != null) { + mMenuRow.onTranslationUpdate(translationX); } } @@ -1232,8 +1278,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { @Override public void onAnimationEnd(Animator anim) { - if (!cancelled && mMenuRow != null && leftTarget == 0) { - mMenuRow.resetState(true /* notify */); + if (!cancelled && leftTarget == 0) { + mMenuRow.resetMenu(); mTranslateAnim = null; } } @@ -1242,20 +1288,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return translateAnim; } - public float getSpaceForGear() { - if (mMenuRow != null) { - return mMenuRow.getSpaceForMenu(); - } - return 0; - } - - public NotificationMenuRow getSettingsRow() { - if (mMenuRow == null) { - mMenuRowStub.inflate(); - } - return mMenuRow; - } - public void inflateGuts() { if (mGuts == null) { mGutsStub.inflate(); @@ -1531,8 +1563,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); updateMaxHeights(); - if (mMenuRow != null) { - mMenuRow.updateVerticalLocation(); + if (mMenuRow.getMenuView() != null) { + mMenuRow.onHeightUpdate(); } updateContentShiftHeight(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java index fd1317e61436..2713f58272d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java @@ -51,8 +51,8 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.statusbar.stack.StackStateAnimator; import java.util.Set; @@ -60,8 +60,7 @@ import java.util.Set; /** * The guts of a notification revealed when performing a long press. */ -public class NotificationGuts extends FrameLayout - implements NotificationMenuRowProvider.GutsInteractionListener { +public class NotificationGuts extends FrameLayout { private static final String TAG = "NotificationGuts"; private static final long CLOSE_GUTS_DELAY = 8000; @@ -78,10 +77,35 @@ public class NotificationGuts extends FrameLayout private GutsContent mGutsContent; + public interface GutsContent { + + public void setGutsParent(NotificationGuts listener); + + /** + * @return the view to be shown in the notification guts. + */ + public View getContentView(); + + /** + * Called when the guts view have been told to close, typically after an outside + * interaction. Returning {@code true} here will prevent the guts view to close. + */ + public boolean handleCloseControls(boolean save); + + /** + * @return whether the notification associated with these guts is set to be removed. + */ + public boolean willBeRemoved(); + } + public interface OnGutsClosedListener { public void onGutsClosed(NotificationGuts guts); } + interface OnSettingsClickListener { + void onClick(View v, int appUid); + } + public NotificationGuts(Context context, AttributeSet attrs) { super(context, attrs); setWillNotDraw(false); @@ -163,10 +187,6 @@ public class NotificationGuts extends FrameLayout } } - interface OnSettingsClickListener { - void onClick(View v, int appUid); - } - public void closeControls(int x, int y, boolean save) { if (getWindowToken() == null) { if (mListener != null) { @@ -251,14 +271,4 @@ public class NotificationGuts extends FrameLayout public boolean isExposed() { return mExposed; } - - @Override - public void onInteraction(View view) { - resetFalsingCheck(); - } - - @Override - public void closeGuts(View view) { - closeControls(-1 /* x */, -1 /* y */, true /* notify */); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java index a9043e4c83bb..8d432c4767f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java @@ -53,8 +53,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener; import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener; import com.android.systemui.statusbar.stack.StackStateAnimator; @@ -65,7 +63,7 @@ import java.util.Set; /** * The guts of a notification revealed when performing a long press. */ -public class NotificationInfo extends LinearLayout implements GutsContent { +public class NotificationInfo extends LinearLayout implements NotificationGuts.GutsContent { private static final String TAG = "InfoGuts"; private INotificationManager mINotificationManager; @@ -79,7 +77,7 @@ public class NotificationInfo extends LinearLayout implements GutsContent { private View mChannelDisabledView; private Switch mChannelEnabledSwitch; - private GutsInteractionListener mGutsInteractionListener; + private NotificationGuts mGutsContainer; public NotificationInfo(Context context, AttributeSet attrs) { super(context, attrs); @@ -274,8 +272,8 @@ public class NotificationInfo extends LinearLayout implements GutsContent { // Callback when checked. mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (mGutsInteractionListener != null) { - mGutsInteractionListener.onInteraction(NotificationInfo.this); + if (mGutsContainer != null) { + mGutsContainer.resetFalsingCheck(); } updateSecondaryText(); }); @@ -300,8 +298,8 @@ public class NotificationInfo extends LinearLayout implements GutsContent { } @Override - public void setInteractionListener(GutsInteractionListener listener) { - mGutsInteractionListener = listener; + public void setGutsParent(NotificationGuts guts) { + mGutsContainer = guts; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java index 534a71936b5a..5055dda0d863 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java @@ -16,276 +16,449 @@ package com.android.systemui.statusbar; +import java.util.ArrayList; + +import com.android.systemui.Interpolators; +import com.android.systemui.R; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; +import com.android.systemui.statusbar.NotificationGuts.GutsContent; +import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; -import android.util.AttributeSet; +import android.os.Handler; import android.util.Log; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.widget.FrameLayout; +import android.widget.FrameLayout.LayoutParams; -import java.util.ArrayList; - -import com.android.systemui.Dependency; -import com.android.systemui.Interpolators; -import com.android.systemui.R; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.OnMenuClickListener; - -public class NotificationMenuRow extends FrameLayout - implements PluginListener<NotificationMenuRowProvider>, View.OnClickListener { +public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnClickListener { private static final int ICON_ALPHA_ANIM_DURATION = 200; + private static final long SHOW_MENU_DELAY = 60; + private static final long SWIPE_MENU_TIMING = 200; + + private static final int NOTIFICATION_INFO_INDEX = 1; private ExpandableNotificationRow mParent; - private OnMenuClickListener mListener; - private NotificationMenuRowProvider mMenuProvider; - private ArrayList<MenuItem> mMenuItems = new ArrayList<>(); + + private Context mContext; + private FrameLayout mMenuContainer; + private ArrayList<MenuItem> mMenuItems; + private OnMenuEventListener mMenuListener; private ValueAnimator mFadeAnimator; - private boolean mMenuFadedIn = false; - private boolean mAnimating = false; - private boolean mOnLeft = true; - private boolean mDismissing = false; - private boolean mSnapping = false; - private boolean mIconsPlaced = false; + private boolean mAnimating; + private boolean mMenuFadedIn; + + private boolean mOnLeft; + private boolean mIconsPlaced; + + private boolean mDismissing; + private boolean mSnapping; + private float mTranslation; private int[] mIconLocation = new int[2]; private int[] mParentLocation = new int[2]; private float mHorizSpaceForIcon; private int mVertSpaceForIcons; - private int mIconPadding; - private int mIconTint; private float mAlpha = 0f; - public NotificationMenuRow(Context context) { - this(context, null); - } + private CheckForDrag mCheckForDrag; + private Handler mHandler; - public NotificationMenuRow(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } + private boolean mMenuSnappedTo; + private boolean mMenuSnappedOnLeft; + private boolean mShouldShowMenu; - public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } + private NotificationSwipeActionHelper mSwipeHelper; - public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs); - mMenuItems.addAll(getDefaultNotificationMenuItems()); + public NotificationMenuRow(Context context) { + mContext = context; + final Resources res = context.getResources(); + mShouldShowMenu = res.getBoolean(R.bool.config_showNotificationGear); + mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size); + mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height); + mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding); + mHandler = new Handler(); + mMenuItems = getDefaultMenuItems(context); } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - Dependency.get(PluginManager.class).addPluginListener( - this, NotificationMenuRowProvider.class, false /* Allow multiple */); + public ArrayList<MenuItem> getMenuItems(Context context) { + return mMenuItems; } @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - Dependency.get(PluginManager.class).removePluginListener(this); + public MenuItem getLongpressMenuItem(Context context) { + return mMenuItems.get(NOTIFICATION_INFO_INDEX); } @Override - protected void onFinishInflate() { - super.onFinishInflate(); - final Resources res = getResources(); - mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size); - mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height); - mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding); - mIconTint = res.getColor(R.color.notification_gear_color); - updateMenu(false /* notify */); + public void setSwipeActionHelper(NotificationSwipeActionHelper helper) { + mSwipeHelper = helper; } - public static MenuItem getLongpressMenuItem(Context context) { - Resources res = context.getResources(); - Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings); - String settingsDescription = res.getString(R.string.notification_menu_gear_description); - NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(context).inflate( - R.layout.notification_info, null, false); - MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent); - return settings; + @Override + public void setMenuClickListener(OnMenuEventListener listener) { + mMenuListener = listener; } - public ArrayList<MenuItem> getDefaultNotificationMenuItems() { - ArrayList<MenuItem> items = new ArrayList<MenuItem>(); - Resources res = getResources(); - - Drawable snoozeIcon = res.getDrawable(R.drawable.ic_snooze); - NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(mContext) - .inflate(R.layout.notification_snooze, null, false); - String snoozeDescription = res.getString(R.string.notification_menu_snooze_description); - MenuItem snooze = new MenuItem(snoozeIcon, snoozeDescription, content); - items.add(snooze); + @Override + public void createMenu(ViewGroup parent) { + mParent = (ExpandableNotificationRow) parent; + mMenuContainer = new FrameLayout(mContext); + for (int i = 0; i < mMenuItems.size(); i++) { + addMenuView(mMenuItems.get(i), mMenuContainer); + } + resetState(false); + } - Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings); - String settingsDescription = res.getString(R.string.notification_menu_gear_description); - NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(mContext).inflate( - R.layout.notification_info, null, false); - MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent); - items.add(settings); - return items; + @Override + public boolean isMenuVisible() { + return mAlpha > 0; } - private void updateMenu(boolean notify) { - removeAllViews(); - mMenuItems.clear(); - if (mMenuProvider != null) { - mMenuItems.addAll(mMenuProvider.getMenuItems(getContext())); - } - mMenuItems.addAll(getDefaultNotificationMenuItems()); - for (int i = 0; i < mMenuItems.size(); i++) { - final View v = createMenuView(mMenuItems.get(i)); - mMenuItems.get(i).menuView = v; - } - resetState(notify); + @Override + public View getMenuView() { + return mMenuContainer; } - private View createMenuView(MenuItem item) { - AlphaOptimizedImageView iv = new AlphaOptimizedImageView(getContext()); - addView(iv); - iv.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding); - iv.setImageDrawable(item.icon); - iv.setOnClickListener(this); - iv.setColorFilter(mIconTint); - iv.setAlpha(mAlpha); - FrameLayout.LayoutParams lp = (LayoutParams) iv.getLayoutParams(); - lp.width = (int) mHorizSpaceForIcon; - lp.height = (int) mHorizSpaceForIcon; - return iv; + @Override + public void resetMenu() { + resetState(true); } - public void resetState(boolean notify) { + private void resetState(boolean notify) { setMenuAlpha(0f); mIconsPlaced = false; mMenuFadedIn = false; mAnimating = false; mSnapping = false; mDismissing = false; - setMenuLocation(mOnLeft ? 1 : -1 /* on left */); - if (mListener != null && notify) { - mListener.onMenuReset(mParent); + mMenuSnappedTo = false; + setMenuLocation(); + if (mMenuListener != null && notify) { + mMenuListener.onMenuReset(mParent); } } - public void setMenuClickListener(OnMenuClickListener listener) { - mListener = listener; - } + @Override + public boolean onTouchEvent(View view, MotionEvent ev, float velocity) { + final int action = ev.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: + mSnapping = false; + if (mFadeAnimator != null) { + mFadeAnimator.cancel(); + } + mHandler.removeCallbacks(mCheckForDrag); + mCheckForDrag = null; + break; + + case MotionEvent.ACTION_MOVE: + mSnapping = false; + // If the menu is visible and the movement is towards it it's not a location change. + boolean locationChange = isTowardsMenu(mTranslation) + ? false : isMenuLocationChange(); + if (locationChange) { + // Don't consider it "snapped" if location has changed. + mMenuSnappedTo = false; + + // Changed directions, make sure we check to fade in icon again. + if (!mHandler.hasCallbacks(mCheckForDrag)) { + // No check scheduled, set null to schedule a new one. + mCheckForDrag = null; + } else { + // Check scheduled, reset alpha and update location; check will fade it in + setMenuAlpha(0f); + setMenuLocation(); + } + } + if (mShouldShowMenu + && !NotificationStackScrollLayout.isPinnedHeadsUp(view) + && !mParent.areGutsExposed() + && (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag))) { + // Only show the menu if we're not a heads up view and guts aren't exposed. + mCheckForDrag = new CheckForDrag(); + mHandler.postDelayed(mCheckForDrag, SHOW_MENU_DELAY); + } + break; - public void setNotificationRowParent(ExpandableNotificationRow parent) { - mParent = parent; - setMenuLocation(mOnLeft ? 1 : -1); + case MotionEvent.ACTION_UP: + return handleUpEvent(ev, view, velocity); + } + return false; } - public void setAppName(String appName) { - Resources res = getResources(); - final int count = mMenuItems.size(); - for (int i = 0; i < count; i++) { - MenuItem item = mMenuItems.get(i); - String description = String.format( - res.getString(R.string.notification_menu_accessibility), - appName, item.menuDescription); - item.menuView.setContentDescription(description); + private boolean handleUpEvent(MotionEvent ev, View animView, float velocity) { + // If the menu should not be shown, then there is no need to check if the a swipe + // should result in a snapping to the menu. As a result, just check if the swipe + // was enough to dismiss the notification. + if (!mShouldShowMenu) { + if (mSwipeHelper.isDismissGesture(ev)) { + dismiss(animView, velocity); + } else { + snapBack(animView, velocity); + } + return true; + } + + final boolean gestureTowardsMenu = isTowardsMenu(velocity); + final boolean gestureFastEnough = + mSwipeHelper.getMinDismissVelocity() <= Math.abs(velocity); + final boolean gestureFarEnough = + mSwipeHelper.swipedFarEnough(mTranslation, mParent.getWidth()); + final double timeForGesture = ev.getEventTime() - ev.getDownTime(); + final boolean showMenuForSlowOnGoing = !mParent.canViewBeDismissed() + && timeForGesture >= SWIPE_MENU_TIMING; + + final float targetLeft = mOnLeft ? getSpaceForMenu() : -getSpaceForMenu(); + if (mMenuSnappedTo && isMenuVisible()) { + if (mMenuSnappedOnLeft == mOnLeft) { + boolean coveringMenu = Math.abs(mTranslation) <= getSpaceForMenu() * 0.6f; + if (gestureTowardsMenu || coveringMenu) { + // Gesture is towards or covering the menu or a dismiss + snapBack(animView, 0); + } else if (mSwipeHelper.isDismissGesture(ev)) { + dismiss(animView, velocity); + } else { + // Didn't move enough to dismiss or cover, snap to the menu + showMenu(animView, targetLeft, velocity); + } + } else if ((!gestureFastEnough && swipedEnoughToShowMenu()) + || (gestureTowardsMenu && !gestureFarEnough)) { + // The menu has been snapped to previously, however, the menu is now on the + // other side. If gesture is towards menu and not too far snap to the menu. + showMenu(animView, targetLeft, velocity); + } else if (mSwipeHelper.isDismissGesture(ev)) { + dismiss(animView, velocity); + } else { + snapBack(animView, velocity); + } + } else if (((!gestureFastEnough || showMenuForSlowOnGoing) + && swipedEnoughToShowMenu()) + || gestureTowardsMenu) { + // Menu has not been snapped to previously and this is menu revealing gesture + showMenu(animView, targetLeft, velocity); + } else if (mSwipeHelper.isDismissGesture(ev)) { + dismiss(animView, velocity); + } else { + snapBack(animView, velocity); } + return true; } - public ExpandableNotificationRow getNotificationParent() { - return mParent; + private void showMenu(View animView, float targetLeft, float velocity) { + mMenuSnappedTo = true; + mMenuSnappedOnLeft = mOnLeft; + mMenuListener.onMenuShown(animView); + mSwipeHelper.snap(animView, targetLeft, velocity); } - public void setMenuAlpha(float alpha) { - mAlpha = alpha; - if (alpha == 0) { - mMenuFadedIn = false; // Can fade in again once it's gone. - setVisibility(View.INVISIBLE); - } else { - setVisibility(View.VISIBLE); - } - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - getChildAt(i).setAlpha(mAlpha); - } + private void snapBack(View animView, float velocity) { + mMenuSnappedTo = false; + mSnapping = true; + mSwipeHelper.snap(animView, 0 /* leftTarget */, velocity); } - /** - * Returns whether the menu is displayed on the left side of the view or not. - */ - public boolean isMenuOnLeft() { - return mOnLeft; + private void dismiss(View animView, float velocity) { + mMenuSnappedTo = false; + mDismissing = true; + mSwipeHelper.dismiss(animView, velocity); } - /** - * Returns the horizontal space in pixels required to display the menu. - */ - public float getSpaceForMenu() { - return mHorizSpaceForIcon * getChildCount(); + private boolean swipedEnoughToShowMenu() { + // If the notification can't be dismissed then how far it can move is + // restricted -- reduce the distance it needs to move in this case. + final float multiplier = mParent.canViewBeDismissed() ? 0.4f : 0.2f; + final float snapBackThreshold = getSpaceForMenu() * multiplier; + return !mSwipeHelper.swipedFarEnough(0, 0) && isMenuVisible() && (mOnLeft + ? mTranslation > snapBackThreshold + : mTranslation < -snapBackThreshold); } /** - * Indicates whether the menu is visible at 1 alpha. Does not indicate if entire view is - * visible. + * Returns whether the gesture is towards the menu location or not. */ - public boolean isVisible() { - return mAlpha > 0; + private boolean isTowardsMenu(float movement) { + return isMenuVisible() + && ((mOnLeft && movement <= 0) + || (!mOnLeft && movement >= 0)); + } + + @Override + public void setAppName(String appName) { + if (appName == null) { + return; + } + Resources res = mContext.getResources(); + final int count = mMenuItems.size(); + for (int i = 0; i < count; i++) { + MenuItem item = mMenuItems.get(i); + String description = String.format( + res.getString(R.string.notification_menu_accessibility), + appName, item.getContentDescription()); + View menuView = item.getMenuView(); + if (menuView != null) { + menuView.setContentDescription(description); + } + } } - public void cancelFadeAnimator() { - if (mFadeAnimator != null) { - mFadeAnimator.cancel(); + @Override + public void onHeightUpdate() { + if (mParent == null || mMenuItems.size() == 0) { + return; + } + int parentHeight = mParent.getCollapsedHeight(); + float translationY; + if (parentHeight < mVertSpaceForIcons) { + translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2); + } else { + translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2; } + mMenuContainer.setTranslationY(translationY); } - public void updateMenuAlpha(final float transX, final float size) { + @Override + public void onTranslationUpdate(float translation) { + mTranslation = translation; if (mAnimating || !mMenuFadedIn) { // Don't adjust when animating, or if the menu hasn't been shown yet. return; } - - final float fadeThreshold = size * 0.3f; - final float absTrans = Math.abs(transX); + final float fadeThreshold = mParent.getWidth() * 0.3f; + final float absTrans = Math.abs(translation); float desiredAlpha = 0; - if (absTrans == 0) { desiredAlpha = 0; } else if (absTrans <= fadeThreshold) { desiredAlpha = 1; } else { - desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold)); + desiredAlpha = 1 - ((absTrans - fadeThreshold) / (mParent.getWidth() - fadeThreshold)); } setMenuAlpha(desiredAlpha); } - public void fadeInMenu(final boolean fromLeft, final float transX, - final float notiThreshold) { + @Override + public void onClick(View v) { + if (mMenuListener == null) { + // Nothing to do + return; + } + v.getLocationOnScreen(mIconLocation); + mParent.getLocationOnScreen(mParentLocation); + final int centerX = (int) (mHorizSpaceForIcon / 2); + final int centerY = v.getHeight() / 2; + final int x = mIconLocation[0] - mParentLocation[0] + centerX; + final int y = mIconLocation[1] - mParentLocation[1] + centerY; + final int index = mMenuContainer.indexOfChild(v); + mMenuListener.onMenuClicked(mParent, x, y, mMenuItems.get(index)); + } + + private boolean isMenuLocationChange() { + boolean onLeft = mTranslation > mIconPadding; + boolean onRight = mTranslation < -mIconPadding; + if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) { + return true; + } + return false; + } + + private void setMenuLocation() { + boolean showOnLeft = mTranslation > 0; + if ((mIconsPlaced && showOnLeft == mOnLeft) || mSnapping || mParent == null) { + // Do nothing + return; + } + final boolean isRtl = mParent.isLayoutRtl(); + final int count = mMenuContainer.getChildCount(); + final int width = mParent.getWidth(); + for (int i = 0; i < count; i++) { + final View v = mMenuContainer.getChildAt(i); + final float left = isRtl + ? -(width - mHorizSpaceForIcon * (i + 1)) + : i * mHorizSpaceForIcon; + final float right = isRtl + ? -i * mHorizSpaceForIcon + : width - (mHorizSpaceForIcon * (i + 1)); + v.setTranslationX(showOnLeft ? left : right); + } + mOnLeft = showOnLeft; + mIconsPlaced = true; + } + + private void setMenuAlpha(float alpha) { + mAlpha = alpha; + if (mMenuContainer == null) { + return; + } + if (alpha == 0) { + mMenuFadedIn = false; // Can fade in again once it's gone. + mMenuContainer.setVisibility(View.INVISIBLE); + } else { + mMenuContainer.setVisibility(View.VISIBLE); + } + final int count = mMenuContainer.getChildCount(); + for (int i = 0; i < count; i++) { + mMenuContainer.getChildAt(i).setAlpha(mAlpha); + } + } + + /** + * Returns the horizontal space in pixels required to display the menu. + */ + private float getSpaceForMenu() { + return mHorizSpaceForIcon * mMenuContainer.getChildCount(); + } + + private final class CheckForDrag implements Runnable { + @Override + public void run() { + final float absTransX = Math.abs(mTranslation); + final float bounceBackToMenuWidth = getSpaceForMenu(); + final float notiThreshold = mParent.getWidth() * 0.4f; + if ((!isMenuVisible() || isMenuLocationChange()) + && absTransX >= bounceBackToMenuWidth * 0.4 + && absTransX < notiThreshold) { + fadeInMenu(notiThreshold); + } + } + } + + private void fadeInMenu(final float notiThreshold) { if (mDismissing || mAnimating) { return; } - if (isMenuLocationChange(transX)) { + if (isMenuLocationChange()) { setMenuAlpha(0f); } - setMenuLocation((int) transX); + final float transX = mTranslation; + final boolean fromLeft = mTranslation > 0; + setMenuLocation(); mFadeAnimator = ValueAnimator.ofFloat(mAlpha, 1); mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { final float absTrans = Math.abs(transX); - boolean pastGear = (fromLeft && transX <= notiThreshold) + boolean pastMenu = (fromLeft && transX <= notiThreshold) || (!fromLeft && absTrans <= notiThreshold); - if (pastGear && !mMenuFadedIn) { + if (pastMenu && !mMenuFadedIn) { setMenuAlpha((float) animation.getAnimatedValue()); } } @@ -313,91 +486,77 @@ public class NotificationMenuRow extends FrameLayout mFadeAnimator.start(); } - public void updateVerticalLocation() { - if (mParent == null || mMenuItems.size() == 0) { - return; - } - int parentHeight = mParent.getCollapsedHeight(); - float translationY; - if (parentHeight < mVertSpaceForIcons) { - translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2); - } else { - translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2; - } - setTranslationY(translationY); - } - @Override - public void onRtlPropertiesChanged(int layoutDirection) { - mIconsPlaced = false; - setMenuLocation(mOnLeft ? 1 : -1); + public void setMenuItems(ArrayList<MenuItem> items) { + // Do nothing we use our own for now. + // TODO -- handle / allow custom menu items! } - public void setMenuLocation(int translation) { - boolean onLeft = translation > 0; - if ((mIconsPlaced && onLeft == mOnLeft) || mSnapping || mParent == null) { - // Do nothing - return; - } - final boolean isRtl = mParent.isLayoutRtl(); - final int count = getChildCount(); - final int width = getWidth(); - for (int i = 0; i < count; i++) { - final View v = getChildAt(i); - final float left = isRtl - ? -(width - mHorizSpaceForIcon * (i + 1)) - : i * mHorizSpaceForIcon; - final float right = isRtl - ? -i * mHorizSpaceForIcon - : width - (mHorizSpaceForIcon * (i + 1)); - v.setTranslationX(onLeft ? left : right); - } - mOnLeft = onLeft; - mIconsPlaced = true; - } + public static ArrayList<MenuItem> getDefaultMenuItems(Context context) { + ArrayList<MenuItem> items = new ArrayList<MenuItem>(); + Resources res = context.getResources(); - public boolean isMenuLocationChange(float translation) { - boolean onLeft = translation > mIconPadding; - boolean onRight = translation < -mIconPadding; - if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) { - return true; - } - return false; - } + NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(context) + .inflate(R.layout.notification_snooze, null, false); + String snoozeDescription = res.getString(R.string.notification_menu_snooze_description); + MenuItem snooze = new NotificationMenuItem(context, snoozeDescription, content, + R.drawable.ic_snooze); + items.add(snooze); - public void setDismissing() { - mDismissing = true; + String settingsDescription = res.getString(R.string.notification_menu_gear_description); + NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(context).inflate( + R.layout.notification_info, null, false); + MenuItem settings = new NotificationMenuItem(context, settingsDescription, settingsContent, + R.drawable.ic_settings); + items.add(settings); + return items; } - public void setSnapping(boolean snapping) { - mSnapping = snapping; + private void addMenuView(MenuItem item, ViewGroup parent) { + View menuView = item.getMenuView(); + if (menuView != null) { + parent.addView(menuView); + menuView.setOnClickListener(this); + FrameLayout.LayoutParams lp = (LayoutParams) menuView.getLayoutParams(); + lp.width = (int) mHorizSpaceForIcon; + lp.height = (int) mHorizSpaceForIcon; + menuView.setLayoutParams(lp); + } } - @Override - public void onClick(View v) { - if (mListener == null) { - // Nothing to do - return; + public static class NotificationMenuItem implements MenuItem { + View mMenuView; + GutsContent mGutsContent; + String mContentDescription; + + public NotificationMenuItem(Context context, String s, GutsContent content, int iconResId) { + Resources res = context.getResources(); + int padding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding); + int tint = res.getColor(R.color.notification_gear_color); + AlphaOptimizedImageView iv = new AlphaOptimizedImageView(context); + iv.setPadding(padding, padding, padding, padding); + Drawable icon = context.getResources().getDrawable(iconResId); + iv.setImageDrawable(icon); + iv.setColorFilter(tint); + iv.setAlpha(1f); + mMenuView = iv; + mContentDescription = s; + mGutsContent = content; } - v.getLocationOnScreen(mIconLocation); - mParent.getLocationOnScreen(mParentLocation); - final int centerX = (int) (mHorizSpaceForIcon / 2); - final int centerY = (int) (v.getTranslationY() * 2 + v.getHeight()) / 2; - final int x = mIconLocation[0] - mParentLocation[0] + centerX; - final int y = mIconLocation[1] - mParentLocation[1] + centerY; - final int index = indexOfChild(v); - mListener.onMenuClicked(mParent, x, y, mMenuItems.get(index)); - } - @Override - public void onPluginConnected(NotificationMenuRowProvider plugin, Context pluginContext) { - mMenuProvider = plugin; - updateMenu(false /* notify */); - } + @Override + public View getMenuView() { + return mMenuView; + } - @Override - public void onPluginDisconnected(NotificationMenuRowProvider plugin) { - mMenuProvider = null; - updateMenu(false /* notify */); + @Override + public View getGutsView() { + return mGutsContent.getContentView(); + } + + @Override + public String getContentDescription() { + return mContentDescription; + } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java index 6b1e62d9ad71..0de3e0244fa4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java @@ -16,14 +16,10 @@ package com.android.systemui.statusbar; */ import java.util.ArrayList; -import java.util.Calendar; import java.util.List; -import java.util.concurrent.TimeUnit; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeOption; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; import android.content.Context; import android.content.res.Resources; @@ -33,23 +29,18 @@ import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; -import android.view.ContextThemeWrapper; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.RadioGroup; -import android.widget.RadioGroup.OnCheckedChangeListener; import android.widget.TextView; -import android.widget.Toast; import com.android.systemui.R; public class NotificationSnooze extends LinearLayout - implements NotificationMenuRowProvider.SnoozeGutsContent, View.OnClickListener { + implements NotificationGuts.GutsContent, View.OnClickListener { private static final int MAX_ASSISTANT_SUGGESTIONS = 2; - private GutsInteractionListener mGutsInteractionListener; - private SnoozeListener mSnoozeListener; + private NotificationGuts mGutsContainer; + private NotificationSwipeActionHelper mSnoozeListener; private StatusBarNotification mSbn; private TextView mSelectedOptionText; @@ -155,8 +146,8 @@ public class NotificationSnooze extends LinearLayout @Override public void onClick(View v) { - if (mGutsInteractionListener != null) { - mGutsInteractionListener.onInteraction(this); + if (mGutsContainer != null) { + mGutsContainer.resetFalsingCheck(); } final int id = v.getId(); final SnoozeOption tag = (SnoozeOption) v.getTag(); @@ -172,7 +163,7 @@ public class NotificationSnooze extends LinearLayout private void undoSnooze() { mSelectedOption = null; - mGutsInteractionListener.closeGuts(this); + mGutsContainer.closeControls(-1 /* x */, -1 /* y */, true /* notify */); } @Override @@ -185,18 +176,16 @@ public class NotificationSnooze extends LinearLayout return this; } - @Override public void setStatusBarNotification(StatusBarNotification sbn) { mSbn = sbn; } @Override - public void setInteractionListener(GutsInteractionListener listener) { - mGutsInteractionListener = listener; + public void setGutsParent(NotificationGuts guts) { + mGutsContainer = guts; } - @Override - public void setSnoozeListener(SnoozeListener listener) { + public void setSnoozeListener(NotificationSwipeActionHelper listener) { mSnoozeListener = listener; } @@ -206,7 +195,7 @@ public class NotificationSnooze extends LinearLayout // then we commit the snooze action. if (mSnoozeListener != null && mSelectedOption != null) { mSnoozing = true; - mSnoozeListener.snoozeNotification(mSbn, mSelectedOption); + mSnoozeListener.snooze(mSbn, mSelectedOption); return true; } else { // Reset the view once it's closed diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 101aee4e9130..b82b113f3f8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -140,8 +140,7 @@ import com.android.systemui.fragments.PluginFragmentListener; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeOption; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; import com.android.systemui.qs.QSFragment; import com.android.systemui.qs.QSPanel; import com.android.systemui.qs.QSTileHost; @@ -241,8 +240,8 @@ import com.android.systemui.DejankUtils; import com.android.systemui.RecentsComponent; import com.android.systemui.SwipeHelper; import com.android.systemui.SystemUI; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeGutsContent; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.recents.Recents; import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.statusbar.stack.StackStateAnimator; @@ -255,8 +254,8 @@ import java.util.Stack; public class StatusBar extends SystemUI implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener, - OnHeadsUpChangedListener, VisualStabilityManager.Callback, SnoozeListener, - CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener, + OnHeadsUpChangedListener, VisualStabilityManager.Callback, CommandQueue.Callbacks, + ActivatableNotificationView.OnActivatedListener, ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment, ExpandableNotificationRow.OnExpandClickListener { public static final boolean MULTIUSER_DEBUG = false; @@ -5069,15 +5068,6 @@ public class StatusBar extends SystemUI implements DemoMode, } - public SnoozeListener getSnoozeListener() { - return this; - } - - @Override - public void snoozeNotification(StatusBarNotification sbn, SnoozeOption snoozeOption) { - setNotificationSnoozed(sbn, snoozeOption); - } - // Begin Extra BaseStatusBar methods. protected CommandQueue mCommandQueue; @@ -5745,7 +5735,7 @@ public class StatusBar extends SystemUI implements DemoMode, }, false /* afterKeyguardGone */); } - protected void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) { + public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) { if (snoozeOption.criterion != null) { mNotificationListener.snoozeNotification(sbn.getKey(), snoozeOption.criterion.getId()); } else { @@ -5768,20 +5758,22 @@ public class StatusBar extends SystemUI implements DemoMode, mGutsMenuItem = null; }); - if (item.gutsContent instanceof SnoozeGutsContent) { - ((SnoozeGutsContent) item.gutsContent).setSnoozeListener(getSnoozeListener()); - ((SnoozeGutsContent) item.gutsContent).setStatusBarNotification(sbn); - ((NotificationSnooze) item.gutsContent).setSnoozeOptions(row.getEntry().snoozeCriteria); + View gutsView = item.getGutsView(); + if (gutsView instanceof NotificationSnooze) { + NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView; + snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper()); + snoozeGuts.setStatusBarNotification(sbn); + snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria); } - if (item.gutsContent instanceof NotificationInfo) { + if (gutsView instanceof NotificationInfo) { final UserHandle userHandle = sbn.getUser(); PackageManager pmUser = getPackageManagerForUser(mContext, userHandle.getIdentifier()); final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)); final String pkg = sbn.getPackageName(); - NotificationInfo info = (NotificationInfo) item.gutsContent; + NotificationInfo info = (NotificationInfo) gutsView; final NotificationInfo.OnSettingsClickListener onSettingsClick = (View v, NotificationChannel channel, int appUid) -> { mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO); @@ -5890,7 +5882,7 @@ public class StatusBar extends SystemUI implements DemoMode, + "window"); return; } - dismissPopups(-1 /* x */, -1 /* y */, false /* resetGear */, + dismissPopups(-1 /* x */, -1 /* y */, false /* resetMenu */, false /* animate */); guts.setVisibility(View.VISIBLE); final double horz = Math.max(guts.getWidth() - x, x); @@ -5904,7 +5896,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); - // Move the notification view back over the gear + // Move the notification view back over the menu row.resetTranslation(); } }); @@ -5930,19 +5922,19 @@ public class StatusBar extends SystemUI implements DemoMode, } public void dismissPopups() { - dismissPopups(-1 /* x */, -1 /* y */, true /* resetGear */, false /* animate */); + dismissPopups(-1 /* x */, -1 /* y */, true /* resetMenu */, false /* animate */); } private void dismissPopups(int x, int y) { - dismissPopups(x, y, true /* resetGear */, false /* animate */); + dismissPopups(x, y, true /* resetMenu */, false /* animate */); } - public void dismissPopups(int x, int y, boolean resetGear, boolean animate) { + public void dismissPopups(int x, int y, boolean resetMenu, boolean animate) { if (mNotificationGutsExposed != null) { mNotificationGutsExposed.closeControls(x, y, true /* save */); } - if (resetGear) { - mStackScroller.resetExposedGearView(animate, true /* force */); + if (resetMenu) { + mStackScroller.resetExposedMenuView(animate, true /* force */); } } @@ -6299,8 +6291,8 @@ public class StatusBar extends SystemUI implements DemoMode, return; } - // Check if the notification is displaying the gear, if so slide notification back - if (row.getSettingsRow() != null && row.getSettingsRow().isVisible()) { + // Check if the notification is displaying the menu, if so slide notification back + if (row.getProvider() != null && row.getProvider().isMenuVisible()) { row.animateTranslateNotification(0); return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index d3b336bef629..7d2d0df3f0d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -37,6 +37,7 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; +import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Log; @@ -63,15 +64,15 @@ import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.SwipeHelper; import com.android.systemui.classifier.FalsingManager; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider; -import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.statusbar.ActivatableNotificationView; import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.NotificationGuts; -import com.android.systemui.statusbar.NotificationMenuRow; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StackScrollerDecorView; import com.android.systemui.statusbar.StatusBarState; @@ -96,7 +97,7 @@ import java.util.List; public class NotificationStackScrollLayout extends ViewGroup implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter, ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener, - NotificationMenuRowProvider.OnMenuClickListener, ScrollContainer, + NotificationMenuRowPlugin.OnMenuEventListener, ScrollContainer, VisibilityLocationProvider { public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; @@ -228,10 +229,9 @@ public class NotificationStackScrollLayout extends ViewGroup private int mMaxScrollAfterExpand; private SwipeHelper.LongPressListener mLongPressListener; - private NotificationMenuRow mCurrIconRow; + private NotificationMenuRowPlugin mCurrMenuRow; private View mTranslatingParentView; - private View mGearExposedView; - private boolean mShouldShowGear; + private View mMenuExposedView; /** * Should in this touch motion only be scrolling allowed? It's true when the scroller was @@ -397,7 +397,6 @@ public class NotificationStackScrollLayout extends ViewGroup mStackScrollAlgorithm = createStackScrollAlgorithm(context); initView(context); mFalsingManager = FalsingManager.getInstance(context); - mShouldShowGear = res.getBoolean(R.bool.config_showNotificationGear); mShouldDrawNotificationBackground = res.getBoolean(R.bool.config_drawNotificationBackground); @@ -410,6 +409,10 @@ public class NotificationStackScrollLayout extends ViewGroup } } + public NotificationSwipeActionHelper getSwipeActionHelper() { + return mSwipeHelper; + } + @Override public void onMenuClicked(View view, int x, int y, MenuItem item) { if (mLongPressListener == null) { @@ -426,13 +429,22 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public void onMenuReset(View row) { if (mTranslatingParentView != null && row == mTranslatingParentView) { - mSwipeHelper.setSnappedToGear(false); - mGearExposedView = null; + mMenuExposedView = null; mTranslatingParentView = null; } } @Override + public void onMenuShown(View row) { + mMenuExposedView = mTranslatingParentView; + if (row instanceof ExpandableNotificationRow) { + MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR, + ((ExpandableNotificationRow) row).getStatusBarNotification() + .getPackageName()); + } + mSwipeHelper.onMenuShown(row); + } + protected void onDraw(Canvas canvas) { if (mShouldDrawNotificationBackground && mCurrentBounds.top < mCurrentBounds.bottom) { canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, @@ -936,9 +948,9 @@ public class NotificationStackScrollLayout extends ViewGroup // We start the swipe and snap back in the same frame, we don't want any animation mDragAnimPendingChildren.remove(animView); } - if (mCurrIconRow != null && targetLeft == 0) { - mCurrIconRow.resetState(true /* notify */); - mCurrIconRow = null; + if (mCurrMenuRow != null && targetLeft == 0) { + mCurrMenuRow.resetMenu(); + mCurrMenuRow = null; } } @@ -999,10 +1011,10 @@ public class NotificationStackScrollLayout extends ViewGroup ExpandableNotificationRow parent = row.getNotificationParent(); if (parent != null && parent.areChildrenExpanded() && (parent.areGutsExposed() - || mGearExposedView == parent + || mMenuExposedView == parent || (parent.getNotificationChildren().size() == 1 && parent.isClearable()))) { - // In this case the group is expanded and showing the gear for the + // In this case the group is expanded and showing the menu for the // group, further interaction should apply to the group, not any // child notifications so we use the parent of the child. We also do the same // if we only have a single child. @@ -1261,8 +1273,8 @@ public class NotificationStackScrollLayout extends ViewGroup public void snapViewIfNeeded(ExpandableNotificationRow child) { boolean animate = mIsExpanded || isPinnedHeadsUp(child); - // If the child is showing the gear to go to settings, snap to that - float targetLeft = child.getSettingsRow().isVisible() ? child.getTranslation() : 0; + // If the child is showing the notification menu snap to that + float targetLeft = child.getProvider().isMenuVisible() ? child.getTranslation() : 0; mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft); } @@ -4196,15 +4208,11 @@ public class NotificationStackScrollLayout extends ViewGroup void flingTopOverscroll(float velocity, boolean open); } - private class NotificationSwipeHelper extends SwipeHelper { - private static final long SHOW_GEAR_DELAY = 60; - private static final long COVER_GEAR_DELAY = 4000; - private static final long SWIPE_GEAR_TIMING = 200; - private CheckForDrag mCheckForDrag; + private class NotificationSwipeHelper extends SwipeHelper + implements NotificationSwipeActionHelper { + private static final long COVER_MENU_DELAY = 4000; private Runnable mFalsingCheck; private Handler mHandler; - private boolean mGearSnappedTo; - private boolean mGearSnappedOnLeft; public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) { super(swipeDirection, callback, context); @@ -4212,69 +4220,46 @@ public class NotificationStackScrollLayout extends ViewGroup mFalsingCheck = new Runnable() { @Override public void run() { - resetExposedGearView(true /* animate */, true /* force */); + resetExposedMenuView(true /* animate */, true /* force */); } }; } @Override - public void onDownUpdate(View currView) { - // Set the active view + public void onDownUpdate(View currView, MotionEvent ev) { mTranslatingParentView = currView; - - // Reset check for drag gesture - cancelCheckForDrag(); - if (mCurrIconRow != null) { - mCurrIconRow.setSnapping(false); + mCurrMenuRow = null; + if (mCurrMenuRow != null) { + mCurrMenuRow.onTouchEvent(currView, ev, 0 /* velocity */); } - mCheckForDrag = null; - mCurrIconRow = null; mHandler.removeCallbacks(mFalsingCheck); - // Slide back any notifications that might be showing a gear - resetExposedGearView(true /* animate */, false /* force */); + // Slide back any notifications that might be showing a menu + resetExposedMenuView(true /* animate */, false /* force */); if (currView instanceof ExpandableNotificationRow) { - // Set the listener for the current row's gear - mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow(); - mCurrIconRow.setMenuClickListener(NotificationStackScrollLayout.this); + ExpandableNotificationRow row = (ExpandableNotificationRow) currView; + mCurrMenuRow = row.createMenu(); + mCurrMenuRow.setSwipeActionHelper(NotificationSwipeHelper.this); + mCurrMenuRow.setMenuClickListener(NotificationStackScrollLayout.this); } } @Override - public void onMoveUpdate(View view, float translation, float delta) { + public void onMoveUpdate(View view, MotionEvent ev, float translation, float delta) { mHandler.removeCallbacks(mFalsingCheck); - - if (mCurrIconRow != null) { - mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping. - - // If the gear is visible and the movement is towards it it's not a location change. - boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isMenuOnLeft(); - boolean locationChange = isTowardsGear(translation, onLeft) - ? false : mCurrIconRow.isMenuLocationChange(translation); - if (locationChange) { - // Don't consider it "snapped" if location has changed. - setSnappedToGear(false); - - // Changed directions, make sure we check to fade in icon again. - if (!mHandler.hasCallbacks(mCheckForDrag)) { - // No check scheduled, set null to schedule a new one. - mCheckForDrag = null; - } else { - // Check scheduled, reset alpha and update location; check will fade it in - mCurrIconRow.setMenuAlpha(0f); - mCurrIconRow.setMenuLocation((int) translation); - } - } + if (mCurrMenuRow != null) { + mCurrMenuRow.onTouchEvent(view, ev, 0 /* velocity */); } + } - final boolean gutsExposed = (view instanceof ExpandableNotificationRow) - && ((ExpandableNotificationRow) view).areGutsExposed(); - - if (mShouldShowGear && !isPinnedHeadsUp(view) && !gutsExposed) { - // Only show the gear if we're not a heads up view and guts aren't exposed. - checkForDrag(); + @Override + public boolean handleUpEvent(MotionEvent ev, View animView, float velocity, + float translation) { + if (mCurrMenuRow != null) { + return mCurrMenuRow.onTouchEvent(animView, ev, velocity); } + return false; } @Override @@ -4286,7 +4271,7 @@ public class NotificationStackScrollLayout extends ViewGroup // of the panel early. handleChildDismissed(view); } - handleGearCoveredOrDismissed(); + handleMenuCoveredOrDismissed(); } @Override @@ -4294,121 +4279,21 @@ public class NotificationStackScrollLayout extends ViewGroup super.snapChild(animView, targetLeft, velocity); onDragCancelled(animView); if (targetLeft == 0) { - handleGearCoveredOrDismissed(); - } - } - - private void handleGearCoveredOrDismissed() { - cancelCheckForDrag(); - setSnappedToGear(false); - if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) { - mGearExposedView = null; + handleMenuCoveredOrDismissed(); } } @Override - public boolean handleUpEvent(MotionEvent ev, View animView, float velocity, - float translation) { - if (mCurrIconRow == null) { - cancelCheckForDrag(); - return false; // Let SwipeHelper handle it. - } - - // If the gear icon should not be shown, then there is no need to check if the a swipe - // should result in a snapping to the gear icon. As a result, just check if the swipe - // was enough to dismiss the notification. - if (!mShouldShowGear) { - dismissOrSnapBack(animView, velocity, ev); - return true; - } - - boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isMenuOnLeft()); - boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity(); - final double timeForGesture = ev.getEventTime() - ev.getDownTime(); - final boolean showGearForSlowOnGoing = !canChildBeDismissed(animView) - && timeForGesture >= SWIPE_GEAR_TIMING; - - if (mGearSnappedTo && mCurrIconRow.isVisible()) { - if (mGearSnappedOnLeft == mCurrIconRow.isMenuOnLeft()) { - boolean coveringGear = - Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f; - if (gestureTowardsGear || coveringGear) { - // Gesture is towards or covering the gear - snapChild(animView, 0 /* leftTarget */, velocity); - } else if (isDismissGesture(ev)) { - // Gesture is a dismiss that's not towards the gear - dismissChild(animView, velocity, - !swipedFastEnough() /* useAccelerateInterpolator */); - } else { - // Didn't move enough to dismiss or cover, snap to the gear - snapToGear(animView, velocity); - } - } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView)) - || (gestureTowardsGear && !swipedFarEnough())) { - // The gear has been snapped to previously, however, the gear is now on the - // other side. If gesture is towards gear and not too far snap to the gear. - snapToGear(animView, velocity); - } else { - dismissOrSnapBack(animView, velocity, ev); - } - } else if (((!gestureFastEnough || showGearForSlowOnGoing) - && swipedEnoughToShowGear(animView)) - || gestureTowardsGear) { - // Gear has not been snapped to previously and this is gear revealing gesture - snapToGear(animView, velocity); - } else { - dismissOrSnapBack(animView, velocity, ev); - } - return true; + public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption) { + mStatusBar.setNotificationSnoozed(sbn, snoozeOption); } - private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) { - if (isDismissGesture(ev)) { - dismissChild(animView, velocity, - !swipedFastEnough() /* useAccelerateInterpolator */); - } else { - snapChild(animView, 0 /* leftTarget */, velocity); + private void handleMenuCoveredOrDismissed() { + if (mMenuExposedView != null && mMenuExposedView == mTranslatingParentView) { + mMenuExposedView = null; } } - private void snapToGear(View animView, float velocity) { - final float snapBackThreshold = getSpaceForGear(animView); - final float target = mCurrIconRow.isMenuOnLeft() ? snapBackThreshold - : -snapBackThreshold; - mGearExposedView = mTranslatingParentView; - if (animView instanceof ExpandableNotificationRow) { - MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR, - ((ExpandableNotificationRow) animView).getStatusBarNotification() - .getPackageName()); - } - if (mCurrIconRow != null) { - mCurrIconRow.setSnapping(true); - setSnappedToGear(true); - } - onDragCancelled(animView); - - // If we're on the lockscreen we want to false this. - if (isAntiFalsingNeeded()) { - mHandler.removeCallbacks(mFalsingCheck); - mHandler.postDelayed(mFalsingCheck, COVER_GEAR_DELAY); - } - super.snapChild(animView, target, velocity); - } - - private boolean swipedEnoughToShowGear(View animView) { - if (mTranslatingParentView == null) { - return false; - } - // If the notification can't be dismissed then how far it can move is - // restricted -- reduce the distance it needs to move in this case. - final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f; - final float snapBackThreshold = getSpaceForGear(animView) * multiplier; - final float translation = getTranslation(animView); - return !swipedFarEnough() && mCurrIconRow.isVisible() && (mCurrIconRow.isMenuOnLeft() - ? translation > snapBackThreshold - : translation < -snapBackThreshold); - } - @Override public Animator getViewTranslationAnimator(View v, float target, AnimatorUpdateListener listener) { @@ -4429,6 +4314,42 @@ public class NotificationStackScrollLayout extends ViewGroup return ((ExpandableView) v).getTranslation(); } + @Override + public void dismiss(View animView, float velocity) { + dismissChild(animView, velocity, + !swipedFastEnough(0, 0) /* useAccelerateInterpolator */); + } + + @Override + public void snap(View animView, float targetLeft, float velocity) { + snapChild(animView, targetLeft, velocity); + } + + @Override + public boolean swipedFarEnough(float translation, float viewSize) { + return swipedFarEnough(); + } + + @Override + public boolean swipedFastEnough(float translation, float velocity) { + return swipedFastEnough(); + } + + @Override + public float getMinDismissVelocity() { + return getEscapeVelocity(); + } + + public void onMenuShown(View animView) { + onDragCancelled(animView); + + // If we're on the lockscreen we want to false this. + if (isAntiFalsingNeeded()) { + mHandler.removeCallbacks(mFalsingCheck); + mHandler.postDelayed(mFalsingCheck, COVER_MENU_DELAY); + } + } + public void closeControlsIfOutsideTouch(MotionEvent ev) { NotificationGuts guts = mStatusBar.getExposedGuts(); View view = null; @@ -4437,9 +4358,9 @@ public class NotificationStackScrollLayout extends ViewGroup // Checking guts view = guts; height = guts.getActualHeight(); - } else if (mCurrIconRow != null && mCurrIconRow.isVisible() + } else if (mCurrMenuRow != null && mCurrMenuRow.isMenuVisible() && mTranslatingParentView != null) { - // Checking gear + // Checking menu view = mTranslatingParentView; height = ((ExpandableView) mTranslatingParentView).getActualHeight(); } @@ -4452,95 +4373,29 @@ public class NotificationStackScrollLayout extends ViewGroup final int y = mTempInt2[1]; Rect rect = new Rect(x, y, x + view.getWidth(), y + height); if (!rect.contains(rx, ry)) { - // Touch was outside visible guts / gear notification, close what's visible - mStatusBar.dismissPopups(-1, -1, true /* resetGear */, true /* animate */); - } - } - } - - /** - * Returns whether the gesture is towards the gear location or not. - */ - private boolean isTowardsGear(float velocity, boolean onLeft) { - if (mCurrIconRow == null) { - return false; - } - return mCurrIconRow.isVisible() - && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0)); - } - - /** - * Indicates the the gear has been snapped to. - */ - private void setSnappedToGear(boolean snapped) { - mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isMenuOnLeft() : false; - mGearSnappedTo = snapped && mCurrIconRow != null; - } - - /** - * Returns the horizontal space in pixels required to display the gear behind a - * notification. - */ - private float getSpaceForGear(View view) { - if (view instanceof ExpandableNotificationRow) { - return ((ExpandableNotificationRow) view).getSpaceForGear(); - } - return 0; - } - - private void checkForDrag() { - if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) { - mCheckForDrag = new CheckForDrag(); - mHandler.postDelayed(mCheckForDrag, SHOW_GEAR_DELAY); - } - } - - private void cancelCheckForDrag() { - if (mCurrIconRow != null) { - mCurrIconRow.cancelFadeAnimator(); - } - mHandler.removeCallbacks(mCheckForDrag); - } - - private final class CheckForDrag implements Runnable { - @Override - public void run() { - if (mTranslatingParentView == null) { - return; - } - final float translation = getTranslation(mTranslatingParentView); - final float absTransX = Math.abs(translation); - final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView); - final float notiThreshold = getSize(mTranslatingParentView) * 0.4f; - if ((mCurrIconRow != null && (!mCurrIconRow.isVisible() - || mCurrIconRow.isMenuLocationChange(translation))) - && absTransX >= bounceBackToGearWidth * 0.4 - && absTransX < notiThreshold) { - // Fade in the gear - mCurrIconRow.fadeInMenu(translation > 0 /* fromLeft */, translation, - notiThreshold); + // Touch was outside visible guts / meny notification, close what's visible + mStatusBar.dismissPopups(-1, -1, true /* resetMenu */, true /* animate */); } } } - public void resetExposedGearView(boolean animate, boolean force) { - if (mGearExposedView == null - || (!force && mGearExposedView == mTranslatingParentView)) { - // If no gear is showing or it's showing for this view we do nothing. + public void resetExposedMenuView(boolean animate, boolean force) { + if (mMenuExposedView == null + || (!force && mMenuExposedView == mTranslatingParentView)) { + // If no menu is showing or it's showing for this view we do nothing. return; } - final View prevGearExposedView = mGearExposedView; + final View prevMenuExposedView = mMenuExposedView; if (animate) { - Animator anim = getViewTranslationAnimator(prevGearExposedView, + Animator anim = getViewTranslationAnimator(prevMenuExposedView, 0 /* leftTarget */, null /* updateListener */); if (anim != null) { anim.start(); } - } else if (mGearExposedView instanceof ExpandableNotificationRow) { - ((ExpandableNotificationRow) mGearExposedView).resetTranslation(); + } else if (mMenuExposedView instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) mMenuExposedView).resetTranslation(); } - mGearExposedView = null; - mGearSnappedTo = false; + mMenuExposedView = null; } } @@ -4557,8 +4412,8 @@ public class NotificationStackScrollLayout extends ViewGroup } } - public void resetExposedGearView(boolean animate, boolean force) { - mSwipeHelper.resetExposedGearView(animate, force); + public void resetExposedMenuView(boolean animate, boolean force) { + mSwipeHelper.resetExposedMenuView(animate, force); } public void closeControlsIfOutsideTouch(MotionEvent ev) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java index c2c633639e81..31b9bae846d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java @@ -18,6 +18,8 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.ViewUtils; +import android.testing.ViewUtils; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.utils.leaks.LeakCheckedTest; import org.junit.Before; @@ -35,10 +37,11 @@ public class NotificationMenuRowTest extends LeakCheckedTest { @Test public void testAttachDetach() { - NotificationMenuRow row = new NotificationMenuRow(mContext); - ViewUtils.attachView(row); + NotificationMenuRowPlugin row = new NotificationMenuRow(mContext); + row.createMenu(null); + ViewUtils.attachView(row.getMenuView()); TestableLooper.get(this).processAllMessages(); - ViewUtils.detachView(row); + ViewUtils.detachView(row.getMenuView()); TestableLooper.get(this).processAllMessages(); } } |