blob: 1182fb370280fb43bb855ebf160a42006bb2efd0 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.notification.stack;
import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows;
import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.ExpandHelper;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.Compile;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Named;
import kotlin.Unit;
/**
* Controller for {@link NotificationStackScrollLayout}.
*/
@CentralSurfacesComponent.CentralSurfacesScope
public class NotificationStackScrollLayoutController {
private static final String TAG = "StackScrollerController";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private final boolean mAllowLongPress;
private final NotificationGutsManager mNotificationGutsManager;
private final NotificationVisibilityProvider mVisibilityProvider;
private final HeadsUpManagerPhone mHeadsUpManager;
private final NotificationRoundnessManager mNotificationRoundnessManager;
private final TunerService mTunerService;
private final DeviceProvisionedController mDeviceProvisionedController;
private final DynamicPrivacyController mDynamicPrivacyController;
private final ConfigurationController mConfigurationController;
private final ZenModeController mZenModeController;
private final MetricsLogger mMetricsLogger;
private final DumpManager mDumpManager;
private final FalsingCollector mFalsingCollector;
private final FalsingManager mFalsingManager;
private final Resources mResources;
private final NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder;
private final ScrimController mScrimController;
private final NotifPipeline mNotifPipeline;
private final NotifCollection mNotifCollection;
private final NotificationEntryManager mNotificationEntryManager;
private final UiEventLogger mUiEventLogger;
private final NotificationRemoteInputManager mRemoteInputManager;
private final VisualStabilityManager mVisualStabilityManager;
private final ShadeController mShadeController;
private final KeyguardMediaController mKeyguardMediaController;
private final SysuiStatusBarStateController mStatusBarStateController;
private final KeyguardBypassController mKeyguardBypassController;
private final NotificationLockscreenUserManager mLockscreenUserManager;
// TODO: CentralSurfaces should be encapsulated behind a Controller
private final CentralSurfaces mCentralSurfaces;
private final SectionHeaderController mSilentHeaderController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final ShadeTransitionController mShadeTransitionController;
private final InteractionJankMonitor mJankMonitor;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final StackStateLogger mStackStateLogger;
private final NotificationStackScrollLogger mLogger;
private final GroupExpansionManager mGroupExpansionManager;
private NotificationStackScrollLayout mView;
private boolean mFadeNotificationsOnDismiss;
private NotificationSwipeHelper mSwipeHelper;
private boolean mShowEmptyShadeView;
@Nullable private Boolean mHistoryEnabled;
private int mBarState;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private View mLongPressedView;
private final NotificationListContainerImpl mNotificationListContainer =
new NotificationListContainerImpl();
private final NotifStackController mNotifStackController =
new NotifStackControllerImpl();
@Nullable
private NotificationActivityStarter mNotificationActivityStarter;
@VisibleForTesting
final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
mConfigurationController.addCallback(mConfigurationListener);
mZenModeController.addCallback(mZenModeControllerCallback);
mBarState = mStatusBarStateController.getState();
mStatusBarStateController.addCallback(
mStateListener, SysuiStatusBarStateController.RANK_STACK_SCROLLER);
}
@Override
public void onViewDetachedFromWindow(View v) {
mConfigurationController.removeCallback(mConfigurationListener);
mZenModeController.removeCallback(mZenModeControllerCallback);
mStatusBarStateController.removeCallback(mStateListener);
}
};
private final DeviceProvisionedListener mDeviceProvisionedListener =
new DeviceProvisionedListener() {
@Override
public void onDeviceProvisionedChanged() {
updateCurrentUserIsSetup();
}
@Override
public void onUserSwitched() {
updateCurrentUserIsSetup();
}
@Override
public void onUserSetupChanged() {
updateCurrentUserIsSetup();
}
private void updateCurrentUserIsSetup() {
mView.setCurrentUserSetup(mDeviceProvisionedController.isCurrentUserSetup());
}
};
private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> {
if (mView.isExpanded()) {
// The bottom might change because we're using the final actual height of the view
mView.setAnimateBottomOnLayout(true);
}
// Let's update the footer once the notifications have been updated (in the next frame)
mView.post(this::updateFooter);
};
@VisibleForTesting
final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
public void onDensityOrFontScaleChanged() {
updateShowEmptyShadeView();
mView.reinflateViews();
}
@Override
public void onUiModeChanged() {
mView.updateBgColor();
mView.updateDecorViews();
}
@Override
public void onThemeChanged() {
mView.updateCornerRadius();
mView.updateBgColor();
mView.updateDecorViews();
mView.reinflateViews();
updateShowEmptyShadeView();
updateFooter();
}
@Override
public void onConfigChanged(Configuration newConfig) {
updateResources();
}
};
private NotifStats mNotifStats = NotifStats.getEmpty();
private void updateResources() {
mNotificationStackSizeCalculator.updateResources();
}
private final StatusBarStateController.StateListener mStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onStatePreChange(int oldState, int newState) {
if (oldState == StatusBarState.SHADE_LOCKED
&& newState == KEYGUARD) {
mView.requestAnimateEverything();
}
}
@Override
public void onStateChanged(int newState) {
mBarState = newState;
mView.setStatusBarState(mBarState);
if (newState == KEYGUARD) {
mGroupExpansionManager.collapseGroups();
}
}
@Override
public void onUpcomingStateChanged(int newState) {
mView.setUpcomingStatusBarState(newState);
}
@Override
public void onStatePostChange() {
mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(),
mLockscreenUserManager.isAnyProfilePublicMode());
mView.onStatePostChange(mStatusBarStateController.fromShadeLocked());
mNotificationEntryManager.updateNotifications("CentralSurfaces state changed");
}
};
private final UserChangedListener mLockscreenUserChangeListener = new UserChangedListener() {
@Override
public void onUserChanged(int userId) {
mView.updateSensitiveness(false, mLockscreenUserManager.isAnyProfilePublicMode());
mHistoryEnabled = null;
updateFooter();
}
};
/**
* Set the overexpansion of the panel to be applied to the view.
*/
public void setOverExpansion(float overExpansion) {
mView.setOverExpansion(overExpansion);
}
private final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() {
@Override
public void onMenuClicked(
View view, int x, int y, NotificationMenuRowPlugin.MenuItem item) {
if (!mAllowLongPress) {
return;
}
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
mMetricsLogger.write(row.getEntry().getSbn().getLogMaker()
.setCategory(MetricsEvent.ACTION_TOUCH_GEAR)
.setType(MetricsEvent.TYPE_ACTION)
);
}
mNotificationGutsManager.openGuts(view, x, y, item);
}
@Override
public void onMenuReset(View row) {
View translatingParentView = mSwipeHelper.getTranslatingParentView();
if (translatingParentView != null && row == translatingParentView) {
mSwipeHelper.clearExposedMenuView();
mSwipeHelper.clearTranslatingParentView();
if (row instanceof ExpandableNotificationRow) {
mHeadsUpManager.setMenuShown(
((ExpandableNotificationRow) row).getEntry(), false);
}
}
}
@Override
public void onMenuShown(View row) {
if (row instanceof ExpandableNotificationRow) {
ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row;
mMetricsLogger.write(notificationRow.getEntry().getSbn().getLogMaker()
.setCategory(MetricsEvent.ACTION_REVEAL_GEAR)
.setType(MetricsEvent.TYPE_ACTION));
mHeadsUpManager.setMenuShown(notificationRow.getEntry(), true);
mSwipeHelper.onMenuShown(row);
mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
false /* resetMenu */);
// Check to see if we want to go directly to the notification guts
NotificationMenuRowPlugin provider = notificationRow.getProvider();
if (provider.shouldShowGutsOnSnapOpen()) {
NotificationMenuRowPlugin.MenuItem item = provider.menuItemToExposeOnSnap();
if (item != null) {
Point origin = provider.getRevealAnimationOrigin();
mNotificationGutsManager.openGuts(row, origin.x, origin.y, item);
} else {
Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no "
+ "menu item in menuItemtoExposeOnSnap. Skipping.");
}
// Close the menu row since we went directly to the guts
mSwipeHelper.resetExposedMenuView(false, true);
}
}
}
};
private final NotificationSwipeHelper.NotificationCallback mNotificationCallback =
new NotificationSwipeHelper.NotificationCallback() {
@Override
public void onDismiss() {
mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
false /* resetMenu */);
}
@Override
public float getTotalTranslationLength(View animView) {
return mView.getTotalTranslationLength(animView);
}
@Override
public void onSnooze(StatusBarNotification sbn,
NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
mCentralSurfaces.setNotificationSnoozed(sbn, snoozeOption);
}
@Override
public boolean shouldDismissQuickly() {
return mView.isExpanded() && mView.isFullyAwake();
}
@Override
public void onDragCancelled(View v) {
mFalsingCollector.onNotificationStopDismissing();
}
/**
* 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 view) {
if (!(view instanceof ActivatableNotificationView)) {
return;
}
ActivatableNotificationView row = (ActivatableNotificationView) view;
if (!row.isDismissed()) {
handleChildViewDismissed(view);
}
row.removeFromTransientContainer();
}
/**
* Starts up notification dismiss and tells the notification, if any, to remove
* itself from the layout.
*
* @param view view (e.g. notification) to dismiss from the layout
*/
public void handleChildViewDismissed(View view) {
if (mView.getClearAllInProgress()) {
return;
}
mView.onSwipeEnd();
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
if (row.isHeadsUp()) {
mHeadsUpManager.addSwipedOutNotification(
row.getEntry().getSbn().getKey());
}
row.performDismiss(false /* fromAccessibility */);
}
mView.addSwipedOutView(view);
mFalsingCollector.onNotificationDismissed();
if (mFalsingCollector.shouldEnforceBouncer()) {
mCentralSurfaces.executeRunnableDismissingKeyguard(
null,
null /* cancelAction */,
false /* dismissShade */,
true /* afterKeyguardGone */,
false /* deferred */);
}
}
@Override
public boolean isAntiFalsingNeeded() {
return mView.onKeyguard();
}
@Override
public View getChildAtPosition(MotionEvent ev) {
View child = mView.getChildAtPosition(
ev.getX(),
ev.getY(),
true /* requireMinHeight */,
false /* ignoreDecors */);
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
ExpandableNotificationRow parent = row.getNotificationParent();
if (parent != null && parent.areChildrenExpanded()
&& (parent.areGutsExposed()
|| mSwipeHelper.getExposedMenuView() == parent
|| (parent.getAttachedChildren().size() == 1
&& parent.getEntry().isDismissable()))) {
// In this case the group is expanded and showing the menu for the
// group, further interaction should apply to the group, not any
// child notifications so we use the parent of the child. We also do the
// same if we only have a single child.
child = parent;
}
}
return child;
}
@Override
public void onLongPressSent(View v) {
mLongPressedView = v;
}
@Override
public void onBeginDrag(View v) {
mFalsingCollector.onNotificationStartDismissing();
mView.onSwipeBegin(v);
}
@Override
public void onChildSnappedBack(View animView, float targetLeft) {
mView.onSwipeEnd();
if (animView instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
if (row.isPinned() && !canChildBeDismissed(row)
&& row.getEntry().getSbn().getNotification().fullScreenIntent
== null) {
mHeadsUpManager.removeNotification(row.getEntry().getSbn().getKey(),
true /* removeImmediately */);
}
}
}
@Override
public boolean updateSwipeProgress(View animView, boolean dismissable,
float swipeProgress) {
// Returning true prevents alpha fading.
return !mFadeNotificationsOnDismiss;
}
@Override
public float getFalsingThresholdFactor() {
return mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
}
@Override
public int getConstrainSwipeStartPosition() {
NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
if (menuRow != null) {
return Math.abs(menuRow.getMenuSnapTarget());
}
return 0;
}
@Override
public boolean canChildBeDismissed(View v) {
return NotificationStackScrollLayout.canChildBeDismissed(v);
}
@Override
public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) {
//TODO: b/131242807 for why this doesn't do anything with direction
return canChildBeDismissed(v);
}
};
private final OnHeadsUpChangedListener mOnHeadsUpChangedListener =
new OnHeadsUpChangedListener() {
@Override
public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
mView.setInHeadsUpPinnedMode(inPinnedMode);
}
@Override
public void onHeadsUpPinned(NotificationEntry entry) {
mNotificationRoundnessManager.updateView(entry.getRow(), false /* animate */);
}
@Override
public void onHeadsUpUnPinned(NotificationEntry entry) {
ExpandableNotificationRow row = entry.getRow();
// update the roundedness posted, because we might be animating away the
// headsup soon, so no need to set the roundedness to 0 and then back to 1.
row.post(() -> mNotificationRoundnessManager.updateView(row,
true /* animate */));
}
@Override
public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
long numEntries = mHeadsUpManager.getAllEntries().count();
NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
mView.setNumHeadsUp(numEntries);
mView.setTopHeadsUpEntry(topEntry);
generateHeadsUpAnimation(entry, isHeadsUp);
ExpandableNotificationRow row = entry.getRow();
mNotificationRoundnessManager.updateView(row, true /* animate */);
}
};
private final ZenModeController.Callback mZenModeControllerCallback =
new ZenModeController.Callback() {
@Override
public void onZenChanged(int zen) {
updateShowEmptyShadeView();
}
};
@Inject
public NotificationStackScrollLayoutController(
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
NotificationGutsManager notificationGutsManager,
NotificationVisibilityProvider visibilityProvider,
HeadsUpManagerPhone headsUpManager,
NotificationRoundnessManager notificationRoundnessManager,
TunerService tunerService,
DeviceProvisionedController deviceProvisionedController,
DynamicPrivacyController dynamicPrivacyController,
ConfigurationController configurationController,
SysuiStatusBarStateController statusBarStateController,
KeyguardMediaController keyguardMediaController,
KeyguardBypassController keyguardBypassController,
ZenModeController zenModeController,
NotificationLockscreenUserManager lockscreenUserManager,
MetricsLogger metricsLogger,
DumpManager dumpManager,
FalsingCollector falsingCollector,
FalsingManager falsingManager,
@Main Resources resources,
NotificationSwipeHelper.Builder notificationSwipeHelperBuilder,
CentralSurfaces centralSurfaces,
ScrimController scrimController,
NotificationGroupManagerLegacy legacyGroupManager,
GroupExpansionManager groupManager,
@SilentHeader SectionHeaderController silentHeaderController,
NotifPipeline notifPipeline,
NotifCollection notifCollection,
NotificationEntryManager notificationEntryManager,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
ShadeTransitionController shadeTransitionController,
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
VisualStabilityManager visualStabilityManager,
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
NotificationStackSizeCalculator notificationStackSizeCalculator) {
mStackStateLogger = stackLogger;
mLogger = logger;
mAllowLongPress = allowLongPress;
mNotificationGutsManager = notificationGutsManager;
mVisibilityProvider = visibilityProvider;
mHeadsUpManager = headsUpManager;
mNotificationRoundnessManager = notificationRoundnessManager;
mTunerService = tunerService;
mDeviceProvisionedController = deviceProvisionedController;
mDynamicPrivacyController = dynamicPrivacyController;
mConfigurationController = configurationController;
mStatusBarStateController = statusBarStateController;
mKeyguardMediaController = keyguardMediaController;
mKeyguardBypassController = keyguardBypassController;
mZenModeController = zenModeController;
mLockscreenUserManager = lockscreenUserManager;
mMetricsLogger = metricsLogger;
mDumpManager = dumpManager;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mShadeTransitionController = shadeTransitionController;
mFalsingCollector = falsingCollector;
mFalsingManager = falsingManager;
mResources = resources;
mNotificationSwipeHelperBuilder = notificationSwipeHelperBuilder;
mCentralSurfaces = centralSurfaces;
mScrimController = scrimController;
mJankMonitor = jankMonitor;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
mGroupExpansionManager = groupManager;
legacyGroupManager.registerGroupChangeListener(new OnGroupChangeListener() {
@Override
public void onGroupsChanged() {
mCentralSurfaces.requestNotificationUpdate("onGroupsChanged");
}
});
mSilentHeaderController = silentHeaderController;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
mNotificationEntryManager = notificationEntryManager;
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
mVisualStabilityManager = visualStabilityManager;
mShadeController = shadeController;
updateResources();
}
public void attach(NotificationStackScrollLayout view) {
mView = view;
mView.setLogger(mStackStateLogger);
mView.setController(this);
mView.setLogger(mLogger);
mView.setTouchHandler(new TouchHandler());
mView.setCentralSurfaces(mCentralSurfaces);
mView.setClearAllAnimationListener(this::onAnimationEnd);
mView.setClearAllListener((selection) -> mUiEventLogger.log(
NotificationPanelEvent.fromSelection(selection)));
mView.setFooterClearAllListener(() ->
mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive());
mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() {
@Override
public void onRemoteInputActive(boolean active) {
mView.setIsRemoteInputActive(active);
}
});
mView.setShadeController(mShadeController);
mDumpManager.registerDumpable(mView);
mKeyguardBypassController.registerOnBypassStateChangedListener(
isEnabled -> mNotificationRoundnessManager.setShouldRoundPulsingViews(!isEnabled));
mNotificationRoundnessManager.setShouldRoundPulsingViews(
!mKeyguardBypassController.getBypassEnabled());
mSwipeHelper = mNotificationSwipeHelperBuilder
.setSwipeDirection(SwipeHelper.X)
.setNotificationCallback(mNotificationCallback)
.setOnMenuEventListener(mMenuEventListener)
.build();
mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
@Override
public void onEntryUpdated(NotificationEntry entry) {
mView.onEntryUpdated(entry);
}
});
mView.initView(mView.getContext(), mSwipeHelper, mNotificationStackSizeCalculator);
mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled());
mKeyguardBypassController
.registerOnBypassStateChangedListener(mView::setKeyguardBypassEnabled);
mView.setManageButtonClickListener(v -> {
if (mNotificationActivityStarter != null) {
mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
}
});
mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener);
mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming);
mLockscreenShadeTransitionController.setStackScroller(this);
mShadeTransitionController.setNotificationStackScrollLayoutController(this);
mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
mFadeNotificationsOnDismiss = // TODO: this should probably be injected directly
mResources.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation);
mTunerService.addTunable(
(key, newValue) -> {
switch (key) {
case Settings.Secure.NOTIFICATION_HISTORY_ENABLED:
mHistoryEnabled = null; // invalidate
updateFooter();
break;
case HIGH_PRIORITY:
mView.setHighPriorityBeforeSpeedBump("1".equals(newValue));
break;
}
},
HIGH_PRIORITY,
Settings.Secure.NOTIFICATION_HISTORY_ENABLED);
mKeyguardMediaController.setVisibilityChangedListener(visible -> {
if (visible) {
mView.generateAddAnimation(
mKeyguardMediaController.getSinglePaneContainer(),
false /*fromMoreCard */);
} else {
mView.generateRemoveAnimation(mKeyguardMediaController.getSinglePaneContainer());
}
mView.requestChildrenUpdate();
return Unit.INSTANCE;
});
// attach callback, and then call it to update mView immediately
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
mDeviceProvisionedListener.onDeviceProvisionedChanged();
if (mView.isAttachedToWindow()) {
mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
}
mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications());
mGroupExpansionManager.registerGroupExpansionChangeListener(
(changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded));
}
private boolean isInVisibleLocation(NotificationEntry entry) {
ExpandableNotificationRow row = entry.getRow();
ExpandableViewState childViewState = row.getViewState();
if (childViewState == null) {
return false;
}
if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
return false;
}
if (row.getVisibility() != View.VISIBLE) {
return false;
}
return true;
}
public boolean isViewAffectedBySwipe(ExpandableView expandableView) {
return mNotificationRoundnessManager.isViewAffectedBySwipe(expandableView);
}
public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
mView.addOnExpandedHeightChangedListener(listener);
}
public void removeOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
mView.removeOnExpandedHeightChangedListener(listener);
}
public void addOnLayoutChangeListener(View.OnLayoutChangeListener listener) {
mView.addOnLayoutChangeListener(listener);
}
public void removeOnLayoutChangeListener(View.OnLayoutChangeListener listener) {
mView.removeOnLayoutChangeListener(listener);
}
public void setHeadsUpAppearanceController(HeadsUpAppearanceController controller) {
mHeadsUpAppearanceController = controller;
mView.setHeadsUpAppearanceController(controller);
}
public float getAppearFraction() {
return mView.getAppearFraction();
}
public float getExpandedHeight() {
return mView.getExpandedHeight();
}
public void requestLayout() {
mView.requestLayout();
}
public Display getDisplay() {
return mView.getDisplay();
}
public WindowInsets getRootWindowInsets() {
return mView.getRootWindowInsets();
}
public int getRight() {
return mView.getRight();
}
public boolean isLayoutRtl() {
return mView.isLayoutRtl();
}
/**
* @return the left of the view.
*/
public int getLeft() {
return mView.getLeft();
}
/**
* @return the top of the view.
*/
public int getTop() {
return mView.getTop();
}
/**
* @return the bottom of the view.
*/
public int getBottom() {
return mView.getBottom();
}
public float getTranslationX() {
return mView.getTranslationX();
}
public int indexOfChild(View view) {
return mView.indexOfChild(view);
}
public void setOnHeightChangedListener(
ExpandableView.OnHeightChangedListener listener) {
mView.setOnHeightChangedListener(listener);
}
public void setOverscrollTopChangedListener(
OnOverscrollTopChangedListener listener) {
mView.setOverscrollTopChangedListener(listener);
}
public void setOnEmptySpaceClickListener(
OnEmptySpaceClickListener listener) {
mView.setOnEmptySpaceClickListener(listener);
}
public void setTrackingHeadsUp(ExpandableNotificationRow expandableNotificationRow) {
mView.setTrackingHeadsUp(expandableNotificationRow);
mNotificationRoundnessManager.setTrackingHeadsUp(expandableNotificationRow);
}
public void wakeUpFromPulse() {
mView.wakeUpFromPulse();
}
public boolean isPulseExpanding() {
return mView.isPulseExpanding();
}
public void setOnPulseHeightChangedListener(Runnable listener) {
mView.setOnPulseHeightChangedListener(listener);
}
public void setDozeAmount(float amount) {
mView.setDozeAmount(amount);
}
public int getSpeedBumpIndex() {
return mView.getSpeedBumpIndex();
}
public void setHideAmount(float linearAmount, float amount) {
mView.setHideAmount(linearAmount, amount);
}
public void notifyHideAnimationStart(boolean hide) {
mView.notifyHideAnimationStart(hide);
}
public float setPulseHeight(float height) {
return mView.setPulseHeight(height);
}
public void getLocationOnScreen(int[] outLocation) {
mView.getLocationOnScreen(outLocation);
}
public ExpandableView getChildAtRawPosition(float x, float y) {
return mView.getChildAtRawPosition(x, y);
}
public ViewGroup.LayoutParams getLayoutParams() {
return mView.getLayoutParams();
}
/**
* Updates layout parameters on the root view
*/
public void setLayoutParams(ViewGroup.LayoutParams lp) {
mView.setLayoutParams(lp);
}
public void setIsFullWidth(boolean isFullWidth) {
mView.setIsFullWidth(isFullWidth);
}
public boolean isAddOrRemoveAnimationPending() {
return mView.isAddOrRemoveAnimationPending();
}
public int getVisibleNotificationCount() {
return mNotifStats.getNumActiveNotifs();
}
public boolean isHistoryEnabled() {
Boolean historyEnabled = mHistoryEnabled;
if (historyEnabled == null) {
if (mView == null || mView.getContext() == null) {
Log.wtf(TAG, "isHistoryEnabled failed to initialize its value");
return false;
}
mHistoryEnabled = historyEnabled =
Settings.Secure.getIntForUser(mView.getContext().getContentResolver(),
Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
}
return historyEnabled;
}
public int getIntrinsicContentHeight() {
return mView.getIntrinsicContentHeight();
}
public void setIntrinsicPadding(int intrinsicPadding) {
mView.setIntrinsicPadding(intrinsicPadding);
}
public int getHeight() {
return mView.getHeight();
}
public int getChildCount() {
return mView.getChildCount();
}
public ExpandableView getChildAt(int i) {
return (ExpandableView) mView.getChildAt(i);
}
public void goToFullShade(long delay) {
mView.goToFullShade(delay);
}
public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
boolean cancelAnimators) {
mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators);
}
public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
mView.setOverScrollAmount(amount, onTop, animate);
}
public void resetScrollPosition() {
mView.resetScrollPosition();
}
public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
mView.setShouldShowShelfOnly(shouldShowShelfOnly);
}
public void cancelLongPress() {
mView.cancelLongPress();
}
public float getX() {
return mView.getX();
}
public boolean isBelowLastNotification(float x, float y) {
return mView.isBelowLastNotification(x, y);
}
public float getWidth() {
return mView.getWidth();
}
public float getOpeningHeight() {
return mView.getOpeningHeight();
}
public float getBottomMostNotificationBottom() {
return mView.getBottomMostNotificationBottom();
}
public void checkSnoozeLeavebehind() {
if (mView.getCheckSnoozeLeaveBehind()) {
mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
false /* resetMenu */);
mView.setCheckForLeaveBehind(false);
}
}
public void setQsFullScreen(boolean fullScreen) {
mView.setQsFullScreen(fullScreen);
updateShowEmptyShadeView();
}
public void setScrollingEnabled(boolean enabled) {
mView.setScrollingEnabled(enabled);
}
public void setQsExpansionFraction(float expansionFraction) {
mView.setQsExpansionFraction(expansionFraction);
}
public void setOnStackYChanged(Consumer<Boolean> onStackYChanged) {
mView.setOnStackYChanged(onStackYChanged);
}
public float getNotificationSquishinessFraction() {
return mView.getNotificationSquishinessFraction();
}
public float calculateAppearFractionBypass() {
return mView.calculateAppearFractionBypass();
}
public void updateTopPadding(float qsHeight, boolean animate) {
mView.updateTopPadding(qsHeight, animate);
}
public boolean isScrolledToBottom() {
return mView.isScrolledToBottom();
}
public int getNotGoneChildCount() {
return mView.getNotGoneChildCount();
}
public float getIntrinsicPadding() {
return mView.getIntrinsicPadding();
}
public float getLayoutMinHeight() {
return mView.getLayoutMinHeight();
}
public int getEmptyBottomMargin() {
return mView.getEmptyBottomMargin();
}
public float getTopPaddingOverflow() {
return mView.getTopPaddingOverflow();
}
public int getTopPadding() {
return mView.getTopPadding();
}
public float getEmptyShadeViewHeight() {
return mView.getEmptyShadeViewHeight();
}
public void setAlpha(float alpha) {
mView.setAlpha(alpha);
}
public float calculateAppearFraction(float height) {
return mView.calculateAppearFraction(height);
}
public void onExpansionStarted() {
mView.onExpansionStarted();
checkSnoozeLeavebehind();
}
public void onExpansionStopped() {
mView.setCheckForLeaveBehind(false);
mView.onExpansionStopped();
}
public void onPanelTrackingStarted() {
mView.onPanelTrackingStarted();
}
public void onPanelTrackingStopped() {
mView.onPanelTrackingStopped();
}
public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
mView.setHeadsUpBoundaries(height, bottomBarHeight);
}
public void setUnlockHintRunning(boolean running) {
mView.setUnlockHintRunning(running);
}
public void setPanelFlinging(boolean flinging) {
mView.setPanelFlinging(flinging);
}
public boolean isFooterViewNotGone() {
return mView.isFooterViewNotGone();
}
public boolean isFooterViewContentVisible() {
return mView.isFooterViewContentVisible();
}
public int getFooterViewHeightWithPadding() {
return mView.getFooterViewHeightWithPadding();
}
/**
* Update whether we should show the empty shade view (no notifications in the shade).
* If so, send the update to our view.
*
* When in split mode, notifications are always visible regardless of the state of the
* QuickSettings panel. That being the case, empty view is always shown if the other conditions
* are true.
*/
public void updateShowEmptyShadeView() {
Trace.beginSection("NSSLC.updateShowEmptyShadeView");
mShowEmptyShadeView = mStatusBarStateController.getCurrentOrUpcomingState() != KEYGUARD
&& !mView.isQsFullScreen()
&& getVisibleNotificationCount() == 0;
mView.updateEmptyShadeView(
mShowEmptyShadeView,
mZenModeController.areNotificationsHiddenInShade());
Trace.endSection();
}
public boolean isShowingEmptyShadeView() {
return mShowEmptyShadeView;
}
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
mView.setHeadsUpAnimatingAway(headsUpAnimatingAway);
}
public HeadsUpTouchHelper.Callback getHeadsUpCallback() {
return mView.getHeadsUpCallback();
}
public void forceNoOverlappingRendering(boolean force) {
mView.forceNoOverlappingRendering(force);
}
public void setExpandingVelocity(float velocity) {
mView.setExpandingVelocity(velocity);
}
public void setExpandedHeight(float expandedHeight) {
mView.setExpandedHeight(expandedHeight);
}
/** Sets the QS header. Used to check if a touch is within its bounds. */
public void setQsHeader(ViewGroup view) {
mView.setQsHeader(view);
}
public void setAnimationsEnabled(boolean enabled) {
mView.setAnimationsEnabled(enabled);
}
public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) {
mView.setDozing(dozing, animate, wakeUpTouchLocation);
}
public void setPulsing(boolean pulsing, boolean animatePulse) {
mView.setPulsing(pulsing, animatePulse);
}
/**
* Return whether there are any clearable notifications
*/
public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
return hasNotifications(selection, true /* clearable */);
}
public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
boolean hasAlertingMatchingClearable = isClearable
? mNotifStats.getHasClearableAlertingNotifs()
: mNotifStats.getHasNonClearableAlertingNotifs();
boolean hasSilentMatchingClearable = isClearable
? mNotifStats.getHasClearableSilentNotifs()
: mNotifStats.getHasNonClearableSilentNotifs();
switch (selection) {
case ROWS_GENTLE:
return hasSilentMatchingClearable;
case ROWS_HIGH_PRIORITY:
return hasAlertingMatchingClearable;
case ROWS_ALL:
return hasSilentMatchingClearable || hasAlertingMatchingClearable;
default:
throw new IllegalStateException("Bad selection: " + selection);
}
}
/**
* Set the maximum number of notifications that can currently be displayed
*/
public void setMaxDisplayedNotifications(int maxNotifications) {
mNotificationListContainer.setMaxDisplayedNotifications(maxNotifications);
}
/**
* This is used for debugging only; it will be used to draw the otherwise invisible line which
* NotificationPanelViewController treats as the bottom when calculating how many notifications
* appear on the keyguard.
* Setting a negative number will disable rendering this line.
*/
public void setKeyguardBottomPaddingForDebug(float keyguardBottomPadding) {
mView.setKeyguardBottomPadding(keyguardBottomPadding);
}
public RemoteInputController.Delegate createDelegate() {
return new RemoteInputController.Delegate() {
public void setRemoteInputActive(NotificationEntry entry,
boolean remoteInputActive) {
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
entry.notifyHeightChanged(true /* needsAnimation */);
updateFooter();
}
public void lockScrollTo(NotificationEntry entry) {
mView.lockScrollTo(entry.getRow());
}
public void requestDisallowLongPressAndDismiss() {
mView.requestDisallowLongPress();
mView.requestDisallowDismiss();
}
};
}
public void updateFooter() {
Trace.beginSection("NSSLC.updateFooter");
mView.updateFooter();
Trace.endSection();
}
public void onUpdateRowStates() {
mView.onUpdateRowStates();
}
public ActivatableNotificationView getActivatedChild() {
return mView.getActivatedChild();
}
public void setActivatedChild(ActivatableNotificationView view) {
mView.setActivatedChild(view);
}
public void runAfterAnimationFinished(Runnable r) {
mView.runAfterAnimationFinished(r);
}
public void setShelfController(NotificationShelfController notificationShelfController) {
mView.setShelfController(notificationShelfController);
}
public ExpandableView getFirstChildNotGone() {
return mView.getFirstChildNotGone();
}
private void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
mView.generateHeadsUpAnimation(entry, isHeadsUp);
}
public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
mView.generateHeadsUpAnimation(row, isHeadsUp);
}
public void setMaxTopPadding(int padding) {
mView.setMaxTopPadding(padding);
}
public int getTransientViewCount() {
return mView.getTransientViewCount();
}
public View getTransientView(int i) {
return mView.getTransientView(i);
}
public int getPositionInLinearLayout(ExpandableView row) {
return mView.getPositionInLinearLayout(row);
}
public NotificationStackScrollLayout getView() {
return mView;
}
public float calculateGapHeight(ExpandableView previousView, ExpandableView child, int count) {
return mView.calculateGapHeight(previousView, child, count);
}
NotificationRoundnessManager getNoticationRoundessManager() {
return mNotificationRoundnessManager;
}
NotificationListContainer getNotificationListContainer() {
return mNotificationListContainer;
}
public NotifStackController getNotifStackController() {
return mNotifStackController;
}
public void resetCheckSnoozeLeavebehind() {
mView.resetCheckSnoozeLeavebehind();
}
private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) {
return new DismissedByUserStats(
DISMISSAL_SHADE,
DISMISS_SENTIMENT_NEUTRAL,
mVisibilityProvider.obtain(entry, true));
}
public void closeControlsIfOutsideTouch(MotionEvent ev) {
NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
View translatingParentView = mSwipeHelper.getTranslatingParentView();
View view = null;
if (guts != null && !guts.getGutsContent().isLeavebehind()) {
// Only close visible guts if they're not a leavebehind.
view = guts;
} else if (menuRow != null && menuRow.isMenuVisible()
&& translatingParentView != null) {
// Checking menu
view = translatingParentView;
}
if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) {
// Touch was outside visible guts / menu notification, close what's visible
mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */,
false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
false /* resetMenu */);
mSwipeHelper.resetExposedMenuView(true /* animate */, true /* force */);
}
}
public void clearSilentNotifications() {
// Leave the shade open if there will be other notifs left over to clear
final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
mView.clearNotifications(ROWS_GENTLE, closeShade);
}
private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove,
@SelectedRows int selectedRows) {
if (selectedRows == ROWS_ALL) {
mNotifCollection.dismissAllNotifications(
mLockscreenUserManager.getCurrentUserId());
} else {
final List<Pair<NotificationEntry, DismissedByUserStats>>
entriesWithRowsDismissedFromShade = new ArrayList<>();
for (ExpandableNotificationRow row : viewsToRemove) {
final NotificationEntry entry = row.getEntry();
entriesWithRowsDismissedFromShade.add(
new Pair<>(entry, getDismissedByUserStats(entry)));
}
mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade);
}
}
/**
* @return the expand helper callback.
*/
public ExpandHelper.Callback getExpandHelperCallback() {
return mView.getExpandHelperCallback();
}
/**
* @return If the shade is in the locked down shade.
*/
public boolean isInLockedDownShade() {
return mDynamicPrivacyController.isInLockedDownShade();
}
public boolean isLongPressInProgress() {
return mLongPressedView != null;
}
/**
* Set the dimmed state for all of the notification views.
*/
public void setDimmed(boolean dimmed, boolean animate) {
mView.setDimmed(dimmed, animate);
}
/**
* @return the inset during the full shade transition, that needs to be added to the position
* of the quick settings edge. This is relevant for media, that is transitioning
* from the keyguard host to the quick settings one.
*/
public int getFullShadeTransitionInset() {
MediaContainerView view = mKeyguardMediaController.getSinglePaneContainer();
if (view == null || view.getHeight() == 0
|| mStatusBarStateController.getState() != KEYGUARD) {
return 0;
}
return view.getHeight() + mView.getPaddingAfterMedia();
}
/**
* @param fraction The fraction of lockscreen to shade transition.
* 0f for all other states.
*
* Once the lockscreen to shade transition completes and the shade is 100% open,
* LockscreenShadeTransitionController resets amount and fraction to 0, where they remain
* until the next lockscreen-to-shade transition.
*/
public void setTransitionToFullShadeAmount(float fraction) {
mView.setFractionToShade(fraction);
}
/**
* Sets the amount of vertical over scroll that should be performed on NSSL.
*/
public void setOverScrollAmount(int overScrollAmount) {
mView.setExtraTopInsetForFullShadeTransition(overScrollAmount);
}
/** */
public void setWillExpand(boolean willExpand) {
mView.setWillExpand(willExpand);
}
/**
* Set a listener to when scrolling changes.
*/
public void setOnScrollListener(Consumer<Integer> listener) {
mView.setOnScrollListener(listener);
}
/**
* Set rounded rect clipping bounds on this view.
*/
public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
int bottomRadius) {
mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius);
}
/**
* Request an animation whenever the toppadding changes next
*/
public void animateNextTopPaddingChange() {
mView.animateNextTopPaddingChange();
}
public void setNotificationActivityStarter(NotificationActivityStarter activityStarter) {
mNotificationActivityStarter = activityStarter;
}
/**
* Enum for UiEvent logged from this class
*/
enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
INVALID(0),
@UiEvent(doc = "User dismissed all notifications from notification panel.")
DISMISS_ALL_NOTIFICATIONS_PANEL(312),
@UiEvent(doc = "User dismissed all silent notifications from notification panel.")
DISMISS_SILENT_NOTIFICATIONS_PANEL(314);
private final int mId;
NotificationPanelEvent(int id) {
mId = id;
}
@Override public int getId() {
return mId;
}
public static UiEventLogger.UiEventEnum fromSelection(@SelectedRows int selection) {
if (selection == ROWS_ALL) {
return DISMISS_ALL_NOTIFICATIONS_PANEL;
}
if (selection == NotificationStackScrollLayout.ROWS_GENTLE) {
return DISMISS_SILENT_NOTIFICATIONS_PANEL;
}
if (NotificationStackScrollLayoutController.DEBUG) {
throw new IllegalArgumentException("Unexpected selection" + selection);
}
return INVALID;
}
}
private class NotificationListContainerImpl implements NotificationListContainer {
@Override
public void setChildTransferInProgress(boolean childTransferInProgress) {
mView.setChildTransferInProgress(childTransferInProgress);
}
@Override
public void changeViewPosition(ExpandableView child, int newIndex) {
mView.changeViewPosition(child, newIndex);
}
@Override
public void notifyGroupChildAdded(ExpandableView row) {
mView.notifyGroupChildAdded(row);
}
@Override
public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {
mView.notifyGroupChildRemoved(row, childrenContainer);
}
@Override
public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
mView.generateAddAnimation(child, fromMoreCard);
}
@Override
public void generateChildOrderChangedEvent() {
mView.generateChildOrderChangedEvent();
}
@Override
public int getContainerChildCount() {
return mView.getContainerChildCount();
}
@Override
public void setNotificationActivityStarter(
NotificationActivityStarter notificationActivityStarter) {
NotificationStackScrollLayoutController.this
.setNotificationActivityStarter(notificationActivityStarter);
}
@Override
public int getTopClippingStartLocation() {
return mView.getTopClippingStartLocation();
}
@Override
public View getContainerChildAt(int i) {
return mView.getContainerChildAt(i);
}
@Override
public void removeContainerView(View v) {
mView.removeContainerView(v);
}
@Override
public void addContainerView(View v) {
mView.addContainerView(v);
}
@Override
public void addContainerViewAt(View v, int index) {
mView.addContainerViewAt(v, index);
}
@Override
public void setMaxDisplayedNotifications(int maxNotifications) {
mView.setMaxDisplayedNotifications(maxNotifications);
}
@Override
public ViewGroup getViewParentForNotification(NotificationEntry entry) {
return mView.getViewParentForNotification(entry);
}
@Override
public void resetExposedMenuView(boolean animate, boolean force) {
mSwipeHelper.resetExposedMenuView(animate, force);
}
@Override
public NotificationSwipeActionHelper getSwipeActionHelper() {
return mSwipeHelper;
}
@Override
public void cleanUpViewStateForEntry(NotificationEntry entry) {
mView.cleanUpViewStateForEntry(entry);
}
@Override
public void setChildLocationsChangedListener(
NotificationLogger.OnChildLocationsChangedListener listener) {
mView.setChildLocationsChangedListener(listener);
}
public boolean hasPulsingNotifications() {
return mView.hasPulsingNotifications();
}
@Override
public boolean isInVisibleLocation(NotificationEntry entry) {
return NotificationStackScrollLayoutController.this.isInVisibleLocation(entry);
}
@Override
public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
mView.onChildHeightChanged(view, needsAnimation);
}
@Override
public void onReset(ExpandableView view) {
mView.onChildHeightReset(view);
}
@Override
public void bindRow(ExpandableNotificationRow row) {
row.setHeadsUpAnimatingAwayListener(animatingAway -> {
mNotificationRoundnessManager.updateView(row, false);
mHeadsUpAppearanceController.updateHeader(row.getEntry());
});
}
@Override
public void applyLaunchAnimationParams(LaunchAnimationParameters params) {
mView.applyLaunchAnimationParams(params);
}
@Override
public void setExpandingNotification(ExpandableNotificationRow row) {
mView.setExpandingNotification(row);
}
@Override
public boolean containsView(View v) {
return mView.containsView(v);
}
@Override
public void setWillExpand(boolean willExpand) {
mView.setWillExpand(willExpand);
}
}
class TouchHandler implements Gefingerpoken {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
mView.initDownStates(ev);
mView.handleEmptySpaceClick(ev);
NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
boolean longPressWantsIt = false;
if (mLongPressedView != null) {
longPressWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
}
boolean expandWantsIt = false;
if (mLongPressedView == null && !mSwipeHelper.isSwiping()
&& !mView.getOnlyScrollingInThisMotion() && guts == null) {
expandWantsIt = mView.getExpandHelper().onInterceptTouchEvent(ev);
}
boolean scrollWantsIt = false;
if (mLongPressedView == null && !mSwipeHelper.isSwiping()
&& !mView.isExpandingNotification()) {
scrollWantsIt = mView.onInterceptTouchEventScroll(ev);
}
boolean swipeWantsIt = false;
if (mLongPressedView == null && !mView.isBeingDragged()
&& !mView.isExpandingNotification()
&& !mView.getExpandedInThisMotion()
&& !mView.getOnlyScrollingInThisMotion()
&& !mView.getDisallowDismissInThisMotion()) {
swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
}
// Check if we need to clear any snooze leavebehinds
boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt &&
!expandWantsIt && !scrollWantsIt) {
mView.setCheckForLeaveBehind(false);
mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
false /* resetMenu */);
}
if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
mView.setCheckForLeaveBehind(true);
}
// When swiping directly on the NSSL, this would only get an onTouchEvent.
// We log any touches other than down, which will be captured by onTouchEvent.
// In the intercept we only start tracing when it's not a down (otherwise that down
// would be duplicated when intercepted).
if (mJankMonitor != null && scrollWantsIt
&& ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
mJankMonitor.begin(mView, CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
}
return swipeWantsIt || scrollWantsIt || expandWantsIt || longPressWantsIt;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
|| ev.getActionMasked() == MotionEvent.ACTION_UP;
mView.handleEmptySpaceClick(ev);
boolean longPressWantsIt = false;
if (guts != null && mLongPressedView != null) {
longPressWantsIt = mSwipeHelper.onTouchEvent(ev);
}
boolean expandWantsIt = false;
boolean onlyScrollingInThisMotion = mView.getOnlyScrollingInThisMotion();
boolean expandingNotification = mView.isExpandingNotification();
if (mLongPressedView == null && mView.getIsExpanded()
&& !mSwipeHelper.isSwiping() && !onlyScrollingInThisMotion && guts == null) {
ExpandHelper expandHelper = mView.getExpandHelper();
if (isCancelOrUp) {
expandHelper.onlyObserveMovements(false);
}
boolean wasExpandingBefore = expandingNotification;
expandWantsIt = expandHelper.onTouchEvent(ev);
expandingNotification = mView.isExpandingNotification();
if (mView.getExpandedInThisMotion() && !expandingNotification && wasExpandingBefore
&& !mView.getDisallowScrollingInThisMotion()) {
mView.dispatchDownEventToScroller(ev);
}
}
boolean scrollerWantsIt = false;
if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping()
&& !expandingNotification && !mView.getDisallowScrollingInThisMotion()) {
scrollerWantsIt = mView.onScrollTouch(ev);
}
boolean horizontalSwipeWantsIt = false;
if (mLongPressedView == null && !mView.isBeingDragged()
&& !expandingNotification
&& !mView.getExpandedInThisMotion()
&& !onlyScrollingInThisMotion
&& !mView.getDisallowDismissInThisMotion()) {
horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
}
// Check if we need to clear any snooze leavebehinds
if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts)
&& guts.getGutsContent() instanceof NotificationSnooze) {
NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
if ((ns.isExpanded() && isCancelOrUp)
|| (!horizontalSwipeWantsIt && scrollerWantsIt)) {
// If the leavebehind is expanded we clear it on the next up event, otherwise we
// clear it on the next non-horizontal swipe or expand event.
checkSnoozeLeavebehind();
}
}
if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
// Ensure the falsing manager records the touch. we don't do anything with it
// at the moment.
mFalsingManager.isFalseTouch(Classifier.SHADE_DRAG);
mView.setCheckForLeaveBehind(true);
}
traceJankOnTouchEvent(ev.getActionMasked(), scrollerWantsIt);
return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || longPressWantsIt;
}
private void traceJankOnTouchEvent(int action, boolean scrollerWantsIt) {
if (mJankMonitor == null) {
Log.w(TAG, "traceJankOnTouchEvent, mJankMonitor is null");
return;
}
// Handle interaction jank monitor cases.
switch (action) {
case MotionEvent.ACTION_DOWN:
if (scrollerWantsIt) {
mJankMonitor.begin(mView, CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
}
break;
case MotionEvent.ACTION_UP:
if (scrollerWantsIt && !mView.isFlingAfterUpEvent()) {
mJankMonitor.end(CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
}
break;
case MotionEvent.ACTION_CANCEL:
if (scrollerWantsIt) {
mJankMonitor.cancel(CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
}
break;
}
}
}
private class NotifStackControllerImpl implements NotifStackController {
@Override
public void setNotifStats(@NonNull NotifStats notifStats) {
mNotifStats = notifStats;
updateFooter();
updateShowEmptyShadeView();
}
}
}