Merge "[Notif] Add Blocking helper to swipe" into pi-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
f431ded9dd
@@ -31,6 +31,7 @@ import com.android.systemui.classifier.FalsingManager;
|
||||
import com.android.systemui.keyguard.DismissCallbackRegistry;
|
||||
import com.android.systemui.qs.QSTileHost;
|
||||
import com.android.systemui.statusbar.KeyguardIndicationController;
|
||||
import com.android.systemui.statusbar.NotificationBlockingHelperManager;
|
||||
import com.android.systemui.statusbar.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.NotificationGutsManager;
|
||||
import com.android.systemui.statusbar.NotificationListener;
|
||||
@@ -130,6 +131,8 @@ public class SystemUIFactory {
|
||||
providers.put(NotificationGroupManager.class, NotificationGroupManager::new);
|
||||
providers.put(NotificationMediaManager.class, () -> new NotificationMediaManager(context));
|
||||
providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(context));
|
||||
providers.put(NotificationBlockingHelperManager.class,
|
||||
() -> new NotificationBlockingHelperManager(context));
|
||||
providers.put(NotificationRemoteInputManager.class,
|
||||
() -> new NotificationRemoteInputManager(context));
|
||||
providers.put(SmartReplyConstants.class,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.statusbar;
|
||||
|
||||
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
|
||||
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
|
||||
import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
|
||||
|
||||
@@ -34,7 +35,6 @@ import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.util.ArraySet;
|
||||
import android.util.AttributeSet;
|
||||
@@ -127,6 +127,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
private boolean mHasUserChangedExpansion;
|
||||
/** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
|
||||
private boolean mUserExpanded;
|
||||
/** Whether the blocking helper is showing on this notification (even if dismissed) */
|
||||
private boolean mIsBlockingHelperShowing;
|
||||
|
||||
/**
|
||||
* Has this notification been expanded while it was pinned
|
||||
@@ -400,8 +402,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
updateIconVisibilities();
|
||||
updateShelfIconColor();
|
||||
|
||||
showBlockingHelper(mEntry.userSentiment ==
|
||||
NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
|
||||
showBlockingHelperButton(mEntry.userSentiment == USER_SENTIMENT_NEGATIVE);
|
||||
updateRippleAllowed();
|
||||
}
|
||||
|
||||
@@ -594,6 +595,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
return mNotificationParent != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this notification is the only child in the group summary
|
||||
*/
|
||||
public boolean isOnlyChildInGroup() {
|
||||
return mGroupManager.isOnlyChildInGroup(getStatusBarNotification());
|
||||
}
|
||||
|
||||
public ExpandableNotificationRow getNotificationParent() {
|
||||
return mNotificationParent;
|
||||
}
|
||||
@@ -1150,11 +1158,31 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
return mGroupParentWhenDismissed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the notification with the option of showing the blocking helper in-place if we have
|
||||
* a negative user sentiment.
|
||||
*
|
||||
* @param fromAccessibility whether this dismiss is coming from an accessibility action
|
||||
* @return whether a blocking helper is shown in this row
|
||||
*/
|
||||
public boolean performDismissWithBlockingHelper(boolean fromAccessibility) {
|
||||
NotificationBlockingHelperManager manager =
|
||||
Dependency.get(NotificationBlockingHelperManager.class);
|
||||
boolean isBlockingHelperShown = manager.perhapsShowBlockingHelper(this, mMenuRow);
|
||||
|
||||
// Continue with dismiss since we don't want the blocking helper to be directly associated
|
||||
// with a certain notification.
|
||||
performDismiss(fromAccessibility);
|
||||
return isBlockingHelperShown;
|
||||
}
|
||||
|
||||
public void performDismiss(boolean fromAccessibility) {
|
||||
if (mGroupManager.isOnlyChildInGroup(getStatusBarNotification())) {
|
||||
if (isOnlyChildInGroup()) {
|
||||
ExpandableNotificationRow groupSummary =
|
||||
mGroupManager.getLogicalGroupSummary(getStatusBarNotification());
|
||||
if (groupSummary.isClearable()) {
|
||||
// If this is the only child in the group, dismiss the group, but don't try to show
|
||||
// the blocking helper affordance!
|
||||
groupSummary.performDismiss(fromAccessibility);
|
||||
}
|
||||
}
|
||||
@@ -1166,6 +1194,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
}
|
||||
}
|
||||
|
||||
public void setBlockingHelperShowing(boolean isBlockingHelperShowing) {
|
||||
mIsBlockingHelperShowing = isBlockingHelperShowing;
|
||||
}
|
||||
|
||||
public boolean isBlockingHelperShowing() {
|
||||
return mIsBlockingHelperShowing;
|
||||
}
|
||||
|
||||
public void setOnDismissRunnable(Runnable onDismissRunnable) {
|
||||
mOnDismissRunnable = onDismissRunnable;
|
||||
}
|
||||
@@ -1390,7 +1426,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public void showBlockingHelper(boolean show) {
|
||||
public void showBlockingHelperButton(boolean show) {
|
||||
mHelperButton.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@@ -1423,7 +1459,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
|
||||
mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout};
|
||||
|
||||
final NotificationGutsManager gutsMan = Dependency.get(NotificationGutsManager.class);
|
||||
mHelperButton = findViewById(R.id.helper);
|
||||
mHelperButton.setOnClickListener(view -> {
|
||||
doLongClickCallback();
|
||||
@@ -2526,7 +2561,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
||||
}
|
||||
switch (action) {
|
||||
case AccessibilityNodeInfo.ACTION_DISMISS:
|
||||
performDismiss(true /* fromAccessibility */);
|
||||
performDismissWithBlockingHelper(true /* fromAccessibility */);
|
||||
return true;
|
||||
case AccessibilityNodeInfo.ACTION_COLLAPSE:
|
||||
case AccessibilityNodeInfo.ACTION_EXPAND:
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
|
||||
|
||||
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
|
||||
|
||||
/**
|
||||
* Manager for the notification blocking helper - tracks and helps create the blocking helper
|
||||
* affordance.
|
||||
*/
|
||||
public class NotificationBlockingHelperManager {
|
||||
/** Enables debug logging and always makes the blocking helper show up after a dismiss. */
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "BlockingHelper";
|
||||
|
||||
private final Context mContext;
|
||||
/** Row that the blocking helper will be shown in (via {@link NotificationGuts}. */
|
||||
private ExpandableNotificationRow mBlockingHelperRow;
|
||||
|
||||
/**
|
||||
* Whether the notification shade/stack is expanded - used to determine blocking helper
|
||||
* eligibility.
|
||||
*/
|
||||
private boolean mIsShadeExpanded;
|
||||
|
||||
public NotificationBlockingHelperManager(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Potentially shows the blocking helper, represented via the {@link NotificationInfo} menu
|
||||
* item, in the current row if user sentiment is negative.
|
||||
*
|
||||
* @param row row to render the blocking helper in
|
||||
* @param menuRow menu used to generate the {@link NotificationInfo} view that houses the
|
||||
* blocking helper UI
|
||||
* @return whether we're showing a blocking helper in the given notification row
|
||||
*/
|
||||
boolean perhapsShowBlockingHelper(
|
||||
ExpandableNotificationRow row, NotificationMenuRowPlugin menuRow) {
|
||||
int numChildren = row.getNumberOfNotificationChildren();
|
||||
|
||||
// We only show the blocking helper if:
|
||||
// - The dismissed row is a valid group (>1 or 0 children) or the only child in the group
|
||||
// - The notification shade is fully expanded (guarantees we're not touching a HUN).
|
||||
// - User sentiment is negative
|
||||
if (DEBUG
|
||||
|| row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE
|
||||
&& mIsShadeExpanded
|
||||
&& (!row.isChildInGroup() || row.isOnlyChildInGroup())) {
|
||||
// Dismiss any current blocking helper before continuing forward (only one can be shown
|
||||
// at a given time).
|
||||
dismissCurrentBlockingHelper();
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Manager.perhapsShowBlockingHelper: Showing new blocking helper");
|
||||
}
|
||||
NotificationGutsManager manager = Dependency.get(NotificationGutsManager.class);
|
||||
|
||||
// Enable blocking helper on the row before moving forward so everything in the guts is
|
||||
// correctly prepped.
|
||||
mBlockingHelperRow = row;
|
||||
mBlockingHelperRow.setBlockingHelperShowing(true);
|
||||
|
||||
// We don't care about the touch origin (x, y) since we're opening guts without any
|
||||
// explicit user interaction.
|
||||
manager.openGuts(mBlockingHelperRow, 0, 0, menuRow.getLongpressMenuItem(mContext));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss the currently showing blocking helper, if any, through a notification update.
|
||||
*
|
||||
* @return whether the blocking helper was dismissed
|
||||
*/
|
||||
boolean dismissCurrentBlockingHelper() {
|
||||
if (!isBlockingHelperRowNull()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Manager.dismissCurrentBlockingHelper: Dismissing current helper");
|
||||
}
|
||||
if (!mBlockingHelperRow.isBlockingHelperShowing()) {
|
||||
Log.e(TAG, "Manager.dismissCurrentBlockingHelper: "
|
||||
+ "Non-null row is not showing a blocking helper");
|
||||
}
|
||||
|
||||
mBlockingHelperRow.setBlockingHelperShowing(false);
|
||||
if (mBlockingHelperRow.isAttachedToWindow()) {
|
||||
Dependency.get(NotificationEntryManager.class).updateNotifications();
|
||||
}
|
||||
mBlockingHelperRow = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the expansion status of the notification shade/stack.
|
||||
*
|
||||
* @param expandedHeight how much the shade is expanded ({code 0} indicating it's collapsed)
|
||||
*/
|
||||
public void setNotificationShadeExpanded(float expandedHeight) {
|
||||
mIsShadeExpanded = expandedHeight > 0.0f;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isBlockingHelperRowNull() {
|
||||
return mBlockingHelperRow == null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setBlockingHelperRowForTest(ExpandableNotificationRow blockingHelperRowForTest) {
|
||||
mBlockingHelperRow = blockingHelperRowForTest;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import android.view.ViewAnimationUtils;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.Interpolators;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.statusbar.stack.StackStateAnimator;
|
||||
@@ -189,8 +190,12 @@ public class NotificationGuts extends FrameLayout {
|
||||
}
|
||||
|
||||
public void openControls(
|
||||
int x, int y, boolean needsFalsingProtection, @Nullable Runnable onAnimationEnd) {
|
||||
animateOpen(x, y, onAnimationEnd);
|
||||
boolean shouldDoCircularReveal,
|
||||
int x,
|
||||
int y,
|
||||
boolean needsFalsingProtection,
|
||||
@Nullable Runnable onAnimationEnd) {
|
||||
animateOpen(shouldDoCircularReveal, x, y, onAnimationEnd);
|
||||
setExposed(true /* exposed */, needsFalsingProtection);
|
||||
}
|
||||
|
||||
@@ -204,7 +209,20 @@ public class NotificationGuts extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes any exposed guts/views.
|
||||
*
|
||||
* @param x x coordinate to animate the close circular reveal with
|
||||
* @param y y coordinate to animate the close circular reveal with
|
||||
* @param save whether the state should be saved
|
||||
* @param force whether the guts should be force-closed regardless of state.
|
||||
*/
|
||||
public void closeControls(int x, int y, boolean save, boolean force) {
|
||||
// First try to dismiss any blocking helper.
|
||||
boolean wasBlockingHelperDismissed =
|
||||
Dependency.get(NotificationBlockingHelperManager.class)
|
||||
.dismissCurrentBlockingHelper();
|
||||
|
||||
if (getWindowToken() == null) {
|
||||
if (mClosedListener != null) {
|
||||
mClosedListener.onGutsClosed(this);
|
||||
@@ -212,8 +230,12 @@ public class NotificationGuts extends FrameLayout {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mGutsContent == null || !mGutsContent.handleCloseControls(save, force)) {
|
||||
animateClose(x, y);
|
||||
if (mGutsContent == null
|
||||
|| !mGutsContent.handleCloseControls(save, force)
|
||||
|| wasBlockingHelperDismissed) {
|
||||
// We only want to do a circular reveal if we're not showing the blocking helper.
|
||||
animateClose(x, y, !wasBlockingHelperDismissed /* shouldDoCircularReveal */);
|
||||
|
||||
setExposed(false, mNeedsFalsingProtection);
|
||||
if (mClosedListener != null) {
|
||||
mClosedListener.onGutsClosed(this);
|
||||
@@ -221,47 +243,58 @@ public class NotificationGuts extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private void animateOpen(int x, int y, @Nullable Runnable onAnimationEnd) {
|
||||
final double horz = Math.max(getWidth() - x, x);
|
||||
final double vert = Math.max(getHeight() - y, y);
|
||||
final float r = (float) Math.hypot(horz, vert);
|
||||
|
||||
final Animator a
|
||||
= ViewAnimationUtils.createCircularReveal(this, x, y, 0, r);
|
||||
a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
|
||||
a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
|
||||
a.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
if (onAnimationEnd != null) {
|
||||
onAnimationEnd.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
a.start();
|
||||
/** Animates in the guts view via either a fade or a circular reveal. */
|
||||
private void animateOpen(
|
||||
boolean shouldDoCircularReveal, int x, int y, @Nullable Runnable onAnimationEnd) {
|
||||
if (shouldDoCircularReveal) {
|
||||
double horz = Math.max(getWidth() - x, x);
|
||||
double vert = Math.max(getHeight() - y, y);
|
||||
float r = (float) Math.hypot(horz, vert);
|
||||
// Circular reveal originating at (x, y)
|
||||
Animator a = ViewAnimationUtils.createCircularReveal(this, x, y, 0, r);
|
||||
a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
|
||||
a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
|
||||
a.addListener(new AnimateOpenListener(onAnimationEnd));
|
||||
a.start();
|
||||
} else {
|
||||
// Fade in content
|
||||
this.setAlpha(0f);
|
||||
this.animate()
|
||||
.alpha(1f)
|
||||
.setDuration(StackStateAnimator.ANIMATION_DURATION_BLOCKING_HELPER_FADE)
|
||||
.setInterpolator(Interpolators.ALPHA_IN)
|
||||
.setListener(new AnimateOpenListener(onAnimationEnd))
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void animateClose(int x, int y) {
|
||||
if (x == -1 || y == -1) {
|
||||
x = (getLeft() + getRight()) / 2;
|
||||
y = (getTop() + getHeight() / 2);
|
||||
}
|
||||
final double horz = Math.max(getWidth() - x, x);
|
||||
final double vert = Math.max(getHeight() - y, y);
|
||||
final float r = (float) Math.hypot(horz, vert);
|
||||
final Animator a = ViewAnimationUtils.createCircularReveal(this,
|
||||
x, y, r, 0);
|
||||
a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
|
||||
a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
|
||||
a.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
setVisibility(View.GONE);
|
||||
|
||||
/** Animates out the guts view via either a fade or a circular reveal. */
|
||||
private void animateClose(int x, int y, boolean shouldDoCircularReveal) {
|
||||
if (shouldDoCircularReveal) {
|
||||
// Circular reveal originating at (x, y)
|
||||
if (x == -1 || y == -1) {
|
||||
x = (getLeft() + getRight()) / 2;
|
||||
y = (getTop() + getHeight() / 2);
|
||||
}
|
||||
});
|
||||
a.start();
|
||||
double horz = Math.max(getWidth() - x, x);
|
||||
double vert = Math.max(getHeight() - y, y);
|
||||
float r = (float) Math.hypot(horz, vert);
|
||||
Animator a = ViewAnimationUtils.createCircularReveal(this,
|
||||
x, y, r, 0);
|
||||
a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
|
||||
a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
|
||||
a.addListener(new AnimateCloseListener(this /* view */));
|
||||
a.start();
|
||||
} else {
|
||||
// Fade in the blocking helper.
|
||||
this.animate()
|
||||
.alpha(0f)
|
||||
.setDuration(StackStateAnimator.ANIMATION_DURATION_BLOCKING_HELPER_FADE)
|
||||
.setInterpolator(Interpolators.ALPHA_OUT)
|
||||
.setListener(new AnimateCloseListener(this /* view */))
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
public void setActualHeight(int actualHeight) {
|
||||
@@ -336,4 +369,36 @@ public class NotificationGuts extends FrameLayout {
|
||||
public boolean isLeavebehind() {
|
||||
return mGutsContent != null && mGutsContent.isLeavebehind();
|
||||
}
|
||||
|
||||
/** Listener for animations executed in {@link #animateOpen(boolean, int, int, Runnable)}. */
|
||||
private static class AnimateOpenListener extends AnimatorListenerAdapter {
|
||||
final Runnable mOnAnimationEnd;
|
||||
|
||||
private AnimateOpenListener(Runnable onAnimationEnd) {
|
||||
mOnAnimationEnd = onAnimationEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
if (mOnAnimationEnd != null) {
|
||||
mOnAnimationEnd.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Listener for animations executed in {@link #animateClose(int, int, boolean)}. */
|
||||
private static class AnimateCloseListener extends AnimatorListenerAdapter {
|
||||
final View mView;
|
||||
|
||||
private AnimateCloseListener(View view) {
|
||||
mView = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
mView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
|
||||
import static android.service.notification.NotificationListenerService.Ranking
|
||||
.USER_SENTIMENT_NEGATIVE;
|
||||
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.INotificationManager;
|
||||
import android.app.NotificationChannel;
|
||||
import android.content.Context;
|
||||
@@ -34,6 +33,7 @@ import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
@@ -119,22 +119,6 @@ public class NotificationGutsManager implements Dumpable {
|
||||
bindGuts(row);
|
||||
}
|
||||
|
||||
private void saveAndCloseNotificationMenu(
|
||||
ExpandableNotificationRow row, NotificationGuts guts, View done) {
|
||||
guts.resetFalsingCheck();
|
||||
int[] rowLocation = new int[2];
|
||||
int[] doneLocation = new int[2];
|
||||
row.getLocationOnScreen(rowLocation);
|
||||
done.getLocationOnScreen(doneLocation);
|
||||
|
||||
final int centerX = done.getWidth() / 2;
|
||||
final int centerY = done.getHeight() / 2;
|
||||
final int x = doneLocation[0] - rowLocation[0] + centerX;
|
||||
final int y = doneLocation[1] - rowLocation[1] + centerY;
|
||||
closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
|
||||
true /* removeControls */, x, y, true /* resetMenu */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an intent to open the app settings for a particular package and optional
|
||||
* channel.
|
||||
@@ -174,12 +158,12 @@ public class NotificationGutsManager implements Dumpable {
|
||||
|
||||
private void bindGuts(final ExpandableNotificationRow row,
|
||||
NotificationMenuRowPlugin.MenuItem item) {
|
||||
StatusBarNotification sbn = row.getStatusBarNotification();
|
||||
|
||||
row.inflateGuts();
|
||||
row.setGutsView(item);
|
||||
final StatusBarNotification sbn = row.getStatusBarNotification();
|
||||
row.setTag(sbn.getPackageName());
|
||||
final NotificationGuts guts = row.getGuts();
|
||||
guts.setClosedListener((NotificationGuts g) -> {
|
||||
row.getGuts().setClosedListener((NotificationGuts g) -> {
|
||||
if (!g.willBeRemoved() && !row.isRemoved()) {
|
||||
mListContainer.onHeightChanged(
|
||||
row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
|
||||
@@ -197,87 +181,143 @@ public class NotificationGutsManager implements Dumpable {
|
||||
|
||||
View gutsView = item.getGutsView();
|
||||
if (gutsView instanceof NotificationSnooze) {
|
||||
NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
|
||||
snoozeGuts.setSnoozeListener(mListContainer.getSwipeActionHelper());
|
||||
snoozeGuts.setStatusBarNotification(sbn);
|
||||
snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
|
||||
guts.setHeightChangedListener((NotificationGuts g) -> {
|
||||
mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
|
||||
});
|
||||
initializeSnoozeView(row, (NotificationSnooze) gutsView);
|
||||
} else if (gutsView instanceof AppOpsInfo) {
|
||||
initializeAppOpsInfo(row, (AppOpsInfo) gutsView);
|
||||
} else if (gutsView instanceof NotificationInfo) {
|
||||
initializeNotificationInfo(row, (NotificationInfo) gutsView);
|
||||
}
|
||||
}
|
||||
|
||||
if (gutsView instanceof AppOpsInfo) {
|
||||
AppOpsInfo info = (AppOpsInfo) gutsView;
|
||||
final UserHandle userHandle = sbn.getUser();
|
||||
PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
|
||||
userHandle.getIdentifier());
|
||||
final AppOpsInfo.OnSettingsClickListener onSettingsClick = (View v,
|
||||
String pkg, int uid, ArraySet<Integer> ops) -> {
|
||||
mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_OPS_GUTS_SETTINGS);
|
||||
guts.resetFalsingCheck();
|
||||
startAppOpsSettingsActivity(pkg, uid, ops, row);
|
||||
};
|
||||
if (!row.getEntry().mActiveAppOps.isEmpty()) {
|
||||
info.bindGuts(pmUser, onSettingsClick, sbn, row.getEntry().mActiveAppOps);
|
||||
}
|
||||
/**
|
||||
* Sets up the {@link NotificationSnooze} inside the notification row's guts.
|
||||
*
|
||||
* @param row view to set up the guts for
|
||||
* @param notificationSnoozeView view to set up/bind within {@code row}
|
||||
*/
|
||||
private void initializeSnoozeView(
|
||||
final ExpandableNotificationRow row,
|
||||
NotificationSnooze notificationSnoozeView) {
|
||||
NotificationGuts guts = row.getGuts();
|
||||
StatusBarNotification sbn = row.getStatusBarNotification();
|
||||
|
||||
notificationSnoozeView.setSnoozeListener(mListContainer.getSwipeActionHelper());
|
||||
notificationSnoozeView.setStatusBarNotification(sbn);
|
||||
notificationSnoozeView.setSnoozeOptions(row.getEntry().snoozeCriteria);
|
||||
guts.setHeightChangedListener((NotificationGuts g) -> {
|
||||
mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the {@link AppOpsInfo} inside the notification row's guts.
|
||||
*
|
||||
* @param row view to set up the guts for
|
||||
* @param appOpsInfoView view to set up/bind within {@code row}
|
||||
*/
|
||||
private void initializeAppOpsInfo(
|
||||
final ExpandableNotificationRow row,
|
||||
AppOpsInfo appOpsInfoView) {
|
||||
NotificationGuts guts = row.getGuts();
|
||||
StatusBarNotification sbn = row.getStatusBarNotification();
|
||||
UserHandle userHandle = sbn.getUser();
|
||||
PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
|
||||
userHandle.getIdentifier());
|
||||
|
||||
AppOpsInfo.OnSettingsClickListener onSettingsClick =
|
||||
(View v, String pkg, int uid, ArraySet<Integer> ops) -> {
|
||||
mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_OPS_GUTS_SETTINGS);
|
||||
guts.resetFalsingCheck();
|
||||
startAppOpsSettingsActivity(pkg, uid, ops, row);
|
||||
};
|
||||
if (!row.getEntry().mActiveAppOps.isEmpty()) {
|
||||
appOpsInfoView.bindGuts(pmUser, onSettingsClick, sbn, row.getEntry().mActiveAppOps);
|
||||
}
|
||||
}
|
||||
|
||||
if (gutsView instanceof NotificationInfo) {
|
||||
final UserHandle userHandle = sbn.getUser();
|
||||
PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
|
||||
userHandle.getIdentifier());
|
||||
final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
|
||||
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
|
||||
final String pkg = sbn.getPackageName();
|
||||
NotificationInfo info = (NotificationInfo) gutsView;
|
||||
// Settings link is only valid for notifications that specify a user, unless this is the
|
||||
// system user.
|
||||
NotificationInfo.OnSettingsClickListener onSettingsClick = null;
|
||||
if (!userHandle.equals(UserHandle.ALL)
|
||||
|| mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
|
||||
onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
|
||||
mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
|
||||
/**
|
||||
* Sets up the {@link NotificationInfo} inside the notification row's guts.
|
||||
*
|
||||
* @param row view to set up the guts for
|
||||
* @param notificationInfoView view to set up/bind within {@code row}
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void initializeNotificationInfo(
|
||||
final ExpandableNotificationRow row,
|
||||
NotificationInfo notificationInfoView) {
|
||||
NotificationGuts guts = row.getGuts();
|
||||
StatusBarNotification sbn = row.getStatusBarNotification();
|
||||
String packageName = sbn.getPackageName();
|
||||
// Settings link is only valid for notifications that specify a non-system user
|
||||
NotificationInfo.OnSettingsClickListener onSettingsClick = null;
|
||||
UserHandle userHandle = sbn.getUser();
|
||||
PackageManager pmUser = StatusBar.getPackageManagerForUser(
|
||||
mContext, userHandle.getIdentifier());
|
||||
INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
|
||||
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
|
||||
final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick =
|
||||
(View v, Intent intent) -> {
|
||||
mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
|
||||
guts.resetFalsingCheck();
|
||||
mOnSettingsClickListener.onClick(sbn.getKey());
|
||||
startAppNotificationSettingsActivity(pkg, appUid, channel, row);
|
||||
mPresenter.startNotificationGutsIntent(intent, sbn.getUid(), row);
|
||||
};
|
||||
}
|
||||
final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
|
||||
Intent intent) -> {
|
||||
mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
|
||||
guts.resetFalsingCheck();
|
||||
mPresenter.startNotificationGutsIntent(intent, sbn.getUid(), row);
|
||||
};
|
||||
final View.OnClickListener onDoneClick = (View v) -> {
|
||||
saveAndCloseNotificationMenu(row, guts, v);
|
||||
};
|
||||
boolean isForBlockingHelper = row.isBlockingHelperShowing();
|
||||
|
||||
ArraySet<NotificationChannel> channels = new ArraySet<>();
|
||||
channels.add(row.getEntry().channel);
|
||||
if (row.isSummaryWithChildren()) {
|
||||
// If this is a summary, then add in the children notification channels for the
|
||||
// same user and pkg.
|
||||
final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
|
||||
final int numChildren = childrenRows.size();
|
||||
for (int i = 0; i < numChildren; i++) {
|
||||
final ExpandableNotificationRow childRow = childrenRows.get(i);
|
||||
final NotificationChannel childChannel = childRow.getEntry().channel;
|
||||
final StatusBarNotification childSbn = childRow.getStatusBarNotification();
|
||||
if (childSbn.getUser().equals(userHandle) &&
|
||||
childSbn.getPackageName().equals(pkg)) {
|
||||
channels.add(childChannel);
|
||||
}
|
||||
if (!userHandle.equals(UserHandle.ALL)
|
||||
|| mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
|
||||
onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
|
||||
mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
|
||||
guts.resetFalsingCheck();
|
||||
mOnSettingsClickListener.onClick(sbn.getKey());
|
||||
startAppNotificationSettingsActivity(packageName, appUid, channel, row);
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
notificationInfoView.bindNotification(
|
||||
pmUser,
|
||||
iNotificationManager,
|
||||
packageName,
|
||||
row.getEntry().channel,
|
||||
getNumNotificationChannels(row, packageName, userHandle),
|
||||
sbn,
|
||||
mCheckSaveListener,
|
||||
onSettingsClick,
|
||||
onAppSettingsClick,
|
||||
mNonBlockablePkgs,
|
||||
isForBlockingHelper,
|
||||
row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of channels covered by the notification row (including its children if
|
||||
* it's a summary notification).
|
||||
*/
|
||||
private int getNumNotificationChannels(
|
||||
ExpandableNotificationRow row, String packageName, UserHandle userHandle) {
|
||||
ArraySet<NotificationChannel> channels = new ArraySet<>();
|
||||
|
||||
channels.add(row.getEntry().channel);
|
||||
|
||||
// If this is a summary, then add in the children notification channels for the
|
||||
// same user and pkg.
|
||||
if (row.isSummaryWithChildren()) {
|
||||
final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
|
||||
final int numChildren = childrenRows.size();
|
||||
for (int i = 0; i < numChildren; i++) {
|
||||
final ExpandableNotificationRow childRow = childrenRows.get(i);
|
||||
final NotificationChannel childChannel = childRow.getEntry().channel;
|
||||
final StatusBarNotification childSbn = childRow.getStatusBarNotification();
|
||||
if (childSbn.getUser().equals(userHandle) &&
|
||||
childSbn.getPackageName().equals(packageName)) {
|
||||
channels.add(childChannel);
|
||||
}
|
||||
}
|
||||
try {
|
||||
info.bindNotification(pmUser, iNotificationManager, pkg, row.getEntry().channel,
|
||||
channels.size(), sbn, mCheckSaveListener, onSettingsClick,
|
||||
onAppSettingsClick, mNonBlockablePkgs,
|
||||
row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
}
|
||||
}
|
||||
return channels.size();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,37 +352,42 @@ public class NotificationGutsManager implements Dumpable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens guts on the given ExpandableNotificationRow |v|.
|
||||
* Opens guts on the given ExpandableNotificationRow {@code view}. This handles opening guts for
|
||||
* the normal half-swipe and long-press use cases via a circular reveal. When the blocking
|
||||
* helper needs to be shown on the row, this will skip the circular reveal.
|
||||
*
|
||||
* @param v ExpandableNotificationRow to open guts on
|
||||
* @param view ExpandableNotificationRow to open guts on
|
||||
* @param x x coordinate of origin of circular reveal
|
||||
* @param y y coordinate of origin of circular reveal
|
||||
* @param item MenuItem the guts should display
|
||||
* @param menuItem MenuItem the guts should display
|
||||
* @return true if guts was opened
|
||||
*/
|
||||
public boolean openGuts(View v, int x, int y,
|
||||
NotificationMenuRowPlugin.MenuItem item) {
|
||||
if (!(v instanceof ExpandableNotificationRow)) {
|
||||
boolean openGuts(
|
||||
View view,
|
||||
int x,
|
||||
int y,
|
||||
NotificationMenuRowPlugin.MenuItem menuItem) {
|
||||
if (!(view instanceof ExpandableNotificationRow)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (v.getWindowToken() == null) {
|
||||
if (view.getWindowToken() == null) {
|
||||
Log.e(TAG, "Trying to show notification guts, but not attached to window");
|
||||
return false;
|
||||
}
|
||||
|
||||
final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
|
||||
final ExpandableNotificationRow row = (ExpandableNotificationRow) view;
|
||||
if (row.isDark()) {
|
||||
return false;
|
||||
}
|
||||
v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
if (row.areGutsExposed()) {
|
||||
closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
|
||||
true /* removeControls */, -1 /* x */, -1 /* y */,
|
||||
true /* resetMenu */);
|
||||
return false;
|
||||
}
|
||||
bindGuts(row, item);
|
||||
bindGuts(row, menuItem);
|
||||
NotificationGuts guts = row.getGuts();
|
||||
|
||||
// Assume we are a status_bar_notification_row
|
||||
@@ -372,15 +417,18 @@ public class NotificationGutsManager implements Dumpable {
|
||||
final boolean needsFalsingProtection =
|
||||
(mPresenter.isPresenterLocked() &&
|
||||
!mAccessibilityManager.isTouchExplorationEnabled());
|
||||
guts.openControls(x, y, needsFalsingProtection, () -> {
|
||||
// Move the notification view back over the menu
|
||||
row.resetTranslation();
|
||||
});
|
||||
|
||||
guts.openControls(
|
||||
!row.isBlockingHelperShowing(),
|
||||
x,
|
||||
y,
|
||||
needsFalsingProtection,
|
||||
row::resetTranslation);
|
||||
|
||||
row.closeRemoteInput();
|
||||
mListContainer.onHeightChanged(row, true /* needsAnimation */);
|
||||
mNotificationGutsExposed = guts;
|
||||
mGutsMenuItem = item;
|
||||
mGutsMenuItem = menuItem;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
|
||||
@@ -46,9 +46,11 @@ import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.Interpolators;
|
||||
import com.android.systemui.R;
|
||||
|
||||
@@ -81,11 +83,12 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
|
||||
private OnSettingsClickListener mOnSettingsClickListener;
|
||||
private OnAppSettingsClickListener mAppSettingsClickListener;
|
||||
private NotificationGuts mGutsContainer;
|
||||
|
||||
/** Whether this view is being shown as part of the blocking helper */
|
||||
private boolean mIsForBlockingHelper;
|
||||
private boolean mNegativeUserSentiment;
|
||||
|
||||
private OnClickListener mOnKeepShowing = v -> {
|
||||
closeControls(v);
|
||||
};
|
||||
private OnClickListener mOnKeepShowing = this::closeControls;
|
||||
|
||||
private OnClickListener mOnStopMinNotifications = v -> {
|
||||
swapContent(false);
|
||||
@@ -114,7 +117,9 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
|
||||
void onClick(View v, Intent intent);
|
||||
}
|
||||
|
||||
public void bindNotification(final PackageManager pm,
|
||||
@VisibleForTesting
|
||||
void bindNotification(
|
||||
final PackageManager pm,
|
||||
final INotificationManager iNotificationManager,
|
||||
final String pkg,
|
||||
final NotificationChannel notificationChannel,
|
||||
@@ -127,20 +132,24 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
|
||||
throws RemoteException {
|
||||
bindNotification(pm, iNotificationManager, pkg, notificationChannel, numChannels, sbn,
|
||||
checkSaveListener, onSettingsClick, onAppSettingsClick, nonBlockablePkgs,
|
||||
false /* negative sentiment */);
|
||||
false /* isBlockingHelper */,
|
||||
false /* isUserSentimentNegative */);
|
||||
}
|
||||
|
||||
public void bindNotification(final PackageManager pm,
|
||||
final INotificationManager iNotificationManager,
|
||||
final String pkg,
|
||||
final NotificationChannel notificationChannel,
|
||||
final int numChannels,
|
||||
final StatusBarNotification sbn,
|
||||
final CheckSaveListener checkSaveListener,
|
||||
final OnSettingsClickListener onSettingsClick,
|
||||
final OnAppSettingsClickListener onAppSettingsClick,
|
||||
final Set<String> nonBlockablePkgs,
|
||||
boolean negativeUserSentiment) throws RemoteException {
|
||||
public void bindNotification(
|
||||
PackageManager pm,
|
||||
INotificationManager iNotificationManager,
|
||||
String pkg,
|
||||
NotificationChannel notificationChannel,
|
||||
int numChannels,
|
||||
StatusBarNotification sbn,
|
||||
CheckSaveListener checkSaveListener,
|
||||
OnSettingsClickListener onSettingsClick,
|
||||
OnAppSettingsClickListener onAppSettingsClick,
|
||||
Set<String> nonBlockablePkgs,
|
||||
boolean isForBlockingHelper,
|
||||
boolean isUserSentimentNegative)
|
||||
throws RemoteException {
|
||||
mINotificationManager = iNotificationManager;
|
||||
mPkg = pkg;
|
||||
mNumNotificationChannels = numChannels;
|
||||
@@ -152,9 +161,10 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
|
||||
mOnSettingsClickListener = onSettingsClick;
|
||||
mSingleNotificationChannel = notificationChannel;
|
||||
mStartingUserImportance = mChosenImportance = mSingleNotificationChannel.getImportance();
|
||||
mNegativeUserSentiment = negativeUserSentiment;
|
||||
mNegativeUserSentiment = isUserSentimentNegative;
|
||||
mIsForeground =
|
||||
(mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
|
||||
mIsForBlockingHelper = isForBlockingHelper;
|
||||
|
||||
int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
|
||||
pkg, mAppUid, false /* includeDeleted */);
|
||||
@@ -294,12 +304,14 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
|
||||
}
|
||||
|
||||
private void bindButtons() {
|
||||
// Set up stay-in-notification actions
|
||||
View block = findViewById(R.id.block);
|
||||
block.setOnClickListener(mOnStopMinNotifications);
|
||||
TextView keep = findViewById(R.id.keep);
|
||||
keep.setOnClickListener(mOnKeepShowing);
|
||||
findViewById(R.id.undo).setOnClickListener(mOnUndo);
|
||||
View minimize = findViewById(R.id.minimize);
|
||||
|
||||
findViewById(R.id.undo).setOnClickListener(mOnUndo);
|
||||
block.setOnClickListener(mOnStopMinNotifications);
|
||||
keep.setOnClickListener(mOnKeepShowing);
|
||||
minimize.setOnClickListener(mOnStopMinNotifications);
|
||||
|
||||
if (mNonblockable) {
|
||||
@@ -314,7 +326,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
|
||||
minimize.setVisibility(GONE);
|
||||
}
|
||||
|
||||
// app settings link
|
||||
// Set up app settings link
|
||||
TextView settingsLinkView = findViewById(R.id.app_settings);
|
||||
Intent settingsIntent = getAppSettingsIntent(mPm, mPkg, mSingleNotificationChannel,
|
||||
mSbn.getId(), mSbn.getTag());
|
||||
@@ -421,16 +433,23 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
|
||||
return intent;
|
||||
}
|
||||
|
||||
private void closeControls(View v) {
|
||||
int[] parentLoc = new int[2];
|
||||
int[] targetLoc = new int[2];
|
||||
mGutsContainer.getLocationOnScreen(parentLoc);
|
||||
v.getLocationOnScreen(targetLoc);
|
||||
final int centerX = v.getWidth() / 2;
|
||||
final int centerY = v.getHeight() / 2;
|
||||
final int x = targetLoc[0] - parentLoc[0] + centerX;
|
||||
final int y = targetLoc[1] - parentLoc[1] + centerY;
|
||||
mGutsContainer.closeControls(x, y, true /* save */, false /* force */);
|
||||
@VisibleForTesting
|
||||
void closeControls(View v) {
|
||||
if (mIsForBlockingHelper) {
|
||||
NotificationBlockingHelperManager manager =
|
||||
Dependency.get(NotificationBlockingHelperManager.class);
|
||||
manager.dismissCurrentBlockingHelper();
|
||||
} else {
|
||||
int[] parentLoc = new int[2];
|
||||
int[] targetLoc = new int[2];
|
||||
mGutsContainer.getLocationOnScreen(parentLoc);
|
||||
v.getLocationOnScreen(targetLoc);
|
||||
final int centerX = v.getWidth() / 2;
|
||||
final int centerY = v.getHeight() / 2;
|
||||
final int x = targetLoc[0] - parentLoc[0] + centerX;
|
||||
final int y = targetLoc[1] - parentLoc[1] + centerY;
|
||||
mGutsContainer.closeControls(x, y, true /* save */, false /* force */);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -227,7 +227,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
expandAmount = Math.min(1.0f, expandAmount);
|
||||
}
|
||||
// find the first view that doesn't overlap with the shelf
|
||||
int notificationIndex = 0;
|
||||
int notGoneIndex = 0;
|
||||
int colorOfViewBeforeLast = NO_COLOR;
|
||||
boolean backgroundForceHidden = false;
|
||||
@@ -247,13 +246,15 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
int baseZHeight = mAmbientState.getBaseZHeight();
|
||||
int backgroundTop = 0;
|
||||
float firstElementRoundness = 0.0f;
|
||||
while (notificationIndex < mHostLayout.getChildCount()) {
|
||||
ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
|
||||
notificationIndex++;
|
||||
|
||||
for (int i = 0; i < mHostLayout.getChildCount(); i++) {
|
||||
ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
|
||||
|
||||
if (!(child instanceof ExpandableNotificationRow)
|
||||
|| child.getVisibility() == GONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
|
||||
float notificationClipEnd;
|
||||
boolean aboveShelf = ViewState.getFinalTranslationZ(row) > baseZHeight
|
||||
@@ -315,6 +316,9 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
notGoneIndex++;
|
||||
previousColor = ownColorUntinted;
|
||||
}
|
||||
|
||||
clipTransientViews();
|
||||
|
||||
setBackgroundTop(backgroundTop);
|
||||
setFirstElementRoundness(firstElementRoundness);
|
||||
mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
|
||||
@@ -337,6 +341,25 @@ public class NotificationShelf extends ActivatableNotificationView implements
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clips transient views to the top of the shelf - Transient views are only used for
|
||||
* disappearing views/animations and need to be clipped correctly by the shelf to ensure they
|
||||
* don't show underneath the notification stack when something is animating and the user
|
||||
* swipes quickly.
|
||||
*/
|
||||
private void clipTransientViews() {
|
||||
for (int i = 0; i < mHostLayout.getTransientViewCount(); i++) {
|
||||
View transientView = mHostLayout.getTransientView(i);
|
||||
if (transientView instanceof ExpandableNotificationRow) {
|
||||
ExpandableNotificationRow transientRow = (ExpandableNotificationRow) transientView;
|
||||
updateNotificationClipHeight(transientRow, getTranslationY());
|
||||
} else {
|
||||
Log.e(TAG, "NotificationShelf.clipTransientViews(): "
|
||||
+ "Trying to clip non-row transient view");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setFirstElementRoundness(float firstElementRoundness) {
|
||||
if (mFirstElementRoundness != firstElementRoundness) {
|
||||
mFirstElementRoundness = firstElementRoundness;
|
||||
|
||||
@@ -125,24 +125,30 @@ public class NotificationViewHierarchyManager {
|
||||
|
||||
}
|
||||
|
||||
ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
|
||||
ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>();
|
||||
for (int i=0; i< mListContainer.getContainerChildCount(); i++) {
|
||||
View child = mListContainer.getContainerChildAt(i);
|
||||
if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) {
|
||||
toRemove.add((ExpandableNotificationRow) child);
|
||||
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
|
||||
|
||||
// Blocking helper is effectively a detached view. Don't bother removing it from the
|
||||
// layout.
|
||||
if (!row.isBlockingHelperShowing()) {
|
||||
viewsToRemove.add((ExpandableNotificationRow) child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ExpandableNotificationRow remove : toRemove) {
|
||||
if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
|
||||
for (ExpandableNotificationRow viewToRemove : viewsToRemove) {
|
||||
if (mGroupManager.isChildInGroupWithSummary(viewToRemove.getStatusBarNotification())) {
|
||||
// we are only transferring this notification to its parent, don't generate an
|
||||
// animation
|
||||
mListContainer.setChildTransferInProgress(true);
|
||||
}
|
||||
if (remove.isSummaryWithChildren()) {
|
||||
remove.removeAllChildren();
|
||||
if (viewToRemove.isSummaryWithChildren()) {
|
||||
viewToRemove.removeAllChildren();
|
||||
}
|
||||
mListContainer.removeContainerView(remove);
|
||||
mListContainer.removeContainerView(viewToRemove);
|
||||
mListContainer.setChildTransferInProgress(false);
|
||||
}
|
||||
|
||||
@@ -168,6 +174,10 @@ public class NotificationViewHierarchyManager {
|
||||
// We don't care about non-notification views.
|
||||
continue;
|
||||
}
|
||||
if (((ExpandableNotificationRow) child).isBlockingHelperShowing()) {
|
||||
// Don't count/reorder notifications that are showing the blocking helper!
|
||||
continue;
|
||||
}
|
||||
|
||||
ExpandableNotificationRow targetChild = toShow.get(j);
|
||||
if (child != targetChild) {
|
||||
@@ -340,7 +350,7 @@ public class NotificationViewHierarchyManager {
|
||||
}
|
||||
}
|
||||
|
||||
row.showBlockingHelper(entry.userSentiment ==
|
||||
row.showBlockingHelperButton(entry.userSentiment ==
|
||||
NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
|
||||
|
||||
row.showAppOpsIcons(entry.mActiveAppOps);
|
||||
|
||||
@@ -169,7 +169,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener {
|
||||
if (group.suppressed) {
|
||||
handleSuppressedSummaryHeadsUpped(group.summary);
|
||||
}
|
||||
if (!mIsUpdatingUnchangedGroup) {
|
||||
if (!mIsUpdatingUnchangedGroup && mListener != null) {
|
||||
mListener.onGroupsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ import android.widget.ScrollView;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.ExpandHelper;
|
||||
import com.android.systemui.Interpolators;
|
||||
import com.android.systemui.R;
|
||||
@@ -83,6 +84,7 @@ import com.android.systemui.statusbar.EmptyShadeView;
|
||||
import com.android.systemui.statusbar.ExpandableNotificationRow;
|
||||
import com.android.systemui.statusbar.ExpandableView;
|
||||
import com.android.systemui.statusbar.FooterView;
|
||||
import com.android.systemui.statusbar.NotificationBlockingHelperManager;
|
||||
import com.android.systemui.statusbar.NotificationData;
|
||||
import com.android.systemui.statusbar.NotificationGuts;
|
||||
import com.android.systemui.statusbar.NotificationListContainer;
|
||||
@@ -448,6 +450,13 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
mRoundnessManager.setOnRoundingChangedCallback(this::invalidate);
|
||||
addOnExpandedHeightListener(mRoundnessManager::setExpanded);
|
||||
|
||||
// Blocking helper manager wants to know the expanded state, update as well.
|
||||
NotificationBlockingHelperManager blockingHelperManager =
|
||||
Dependency.get(NotificationBlockingHelperManager.class);
|
||||
addOnExpandedHeightListener((height, unused) -> {
|
||||
blockingHelperManager.setNotificationShadeExpanded(height);
|
||||
});
|
||||
|
||||
updateWillNotDraw();
|
||||
mBackgroundPaint.setAntiAlias(true);
|
||||
if (DEBUG) {
|
||||
@@ -1038,45 +1047,63 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
mQsContainer = qsContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles cleanup after the given {@code view} has been fully swiped out (including
|
||||
* re-invoking dismiss logic in case the notification has not made its way out yet).
|
||||
*/
|
||||
@Override
|
||||
public void onChildDismissed(View v) {
|
||||
ExpandableNotificationRow row = (ExpandableNotificationRow) v;
|
||||
public void onChildDismissed(View view) {
|
||||
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
|
||||
if (!row.isDismissed()) {
|
||||
handleChildDismissed(v);
|
||||
handleChildViewDismissed(view);
|
||||
}
|
||||
ViewGroup transientContainer = row.getTransientContainer();
|
||||
if (transientContainer != null) {
|
||||
transientContainer.removeTransientView(v);
|
||||
transientContainer.removeTransientView(view);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleChildDismissed(View v) {
|
||||
/**
|
||||
* Starts up notification dismiss and tells the notification, if any, to remove itself from
|
||||
* layout.
|
||||
*
|
||||
* @param view view (e.g. notification) to dismiss from the layout
|
||||
*/
|
||||
private void handleChildViewDismissed(View view) {
|
||||
if (mDismissAllInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isBlockingHelperShown = false;
|
||||
|
||||
setSwipingInProgress(false);
|
||||
if (mDragAnimPendingChildren.contains(v)) {
|
||||
// We start the swipe and finish it in the same frame, we don't want any animation
|
||||
// for the drag
|
||||
mDragAnimPendingChildren.remove(v);
|
||||
if (mDragAnimPendingChildren.contains(view)) {
|
||||
// We start the swipe and finish it in the same frame; we don't want a drag animation.
|
||||
mDragAnimPendingChildren.remove(view);
|
||||
}
|
||||
mSwipedOutViews.add(v);
|
||||
mAmbientState.onDragFinished(v);
|
||||
mAmbientState.onDragFinished(view);
|
||||
updateContinuousShadowDrawing();
|
||||
if (v instanceof ExpandableNotificationRow) {
|
||||
ExpandableNotificationRow row = (ExpandableNotificationRow) v;
|
||||
|
||||
if (view instanceof ExpandableNotificationRow) {
|
||||
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
|
||||
if (row.isHeadsUp()) {
|
||||
mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
|
||||
}
|
||||
}
|
||||
if (v instanceof ExpandableNotificationRow) {
|
||||
((ExpandableNotificationRow) v).performDismiss(false /* fromAccessibility */);
|
||||
isBlockingHelperShown =
|
||||
row.performDismissWithBlockingHelper(false /* fromAccessibility */);
|
||||
}
|
||||
|
||||
if (!isBlockingHelperShown) {
|
||||
mSwipedOutViews.add(view);
|
||||
}
|
||||
mFalsingManager.onNotificationDismissed();
|
||||
if (mFalsingManager.shouldEnforceBouncer()) {
|
||||
mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
|
||||
false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
|
||||
mStatusBar.executeRunnableDismissingKeyguard(
|
||||
null,
|
||||
null /* cancelAction */,
|
||||
false /* dismissShade */,
|
||||
true /* afterKeyguardGone */,
|
||||
false /* deferred */);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2729,9 +2756,8 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
updateScrollStateForRemovedChild(expandableView);
|
||||
boolean animationGenerated = generateRemoveAnimation(child);
|
||||
if (animationGenerated) {
|
||||
if (!mSwipedOutViews.contains(child)) {
|
||||
container.getOverlay().add(child);
|
||||
} else if (Math.abs(expandableView.getTranslation()) != expandableView.getWidth()) {
|
||||
if (!mSwipedOutViews.contains(child)
|
||||
|| Math.abs(expandableView.getTranslation()) != expandableView.getWidth()) {
|
||||
container.addTransientView(child, 0);
|
||||
expandableView.setTransientContainer(container);
|
||||
}
|
||||
@@ -4618,7 +4644,7 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
if (mIsExpanded) {
|
||||
// We don't want to quick-dismiss when it's a heads up as this might lead to closing
|
||||
// of the panel early.
|
||||
handleChildDismissed(view);
|
||||
handleChildViewDismissed(view);
|
||||
}
|
||||
mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
|
||||
false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
|
||||
|
||||
@@ -51,6 +51,7 @@ public class StackStateAnimator {
|
||||
= (int) (ANIMATION_DURATION_HEADS_UP_APPEAR
|
||||
* HeadsUpAppearInterpolator.getFractionUntilOvershoot());
|
||||
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 300;
|
||||
public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
|
||||
public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
|
||||
public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
|
||||
public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
|
||||
@@ -79,6 +80,7 @@ public class StackStateAnimator {
|
||||
private ValueAnimator mBottomOverScrollAnimator;
|
||||
private int mHeadsUpAppearHeightBottom;
|
||||
private boolean mShadeExpanded;
|
||||
private ArrayList<ExpandableView> mTransientViewsToRemove = new ArrayList<>();
|
||||
private NotificationShelf mShelf;
|
||||
private float mStatusBarIconLocation;
|
||||
private int[] mTmpLocation = new int[2];
|
||||
@@ -333,6 +335,12 @@ public class StackStateAnimator {
|
||||
|
||||
private void onAnimationFinished() {
|
||||
mHostLayout.onChildAnimationFinished();
|
||||
|
||||
for (ExpandableView transientViewsToRemove : mTransientViewsToRemove) {
|
||||
transientViewsToRemove.getTransientContainer()
|
||||
.removeTransientView(transientViewsToRemove);
|
||||
}
|
||||
mTransientViewsToRemove.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -362,7 +370,7 @@ public class StackStateAnimator {
|
||||
} else if (event.animationType ==
|
||||
NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
|
||||
if (changingView.getVisibility() != View.VISIBLE) {
|
||||
removeFromOverlay(changingView);
|
||||
removeTransientView(changingView);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -402,8 +410,7 @@ public class StackStateAnimator {
|
||||
0, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// remove the temporary overlay
|
||||
removeFromOverlay(changingView);
|
||||
removeTransientView(changingView);
|
||||
}
|
||||
}, null);
|
||||
} else if (event.animationType ==
|
||||
@@ -494,6 +501,12 @@ public class StackStateAnimator {
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeTransientView(ExpandableView viewToRemove) {
|
||||
if (viewToRemove.getTransientContainer() != null) {
|
||||
viewToRemove.getTransientContainer().removeTransientView(viewToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeFromOverlay(View changingView) {
|
||||
ViewGroup parent = (ViewGroup) changingView.getParent();
|
||||
if (parent != null) {
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.systemui.statusbar;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
@@ -40,8 +42,13 @@ import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -49,52 +56,59 @@ import java.util.function.Consumer;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExpandableNotificationRowTest extends SysuiTestCase {
|
||||
|
||||
private ExpandableNotificationRow mGroup;
|
||||
private ExpandableNotificationRow mGroupRow;
|
||||
|
||||
private NotificationTestHelper mNotificationTestHelper;
|
||||
boolean mHeadsUpAnimatingAway = false;
|
||||
|
||||
@Rule public MockitoRule mockito = MockitoJUnit.rule();
|
||||
@Mock private NotificationBlockingHelperManager mBlockingHelperManager;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mNotificationTestHelper = new NotificationTestHelper(mContext);
|
||||
mGroup = mNotificationTestHelper.createGroup();
|
||||
mGroup.setHeadsUpAnimatingAwayListener(
|
||||
mGroupRow = mNotificationTestHelper.createGroup();
|
||||
mGroupRow.setHeadsUpAnimatingAwayListener(
|
||||
animatingAway -> mHeadsUpAnimatingAway = animatingAway);
|
||||
mDependency.injectTestDependency(
|
||||
NotificationBlockingHelperManager.class,
|
||||
mBlockingHelperManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupSummaryNotShowingIconWhenPublic() {
|
||||
mGroup.setSensitive(true, true);
|
||||
mGroup.setHideSensitiveForIntrinsicHeight(true);
|
||||
Assert.assertTrue(mGroup.isSummaryWithChildren());
|
||||
Assert.assertFalse(mGroup.isShowingIcon());
|
||||
mGroupRow.setSensitive(true, true);
|
||||
mGroupRow.setHideSensitiveForIntrinsicHeight(true);
|
||||
assertTrue(mGroupRow.isSummaryWithChildren());
|
||||
assertFalse(mGroupRow.isShowingIcon());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotificationHeaderVisibleWhenAnimating() {
|
||||
mGroup.setSensitive(true, true);
|
||||
mGroup.setHideSensitive(true, false, 0, 0);
|
||||
mGroup.setHideSensitive(false, true, 0, 0);
|
||||
Assert.assertTrue(mGroup.getChildrenContainer().getVisibleHeader().getVisibility()
|
||||
mGroupRow.setSensitive(true, true);
|
||||
mGroupRow.setHideSensitive(true, false, 0, 0);
|
||||
mGroupRow.setHideSensitive(false, true, 0, 0);
|
||||
assertTrue(mGroupRow.getChildrenContainer().getVisibleHeader().getVisibility()
|
||||
== View.VISIBLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserLockedResetEvenWhenNoChildren() {
|
||||
mGroup.setUserLocked(true);
|
||||
mGroup.removeAllChildren();
|
||||
mGroup.setUserLocked(false);
|
||||
Assert.assertFalse("The childrencontainer should not be userlocked but is, the state "
|
||||
+ "seems out of sync.", mGroup.getChildrenContainer().isUserLocked());
|
||||
mGroupRow.setUserLocked(true);
|
||||
mGroupRow.removeAllChildren();
|
||||
mGroupRow.setUserLocked(false);
|
||||
assertFalse("The childrencontainer should not be userlocked but is, the state "
|
||||
+ "seems out of sync.", mGroupRow.getChildrenContainer().isUserLocked());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReinflatedOnDensityChange() {
|
||||
mGroup.setUserLocked(true);
|
||||
mGroup.removeAllChildren();
|
||||
mGroup.setUserLocked(false);
|
||||
mGroupRow.setUserLocked(true);
|
||||
mGroupRow.removeAllChildren();
|
||||
mGroupRow.setUserLocked(false);
|
||||
NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
|
||||
mGroup.setChildrenContainer(mockContainer);
|
||||
mGroup.onDensityOrFontScaleChanged();
|
||||
mGroupRow.setChildrenContainer(mockContainer);
|
||||
mGroupRow.onDensityOrFontScaleChanged();
|
||||
verify(mockContainer).reInflateViews(any(), any());
|
||||
}
|
||||
|
||||
@@ -151,38 +165,38 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void testClickSound() throws Exception {
|
||||
Assert.assertTrue("Should play sounds by default.", mGroup.isSoundEffectsEnabled());
|
||||
mGroup.setDark(true /* dark */, false /* fade */, 0 /* delay */);
|
||||
mGroup.setSecureStateProvider(()-> false);
|
||||
Assert.assertFalse("Shouldn't play sounds when dark and trusted.",
|
||||
mGroup.isSoundEffectsEnabled());
|
||||
mGroup.setSecureStateProvider(()-> true);
|
||||
Assert.assertTrue("Should always play sounds when not trusted.",
|
||||
mGroup.isSoundEffectsEnabled());
|
||||
assertTrue("Should play sounds by default.", mGroupRow.isSoundEffectsEnabled());
|
||||
mGroupRow.setDark(true /* dark */, false /* fade */, 0 /* delay */);
|
||||
mGroupRow.setSecureStateProvider(()-> false);
|
||||
assertFalse("Shouldn't play sounds when dark and trusted.",
|
||||
mGroupRow.isSoundEffectsEnabled());
|
||||
mGroupRow.setSecureStateProvider(()-> true);
|
||||
assertTrue("Should always play sounds when not trusted.",
|
||||
mGroupRow.isSoundEffectsEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetDismissed_longPressListenerRemoved() {
|
||||
ExpandableNotificationRow.LongPressListener listener =
|
||||
mock(ExpandableNotificationRow.LongPressListener.class);
|
||||
mGroup.setLongPressListener(listener);
|
||||
mGroup.doLongClickCallback(0,0);
|
||||
verify(listener, times(1)).onLongPress(eq(mGroup), eq(0), eq(0),
|
||||
mGroupRow.setLongPressListener(listener);
|
||||
mGroupRow.doLongClickCallback(0,0);
|
||||
verify(listener, times(1)).onLongPress(eq(mGroupRow), eq(0), eq(0),
|
||||
any(NotificationMenuRowPlugin.MenuItem.class));
|
||||
reset(listener);
|
||||
|
||||
mGroup.setDismissed(true);
|
||||
mGroup.doLongClickCallback(0,0);
|
||||
verify(listener, times(0)).onLongPress(eq(mGroup), eq(0), eq(0),
|
||||
mGroupRow.setDismissed(true);
|
||||
mGroupRow.doLongClickCallback(0,0);
|
||||
verify(listener, times(0)).onLongPress(eq(mGroupRow), eq(0), eq(0),
|
||||
any(NotificationMenuRowPlugin.MenuItem.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShowAppOps_noHeader() {
|
||||
// public notification is custom layout - no header
|
||||
mGroup.setSensitive(true, true);
|
||||
mGroup.setAppOpsOnClickListener(null);
|
||||
mGroup.showAppOpsIcons(null);
|
||||
mGroupRow.setSensitive(true, true);
|
||||
mGroupRow.setAppOpsOnClickListener(null);
|
||||
mGroupRow.showAppOpsIcons(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -190,17 +204,17 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
|
||||
NotificationHeaderView mockHeader = mock(NotificationHeaderView.class);
|
||||
|
||||
NotificationContentView publicLayout = mock(NotificationContentView.class);
|
||||
mGroup.setPublicLayout(publicLayout);
|
||||
mGroupRow.setPublicLayout(publicLayout);
|
||||
NotificationContentView privateLayout = mock(NotificationContentView.class);
|
||||
mGroup.setPrivateLayout(privateLayout);
|
||||
mGroupRow.setPrivateLayout(privateLayout);
|
||||
NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
|
||||
when(mockContainer.getNotificationChildCount()).thenReturn(1);
|
||||
when(mockContainer.getHeaderView()).thenReturn(mockHeader);
|
||||
mGroup.setChildrenContainer(mockContainer);
|
||||
mGroupRow.setChildrenContainer(mockContainer);
|
||||
|
||||
ArraySet<Integer> ops = new ArraySet<>();
|
||||
ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS);
|
||||
mGroup.showAppOpsIcons(ops);
|
||||
mGroupRow.showAppOpsIcons(ops);
|
||||
|
||||
verify(mockHeader, times(1)).showAppOpsIcons(ops);
|
||||
verify(privateLayout, times(1)).showAppOpsIcons(ops);
|
||||
@@ -214,17 +228,50 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
|
||||
ExpandableNotificationRow.OnAppOpsClickListener.class);
|
||||
View view = mock(View.class);
|
||||
|
||||
mGroup.setAppOpsOnClickListener(l);
|
||||
mGroupRow.setAppOpsOnClickListener(l);
|
||||
|
||||
mGroup.getAppOpsOnClickListener().onClick(view);
|
||||
mGroupRow.getAppOpsOnClickListener().onClick(view);
|
||||
verify(l, times(1)).onClick(any(), anyInt(), anyInt(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeadsUpAnimatingAwayListener() {
|
||||
mGroup.setHeadsUpAnimatingAway(true);
|
||||
mGroupRow.setHeadsUpAnimatingAway(true);
|
||||
Assert.assertEquals(true, mHeadsUpAnimatingAway);
|
||||
mGroup.setHeadsUpAnimatingAway(false);
|
||||
mGroupRow.setHeadsUpAnimatingAway(false);
|
||||
Assert.assertEquals(false, mHeadsUpAnimatingAway);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerformDismissWithBlockingHelper_falseWhenBlockingHelperIsntShown() {
|
||||
when(mBlockingHelperManager.perhapsShowBlockingHelper(
|
||||
eq(mGroupRow), any(NotificationMenuRowPlugin.class))).thenReturn(false);
|
||||
|
||||
assertFalse(
|
||||
mGroupRow.performDismissWithBlockingHelper(false /* fromAccessibility */));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerformDismissWithBlockingHelper_doesntPerformOnGroupSummary() {
|
||||
ExpandableNotificationRow childRow = mGroupRow.getChildrenContainer().getViewAtPosition(0);
|
||||
when(mBlockingHelperManager.perhapsShowBlockingHelper(eq(childRow), any(NotificationMenuRowPlugin.class)))
|
||||
.thenReturn(true);
|
||||
|
||||
assertTrue(
|
||||
childRow.performDismissWithBlockingHelper(false /* fromAccessibility */));
|
||||
|
||||
verify(mBlockingHelperManager, times(1))
|
||||
.perhapsShowBlockingHelper(eq(childRow), any(NotificationMenuRowPlugin.class));
|
||||
verify(mBlockingHelperManager, times(0))
|
||||
.perhapsShowBlockingHelper(eq(mGroupRow), any(NotificationMenuRowPlugin.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsBlockingHelperShowing_isCorrectlyUpdated() {
|
||||
mGroupRow.setBlockingHelperShowing(true);
|
||||
assertTrue(mGroupRow.isBlockingHelperShowing());
|
||||
|
||||
mGroupRow.setBlockingHelperShowing(false);
|
||||
assertFalse(mGroupRow.isBlockingHelperShowing());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.View;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
|
||||
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
|
||||
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link NotificationBlockingHelperManager}.
|
||||
*/
|
||||
@SmallTest
|
||||
@org.junit.runner.RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class NotificationBlockingHelperManagerTest extends SysuiTestCase {
|
||||
|
||||
private NotificationBlockingHelperManager mBlockingHelperManager;
|
||||
|
||||
private NotificationTestHelper mHelper;
|
||||
|
||||
@Rule public MockitoRule mockito = MockitoJUnit.rule();
|
||||
@Mock private NotificationGutsManager mGutsManager;
|
||||
@Mock private NotificationEntryManager mEntryManager;
|
||||
@Mock private NotificationMenuRow mMenuRow;
|
||||
@Mock private NotificationMenuRowPlugin.MenuItem mMenuItem;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mBlockingHelperManager = new NotificationBlockingHelperManager(mContext);
|
||||
|
||||
mHelper = new NotificationTestHelper(mContext);
|
||||
when(mGutsManager.openGuts(
|
||||
any(View.class),
|
||||
anyInt(),
|
||||
anyInt(),
|
||||
any(NotificationMenuRowPlugin.MenuItem.class)))
|
||||
.thenReturn(true);
|
||||
mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager);
|
||||
mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
|
||||
when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDismissCurrentBlockingHelper_nullBlockingHelperRow() {
|
||||
// By default, this shouldn't dismiss (no pointers/vars set up!)
|
||||
assertFalse(mBlockingHelperManager.dismissCurrentBlockingHelper());
|
||||
assertTrue(mBlockingHelperManager.isBlockingHelperRowNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDismissCurrentBlockingHelper_withDetachedBlockingHelperRow() throws Exception {
|
||||
ExpandableNotificationRow row = spy(mHelper.createRow());
|
||||
row.setBlockingHelperShowing(true);
|
||||
when(row.isAttachedToWindow()).thenReturn(false);
|
||||
mBlockingHelperManager.setBlockingHelperRowForTest(row);
|
||||
|
||||
assertTrue(mBlockingHelperManager.dismissCurrentBlockingHelper());
|
||||
assertTrue(mBlockingHelperManager.isBlockingHelperRowNull());
|
||||
|
||||
verify(mEntryManager, times(0)).updateNotifications();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDismissCurrentBlockingHelper_withAttachedBlockingHelperRow() throws Exception {
|
||||
ExpandableNotificationRow row = spy(mHelper.createRow());
|
||||
row.setBlockingHelperShowing(true);
|
||||
when(row.isAttachedToWindow()).thenReturn(true);
|
||||
mBlockingHelperManager.setBlockingHelperRowForTest(row);
|
||||
|
||||
assertTrue(mBlockingHelperManager.dismissCurrentBlockingHelper());
|
||||
assertTrue(mBlockingHelperManager.isBlockingHelperRowNull());
|
||||
|
||||
verify(mEntryManager).updateNotifications();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerhapsShowBlockingHelper_shown() throws Exception {
|
||||
ExpandableNotificationRow row = mHelper.createRow();
|
||||
row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
|
||||
mBlockingHelperManager.setNotificationShadeExpanded(1f);
|
||||
|
||||
assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow));
|
||||
|
||||
verify(mGutsManager).openGuts(row, 0, 0, mMenuItem);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPerhapsShowBlockingHelper_shownForLargeGroup() throws Exception {
|
||||
ExpandableNotificationRow groupRow = mHelper.createGroup(10);
|
||||
groupRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
|
||||
mBlockingHelperManager.setNotificationShadeExpanded(1f);
|
||||
|
||||
assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(groupRow, mMenuRow));
|
||||
|
||||
verify(mGutsManager).openGuts(groupRow, 0, 0, mMenuItem);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerhapsShowBlockingHelper_shownForOnlyChildNotification()
|
||||
throws Exception {
|
||||
ExpandableNotificationRow groupRow = mHelper.createGroup(1);
|
||||
// Explicitly get the children container & call getViewAtPosition on it instead of the row
|
||||
// as other factors such as view expansion may cause us to get the parent row back instead
|
||||
// of the child row.
|
||||
ExpandableNotificationRow childRow = groupRow.getChildrenContainer().getViewAtPosition(0);
|
||||
childRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
|
||||
mBlockingHelperManager.setNotificationShadeExpanded(1f);
|
||||
|
||||
assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(childRow, mMenuRow));
|
||||
|
||||
verify(mGutsManager).openGuts(childRow, 0, 0, mMenuItem);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerhapsShowBlockingHelper_notShownDueToNeutralUserSentiment() throws Exception {
|
||||
ExpandableNotificationRow row = mHelper.createRow();
|
||||
row.getEntry().userSentiment = USER_SENTIMENT_NEUTRAL;
|
||||
mBlockingHelperManager.setNotificationShadeExpanded(1f);
|
||||
|
||||
assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerhapsShowBlockingHelper_notShownDueToPositiveUserSentiment()
|
||||
throws Exception {
|
||||
ExpandableNotificationRow row = mHelper.createRow();
|
||||
row.getEntry().userSentiment = USER_SENTIMENT_POSITIVE;
|
||||
mBlockingHelperManager.setNotificationShadeExpanded(1f);
|
||||
|
||||
assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerhapsShowBlockingHelper_notShownDueToShadeVisibility() throws Exception {
|
||||
ExpandableNotificationRow row = mHelper.createRow();
|
||||
row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
|
||||
// Hide the shade
|
||||
mBlockingHelperManager.setNotificationShadeExpanded(0f);
|
||||
|
||||
assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerhapsShowBlockingHelper_notShownAsNotificationIsInMultipleChildGroup()
|
||||
throws Exception {
|
||||
ExpandableNotificationRow groupRow = mHelper.createGroup(2);
|
||||
// Explicitly get the children container & call getViewAtPosition on it instead of the row
|
||||
// as other factors such as view expansion may cause us to get the parent row back instead
|
||||
// of the child row.
|
||||
ExpandableNotificationRow childRow = groupRow.getChildrenContainer().getViewAtPosition(0);
|
||||
childRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
|
||||
mBlockingHelperManager.setNotificationShadeExpanded(1f);
|
||||
|
||||
assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(childRow, mMenuRow));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockingHelperShowAndDismiss() throws Exception{
|
||||
ExpandableNotificationRow row = spy(mHelper.createRow());
|
||||
row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
|
||||
when(row.isAttachedToWindow()).thenReturn(true);
|
||||
mBlockingHelperManager.setNotificationShadeExpanded(1f);
|
||||
|
||||
// Show check
|
||||
assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow));
|
||||
|
||||
verify(mGutsManager).openGuts(row, 0, 0, mMenuItem);
|
||||
|
||||
// Dismiss check
|
||||
assertTrue(mBlockingHelperManager.dismissCurrentBlockingHelper());
|
||||
assertTrue(mBlockingHelperManager.isBlockingHelperRowNull());
|
||||
|
||||
verify(mEntryManager).updateNotifications();
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import static android.app.AppOpsManager.OP_CAMERA;
|
||||
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
|
||||
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
|
||||
|
||||
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -27,6 +28,8 @@ import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -34,31 +37,24 @@ import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
||||
import android.app.INotificationManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.util.ArraySet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
|
||||
import com.android.systemui.statusbar.NotificationData;
|
||||
import com.android.systemui.statusbar.NotificationGuts;
|
||||
import com.android.systemui.statusbar.NotificationTestHelper;
|
||||
import com.android.systemui.statusbar.notification.NotificationInflater;
|
||||
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -70,15 +66,18 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Tests for {@link NotificationGutsManager}.
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class NotificationGutsManagerTest extends SysuiTestCase {
|
||||
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
|
||||
|
||||
private final String mPackageName = mContext.getPackageName();
|
||||
private final int mUid = Binder.getCallingUid();
|
||||
|
||||
private NotificationChannel mTestNotificationChannel = new NotificationChannel(
|
||||
TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
private TestableLooper mTestableLooper;
|
||||
@@ -87,7 +86,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
|
||||
private NotificationGutsManager mGutsManager;
|
||||
|
||||
@Rule public MockitoRule mockito = MockitoJUnit.rule();
|
||||
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
|
||||
@Mock private NotificationPresenter mPresenter;
|
||||
@Mock private NotificationEntryManager mEntryManager;
|
||||
@Mock private NotificationStackScrollLayout mStackScroller;
|
||||
@@ -118,7 +116,12 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
|
||||
});
|
||||
|
||||
// Test doesn't support animation since the guts view is not attached.
|
||||
doNothing().when(guts).openControls(anyInt(), anyInt(), anyBoolean(), any(Runnable.class));
|
||||
doNothing().when(guts).openControls(
|
||||
eq(true) /* shouldDoCircularReveal */,
|
||||
anyInt(),
|
||||
anyInt(),
|
||||
anyBoolean(),
|
||||
any(Runnable.class));
|
||||
|
||||
ExpandableNotificationRow realRow = createTestNotificationRow();
|
||||
NotificationMenuRowPlugin.MenuItem menuItem = createTestMenuItem(realRow);
|
||||
@@ -130,7 +133,12 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
|
||||
mGutsManager.openGuts(row, 0, 0, menuItem);
|
||||
assertEquals(View.INVISIBLE, guts.getVisibility());
|
||||
mTestableLooper.processAllMessages();
|
||||
verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any(Runnable.class));
|
||||
verify(guts).openControls(
|
||||
eq(true),
|
||||
anyInt(),
|
||||
anyInt(),
|
||||
anyBoolean(),
|
||||
any(Runnable.class));
|
||||
|
||||
assertEquals(View.VISIBLE, guts.getVisibility());
|
||||
mGutsManager.closeAndSaveGuts(false, false, false, 0, 0, false);
|
||||
@@ -148,7 +156,12 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
|
||||
});
|
||||
|
||||
// Test doesn't support animation since the guts view is not attached.
|
||||
doNothing().when(guts).openControls(anyInt(), anyInt(), anyBoolean(), any(Runnable.class));
|
||||
doNothing().when(guts).openControls(
|
||||
eq(true) /* shouldDoCircularReveal */,
|
||||
anyInt(),
|
||||
anyInt(),
|
||||
anyBoolean(),
|
||||
any(Runnable.class));
|
||||
|
||||
ExpandableNotificationRow realRow = createTestNotificationRow();
|
||||
NotificationMenuRowPlugin.MenuItem menuItem = createTestMenuItem(realRow);
|
||||
@@ -160,7 +173,12 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
|
||||
|
||||
mGutsManager.openGuts(row, 0, 0, menuItem);
|
||||
mTestableLooper.processAllMessages();
|
||||
verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any(Runnable.class));
|
||||
verify(guts).openControls(
|
||||
eq(true),
|
||||
anyInt(),
|
||||
anyInt(),
|
||||
anyBoolean(),
|
||||
any(Runnable.class));
|
||||
|
||||
row.onDensityOrFontScaleChanged();
|
||||
mGutsManager.onDensityOrFontScaleChanged(row);
|
||||
@@ -247,6 +265,56 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
|
||||
assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.getValue().getAction());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitializeNotificationInfoView_showBlockingHelper() throws Exception {
|
||||
NotificationInfo notificationInfoView = mock(NotificationInfo.class);
|
||||
ExpandableNotificationRow row = mHelper.createRow();
|
||||
row.setBlockingHelperShowing(true);
|
||||
row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
|
||||
StatusBarNotification statusBarNotification = row.getStatusBarNotification();
|
||||
|
||||
mGutsManager.initializeNotificationInfo(row, notificationInfoView);
|
||||
|
||||
verify(notificationInfoView).bindNotification(
|
||||
any(PackageManager.class),
|
||||
any(INotificationManager.class),
|
||||
eq(statusBarNotification.getPackageName()),
|
||||
isNull(),
|
||||
anyInt(),
|
||||
eq(statusBarNotification),
|
||||
any(NotificationInfo.CheckSaveListener.class),
|
||||
any(NotificationInfo.OnSettingsClickListener.class),
|
||||
any(NotificationInfo.OnAppSettingsClickListener.class),
|
||||
any(),
|
||||
eq(true) /* isForBlockingHelper */,
|
||||
eq(true) /* isUserSentimentNegative */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitializeNotificationInfoView_dontShowBlockingHelper() throws Exception {
|
||||
NotificationInfo notificationInfoView = mock(NotificationInfo.class);
|
||||
ExpandableNotificationRow row = mHelper.createRow();
|
||||
row.setBlockingHelperShowing(false);
|
||||
row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
|
||||
StatusBarNotification statusBarNotification = row.getStatusBarNotification();
|
||||
|
||||
mGutsManager.initializeNotificationInfo(row, notificationInfoView);
|
||||
|
||||
verify(notificationInfoView).bindNotification(
|
||||
any(PackageManager.class),
|
||||
any(INotificationManager.class),
|
||||
eq(statusBarNotification.getPackageName()),
|
||||
isNull(),
|
||||
anyInt(),
|
||||
eq(statusBarNotification),
|
||||
any(NotificationInfo.CheckSaveListener.class),
|
||||
any(NotificationInfo.OnSettingsClickListener.class),
|
||||
any(NotificationInfo.OnAppSettingsClickListener.class),
|
||||
any(),
|
||||
eq(false) /* isForBlockingHelper */,
|
||||
eq(true) /* isUserSentimentNegative */);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Utility methods:
|
||||
|
||||
|
||||
@@ -67,9 +67,13 @@ import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -87,14 +91,21 @@ public class NotificationInfoTest extends SysuiTestCase {
|
||||
private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME";
|
||||
|
||||
private NotificationInfo mNotificationInfo;
|
||||
private final INotificationManager mMockINotificationManager = mock(INotificationManager.class);
|
||||
private final PackageManager mMockPackageManager = mock(PackageManager.class);
|
||||
private NotificationChannel mNotificationChannel;
|
||||
private NotificationChannel mDefaultNotificationChannel;
|
||||
private StatusBarNotification mSbn;
|
||||
|
||||
@Rule public MockitoRule mockito = MockitoJUnit.rule();
|
||||
@Mock private INotificationManager mMockINotificationManager;
|
||||
@Mock private PackageManager mMockPackageManager;
|
||||
@Mock private NotificationBlockingHelperManager mBlockingHelperManager;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mDependency.injectTestDependency(
|
||||
NotificationBlockingHelperManager.class,
|
||||
mBlockingHelperManager);
|
||||
|
||||
// Inflate the layout
|
||||
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
|
||||
mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info,
|
||||
@@ -311,7 +322,7 @@ public class NotificationInfoTest extends SysuiTestCase {
|
||||
public void testbindNotification_BlockingHelper() throws Exception {
|
||||
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
|
||||
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null,
|
||||
null, null, true);
|
||||
null, null, false, true);
|
||||
final TextView view = mNotificationInfo.findViewById(R.id.block_prompt);
|
||||
assertEquals(View.VISIBLE, view.getVisibility());
|
||||
assertEquals(mContext.getString(R.string.inline_blocking_helper), view.getText());
|
||||
@@ -385,6 +396,27 @@ public class NotificationInfoTest extends SysuiTestCase {
|
||||
assertEquals(IMPORTANCE_UNSPECIFIED, mNotificationChannel.getImportance());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseControls_blockingHelperDismissedIfShown() throws Exception {
|
||||
mNotificationInfo.bindNotification(
|
||||
mMockPackageManager,
|
||||
mMockINotificationManager,
|
||||
TEST_PACKAGE_NAME,
|
||||
mNotificationChannel,
|
||||
1 /* numChannels */,
|
||||
mSbn,
|
||||
null /* checkSaveListener */,
|
||||
null /* onSettingsClick */,
|
||||
null /* onAppSettingsClick */,
|
||||
null /* nonBlockablePkgs */,
|
||||
true /* isForBlockingHelper */,
|
||||
false /* isUserSentimentNegative */);
|
||||
|
||||
mNotificationInfo.closeControls(mNotificationInfo);
|
||||
|
||||
verify(mBlockingHelperManager).dismissCurrentBlockingHelper();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonBlockableAppDoesNotBecomeBlocked() throws Exception {
|
||||
mNotificationChannel.setImportance(IMPORTANCE_LOW);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.statusbar;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Instrumentation;
|
||||
import android.app.Notification;
|
||||
@@ -23,33 +24,32 @@ import android.content.Context;
|
||||
import android.os.UserHandle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
|
||||
import com.android.systemui.statusbar.notification.AboveShelfObserver;
|
||||
import com.android.systemui.statusbar.notification.InflationException;
|
||||
import com.android.systemui.statusbar.notification.NotificationInflaterTest;
|
||||
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
|
||||
import com.android.systemui.statusbar.phone.NotificationGroupManager;
|
||||
import com.android.systemui.statusbar.policy.HeadsUpManager;
|
||||
|
||||
/**
|
||||
* A helper class to create {@link ExpandableNotificationRow}
|
||||
* A helper class to create {@link ExpandableNotificationRow} (for both individual and group
|
||||
* notifications).
|
||||
*/
|
||||
public class NotificationTestHelper {
|
||||
|
||||
static final String PKG = "com.android.systemui";
|
||||
static final int UID = 1000;
|
||||
private static final String GROUP_KEY = "gruKey";
|
||||
|
||||
private final Context mContext;
|
||||
private final Instrumentation mInstrumentation;
|
||||
private int mId;
|
||||
private final NotificationGroupManager mGroupManager = new NotificationGroupManager();
|
||||
private ExpandableNotificationRow mRow;
|
||||
private InflationException mException;
|
||||
private HeadsUpManager mHeadsUpManager;
|
||||
protected static final String PKG = "com.android.systemui";
|
||||
protected static final int UID = 1000;
|
||||
|
||||
public NotificationTestHelper(Context context) {
|
||||
mContext = context;
|
||||
@@ -57,57 +57,125 @@ public class NotificationTestHelper {
|
||||
mHeadsUpManager = new HeadsUpManagerPhone(mContext, null, mGroupManager, null, null);
|
||||
}
|
||||
|
||||
public ExpandableNotificationRow createRow() throws Exception {
|
||||
return createRow(PKG, UID);
|
||||
}
|
||||
|
||||
public ExpandableNotificationRow createRow(String pkg, int uid) throws Exception {
|
||||
return createRow(pkg, uid, false /* isGroupSummary */, null /* groupKey */);
|
||||
}
|
||||
|
||||
public ExpandableNotificationRow createRow(Notification notification) throws Exception {
|
||||
return generateRow(notification, PKG, UID, false /* isGroupRow */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link ExpandableNotificationRow} group with the given number of child
|
||||
* notifications.
|
||||
*/
|
||||
public ExpandableNotificationRow createGroup(int numChildren) throws Exception {
|
||||
ExpandableNotificationRow row = createGroupSummary(GROUP_KEY);
|
||||
for (int i = 0; i < numChildren; i++) {
|
||||
ExpandableNotificationRow childRow = createGroupChild(GROUP_KEY);
|
||||
row.addChildNotification(childRow);
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
/** Returns a group notification with 2 child notifications. */
|
||||
public ExpandableNotificationRow createGroup() throws Exception {
|
||||
return createGroup(2);
|
||||
}
|
||||
|
||||
private ExpandableNotificationRow createGroupSummary(String groupkey) throws Exception {
|
||||
return createRow(PKG, UID, true /* isGroupSummary */, groupkey);
|
||||
}
|
||||
|
||||
private ExpandableNotificationRow createGroupChild(String groupkey) throws Exception {
|
||||
return createRow(PKG, UID, false /* isGroupSummary */, groupkey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a notification row with the given details.
|
||||
*
|
||||
* @param pkg package used for creating a {@link StatusBarNotification}
|
||||
* @param uid uid used for creating a {@link StatusBarNotification}
|
||||
* @param isGroupSummary whether the notification row is a group summary
|
||||
* @param groupKey the group key for the notification group used across notifications
|
||||
* @return a row with that's either a standalone notification or a group notification if the
|
||||
* groupKey is non-null
|
||||
* @throws Exception
|
||||
*/
|
||||
private ExpandableNotificationRow createRow(
|
||||
String pkg,
|
||||
int uid,
|
||||
boolean isGroupSummary,
|
||||
@Nullable String groupKey)
|
||||
throws Exception {
|
||||
Notification publicVersion = new Notification.Builder(mContext).setSmallIcon(
|
||||
R.drawable.ic_person)
|
||||
.setCustomContentView(new RemoteViews(mContext.getPackageName(),
|
||||
R.layout.custom_view_dark))
|
||||
.build();
|
||||
Notification notification = new Notification.Builder(mContext).setSmallIcon(
|
||||
R.drawable.ic_person)
|
||||
.setContentTitle("Title")
|
||||
.setContentText("Text")
|
||||
.setPublicVersion(publicVersion)
|
||||
.build();
|
||||
return createRow(notification, pkg, uid);
|
||||
Notification.Builder notificationBuilder =
|
||||
new Notification.Builder(mContext)
|
||||
.setSmallIcon(R.drawable.ic_person)
|
||||
.setContentTitle("Title")
|
||||
.setContentText("Text")
|
||||
.setPublicVersion(publicVersion);
|
||||
|
||||
// Group notification setup
|
||||
if (isGroupSummary) {
|
||||
notificationBuilder.setGroupSummary(true);
|
||||
}
|
||||
if (!TextUtils.isEmpty(groupKey)) {
|
||||
notificationBuilder.setGroup(groupKey);
|
||||
}
|
||||
|
||||
return generateRow(notificationBuilder.build(), pkg, uid, !TextUtils.isEmpty(groupKey));
|
||||
}
|
||||
|
||||
public ExpandableNotificationRow createRow() throws Exception {
|
||||
return createRow(PKG, UID);
|
||||
}
|
||||
|
||||
public ExpandableNotificationRow createRow(Notification notification) throws Exception {
|
||||
return createRow(notification, PKG, UID);
|
||||
}
|
||||
|
||||
public ExpandableNotificationRow createRow(Notification notification, String pkg, int uid)
|
||||
private ExpandableNotificationRow generateRow(
|
||||
Notification notification,
|
||||
String pkg,
|
||||
int uid,
|
||||
boolean isGroupRow)
|
||||
throws Exception {
|
||||
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
|
||||
mContext.LAYOUT_INFLATER_SERVICE);
|
||||
mInstrumentation.runOnMainSync(() -> {
|
||||
mRow = (ExpandableNotificationRow) inflater.inflate(
|
||||
R.layout.status_bar_notification_row,
|
||||
null, false);
|
||||
});
|
||||
mInstrumentation.runOnMainSync(() ->
|
||||
mRow = (ExpandableNotificationRow) inflater.inflate(
|
||||
R.layout.status_bar_notification_row,
|
||||
null /* root */,
|
||||
false /* attachToRoot */)
|
||||
);
|
||||
ExpandableNotificationRow row = mRow;
|
||||
row.setGroupManager(mGroupManager);
|
||||
row.setHeadsUpManager(mHeadsUpManager);
|
||||
row.setAboveShelfChangedListener(aboveShelf -> {});
|
||||
UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
|
||||
StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, mId++, null, uid,
|
||||
2000, notification, mUser, null, System.currentTimeMillis());
|
||||
StatusBarNotification sbn = new StatusBarNotification(
|
||||
pkg,
|
||||
pkg,
|
||||
mId++,
|
||||
null /* tag */,
|
||||
uid,
|
||||
2000 /* initialPid */,
|
||||
notification,
|
||||
mUser,
|
||||
null /* overrideGroupKey */,
|
||||
System.currentTimeMillis());
|
||||
NotificationData.Entry entry = new NotificationData.Entry(sbn);
|
||||
entry.row = row;
|
||||
entry.createIcons(mContext, sbn);
|
||||
NotificationInflaterTest.runThenWaitForInflation(() -> row.updateNotification(entry),
|
||||
NotificationInflaterTest.runThenWaitForInflation(
|
||||
() -> row.updateNotification(entry),
|
||||
row.getNotificationInflater());
|
||||
return row;
|
||||
}
|
||||
|
||||
public ExpandableNotificationRow createGroup() throws Exception {
|
||||
ExpandableNotificationRow row = createRow();
|
||||
row.addChildNotification(createRow());
|
||||
row.addChildNotification(createRow());
|
||||
// This would be done as part of onAsyncInflationFinished, but we skip large amounts of
|
||||
// the callback chain, so we need to make up for not adding it to the group manager
|
||||
// here.
|
||||
mGroupManager.onEntryAdded(entry);
|
||||
return row;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
|
||||
package com.android.systemui.statusbar.stack;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -25,33 +30,73 @@ import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.systemui.ExpandHelper;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.TestableDependency;
|
||||
import com.android.systemui.statusbar.EmptyShadeView;
|
||||
import com.android.systemui.statusbar.FooterView;
|
||||
import com.android.systemui.statusbar.NotificationBlockingHelperManager;
|
||||
import com.android.systemui.statusbar.NotificationShelf;
|
||||
import com.android.systemui.statusbar.StatusBarState;
|
||||
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
|
||||
import com.android.systemui.statusbar.phone.NotificationGroupManager;
|
||||
import com.android.systemui.statusbar.phone.ScrimController;
|
||||
import com.android.systemui.statusbar.phone.StatusBar;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
/**
|
||||
* Tests for {@link NotificationStackScrollLayout}.
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NotificationStackScrollLayoutTest extends SysuiTestCase {
|
||||
|
||||
private NotificationStackScrollLayout mStackScroller;
|
||||
private StatusBar mBar;
|
||||
|
||||
@Rule public MockitoRule mockito = MockitoJUnit.rule();
|
||||
@Mock private StatusBar mBar;
|
||||
@Mock private HeadsUpManagerPhone mHeadsUpManager;
|
||||
@Mock private NotificationBlockingHelperManager mBlockingHelperManager;
|
||||
@Mock private NotificationGroupManager mGroupManager;
|
||||
@Mock private ExpandHelper mExpandHelper;
|
||||
@Mock private EmptyShadeView mEmptyShadeView;
|
||||
|
||||
@Before
|
||||
@UiThreadTest
|
||||
public void setUp() throws Exception {
|
||||
// Inject dependencies before initializing the layout
|
||||
mDependency.injectTestDependency(
|
||||
NotificationBlockingHelperManager.class,
|
||||
mBlockingHelperManager);
|
||||
|
||||
NotificationShelf notificationShelf = spy(new NotificationShelf(getContext(), null));
|
||||
mStackScroller = new NotificationStackScrollLayout(getContext());
|
||||
mBar = mock(StatusBar.class);
|
||||
mStackScroller.setShelf(notificationShelf);
|
||||
mStackScroller.setStatusBar(mBar);
|
||||
mStackScroller.setScrimController(mock(ScrimController.class));
|
||||
mStackScroller.setHeadsUpManager(mHeadsUpManager);
|
||||
mStackScroller.setGroupManager(mGroupManager);
|
||||
mStackScroller.setEmptyShadeView(mEmptyShadeView);
|
||||
|
||||
// Stub out functionality that isn't necessary to test.
|
||||
doNothing().when(mBar)
|
||||
.executeRunnableDismissingKeyguard(any(Runnable.class),
|
||||
any(Runnable.class),
|
||||
anyBoolean(),
|
||||
anyBoolean(),
|
||||
anyBoolean());
|
||||
doNothing().when(mGroupManager).collapseAllGroups();
|
||||
doNothing().when(mExpandHelper).cancelImmediately();
|
||||
doNothing().when(notificationShelf).setAnimationsEnabled(anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -75,26 +120,34 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void updateEmptyView_dndSuppressing() {
|
||||
EmptyShadeView view = mock(EmptyShadeView.class);
|
||||
mStackScroller.setEmptyShadeView(view);
|
||||
when(view.willBeGone()).thenReturn(true);
|
||||
when(mEmptyShadeView.willBeGone()).thenReturn(true);
|
||||
when(mBar.areNotificationsHidden()).thenReturn(true);
|
||||
|
||||
mStackScroller.updateEmptyShadeView(true);
|
||||
|
||||
verify(view).setText(R.string.dnd_suppressing_shade_text);
|
||||
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateEmptyView_dndNotSuppressing() {
|
||||
EmptyShadeView view = mock(EmptyShadeView.class);
|
||||
mStackScroller.setEmptyShadeView(view);
|
||||
when(view.willBeGone()).thenReturn(true);
|
||||
mStackScroller.setEmptyShadeView(mEmptyShadeView);
|
||||
when(mEmptyShadeView.willBeGone()).thenReturn(true);
|
||||
when(mBar.areNotificationsHidden()).thenReturn(false);
|
||||
|
||||
mStackScroller.updateEmptyShadeView(true);
|
||||
|
||||
verify(view).setText(R.string.empty_shade_text);
|
||||
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
public void testSetExpandedHeight_blockingHelperManagerReceivedCallbacks() {
|
||||
mStackScroller.setExpandedHeight(0f);
|
||||
verify(mBlockingHelperManager).setNotificationShadeExpanded(0f);
|
||||
reset(mBlockingHelperManager);
|
||||
|
||||
mStackScroller.setExpandedHeight(100f);
|
||||
verify(mBlockingHelperManager).setNotificationShadeExpanded(100f);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user