[Status Bar Refactor] Move PanelBar's touch handling into

NotificationPanelViewController.

All the code should be exactly the same, it's just
happening in a different place.

Bug: 202981994
Bug: 200063118
Test: Manual: Can still interact with status bar and notification shade
in all the typical ways
Test: atest NotificationPanelViewControllerTest,
PhoneStatusBarViewControllerTest, PhoneStatusBarViewTest, StatusBarTest

Change-Id: Id882cff69d4670dd1ac89264c689e63d15240287
This commit is contained in:
Caitlin Cassidy
2021-10-11 21:04:43 +00:00
parent 79dd2c6afb
commit 8add0cb7ff
9 changed files with 218 additions and 129 deletions

View File

@@ -3766,6 +3766,45 @@ public class NotificationPanelViewController extends PanelViewController {
private long mLastTouchDownTime = -1L;
@Override
public boolean onTouchForwardedFromStatusBar(MotionEvent event) {
// TODO(b/202981994): Move the touch debugging in this method to a central location.
// (Right now, it's split between StatusBar and here.)
// If panels aren't enabled, ignore the gesture and don't pass it down to the
// panel view.
if (!mCommandQueue.panelsEnabled()) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.v(
TAG,
String.format(
"onTouchForwardedFromStatusBar: "
+ "panel disabled, ignoring touch at (%d,%d)",
(int) event.getX(),
(int) event.getY()
)
);
}
return false;
}
// If the view that would receive the touch is disabled, just have status bar eat
// the gesture.
if (event.getAction() == MotionEvent.ACTION_DOWN && !mView.isEnabled()) {
Log.v(TAG,
String.format(
"onTouchForwardedFromStatusBar: "
+ "panel view disabled, eating touch at (%d,%d)",
(int) event.getX(),
(int) event.getY()
)
);
return true;
}
return mView.dispatchTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mBlockTouches || mQs.disallowPanelTouches()) {
@@ -3777,7 +3816,7 @@ public class NotificationPanelViewController extends PanelViewController {
if (mStatusBar.isBouncerShowing()) {
return true;
}
if (mBar.panelEnabled()
if (mCommandQueue.panelsEnabled()
&& !mNotificationStackScrollLayoutController.isLongPressInProgress()
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
@@ -4632,4 +4671,10 @@ public class NotificationPanelViewController extends PanelViewController {
public PanelBar.PanelStateChangeListener getPanelStateChangeListener() {
return mPanelStateChangeListener;
}
/** Returns the handler that the status bar should forward touches to. */
public PhoneStatusBarView.TouchEventHandler getStatusBarTouchEventHandler() {
return getTouchHandler()::onTouchForwardedFromStatusBar;
}
}

View File

@@ -25,7 +25,6 @@ import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
@@ -53,7 +52,6 @@ public abstract class PanelBar extends FrameLayout {
public static final int STATE_OPENING = 1;
public static final int STATE_OPEN = 2;
private PanelViewController mPanel;
@Nullable private PanelStateChangeListener mPanelStateChangeListener;
private int mState = STATE_CLOSED;
private boolean mTracking;
@@ -105,54 +103,11 @@ public abstract class PanelBar extends FrameLayout {
super.onFinishInflate();
}
/** Set the PanelViewController */
public void setPanel(PanelViewController pv) {
mPanel = pv;
pv.setBar(this);
}
/** Sets the listener that will be notified of panel state changes. */
public void setPanelStateChangeListener(PanelStateChangeListener listener) {
mPanelStateChangeListener = listener;
}
public boolean panelEnabled() {
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Allow subclasses to implement enable/disable semantics
if (!panelEnabled()) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.v(TAG, String.format("onTouch: all panels disabled, ignoring touch at (%d,%d)",
(int) event.getX(), (int) event.getY()));
}
return false;
}
if (event.getAction() == MotionEvent.ACTION_DOWN) {
final PanelViewController panel = mPanel;
if (panel == null) {
// panel is not there, so we'll eat the gesture
Log.v(TAG, String.format("onTouch: no panel for touch at (%d,%d)",
(int) event.getX(), (int) event.getY()));
return true;
}
boolean enabled = panel.isEnabled();
if (DEBUG) LOG("PanelBar.onTouch: state=%d ACTION_DOWN: panel %s %s", mState, panel,
(enabled ? "" : " (disabled)"));
if (!enabled) {
// panel is disabled, so we'll eat the gesture
Log.v(TAG, String.format(
"onTouch: panel (%s) is disabled, ignoring touch at (%d,%d)",
panel, (int) event.getX(), (int) event.getY()));
return true;
}
}
return mPanel == null || mPanel.getView().dispatchTouchEvent(event);
}
/**
* @param frac the fraction from the expansion in [0, 1]
* @param expanded whether the panel is currently expanded; this is independent from the

View File

@@ -185,6 +185,8 @@ public abstract class PanelViewController {
protected final SysuiStatusBarStateController mStatusBarStateController;
protected final AmbientState mAmbientState;
protected final LockscreenGestureLogger mLockscreenGestureLogger;
private final TouchHandler mTouchHandler;
protected void onExpandingFinished() {
mBar.onExpandingFinished();
@@ -226,6 +228,7 @@ public abstract class PanelViewController {
mView = view;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mLockscreenGestureLogger = lockscreenGestureLogger;
mTouchHandler = createTouchHandler();
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -238,7 +241,7 @@ public abstract class PanelViewController {
});
mView.addOnLayoutChangeListener(createLayoutChangeListener());
mView.setOnTouchListener(createTouchHandler());
mView.setOnTouchListener(mTouchHandler);
mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
mResources = mView.getResources();
@@ -289,6 +292,10 @@ public abstract class PanelViewController {
: mTouchSlop;
}
protected TouchHandler getTouchHandler() {
return mTouchHandler;
}
private void addMovement(MotionEvent event) {
// Add movement to velocity tracker using raw screen X and Y coordinates instead
// of window coordinates because the window frame may be moving at the same time.
@@ -1153,23 +1160,28 @@ public abstract class PanelViewController {
return mView;
}
public boolean isEnabled() {
return mView.isEnabled();
}
public OnLayoutChangeListener createLayoutChangeListener() {
return new OnLayoutChangeListener();
}
protected TouchHandler createTouchHandler() {
return new TouchHandler();
}
protected abstract TouchHandler createTouchHandler();
protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
return new OnConfigurationChangedListener();
}
public class TouchHandler implements View.OnTouchListener {
public abstract class TouchHandler implements View.OnTouchListener {
/**
* Method called when a touch has occurred on {@link PhoneStatusBarView}.
*
* Touches that occur on the status bar view may have ramifications for the notification
* panel (e.g. a touch that pulls down the shade could start on the status bar), so we need
* to notify the panel controller when these touches occur.
*
* Returns true if the event was handled and false otherwise.
*/
public abstract boolean onTouchForwardedFromStatusBar(MotionEvent event);
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
&& event.getActionMasked() != MotionEvent.ACTION_DOWN)) {

View File

@@ -24,7 +24,6 @@ import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
import android.view.DisplayCutout;
@@ -37,7 +36,6 @@ import android.view.accessibility.AccessibilityEvent;
import android.widget.LinearLayout;
import com.android.systemui.Dependency;
import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
@@ -69,8 +67,8 @@ public class PhoneStatusBarView extends PanelBar {
private List<StatusBar.ExpansionChangedListener> mExpansionChangedListeners;
@Nullable
private PanelExpansionStateChangedListener mPanelExpansionStateChangedListener;
private PanelEnabledProvider mPanelEnabledProvider;
@Nullable
private TouchEventHandler mTouchEventHandler;
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -95,6 +93,10 @@ public class PhoneStatusBarView extends PanelBar {
mPanelExpansionStateChangedListener = listener;
}
void setTouchEventHandler(TouchEventHandler handler) {
mTouchEventHandler = handler;
}
public void setScrimController(ScrimController scrimController) {
mScrimController = scrimController;
}
@@ -168,15 +170,6 @@ public class PhoneStatusBarView extends PanelBar {
return changed;
}
@Override
public boolean panelEnabled() {
if (mPanelEnabledProvider == null) {
Log.e(TAG, "panelEnabledProvider is null; defaulting to super class.");
return super.panelEnabled();
}
return mPanelEnabledProvider.panelEnabled();
}
@Override
public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
if (super.onRequestSendAccessibilityEventInternal(child, event)) {
@@ -201,15 +194,18 @@ public class PhoneStatusBarView extends PanelBar {
@Override
public boolean onTouchEvent(MotionEvent event) {
mBar.onTouchEvent(event);
if (DEBUG_GESTURES) {
if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
EventLog.writeEvent(EventLogTags.SYSUI_PANELBAR_TOUCH,
event.getActionMasked(), (int) event.getX(), (int) event.getY());
}
if (mTouchEventHandler == null) {
Log.w(
TAG,
String.format(
"onTouch: No touch handler provided; eating gesture at (%d,%d)",
(int) event.getX(),
(int) event.getY()
)
);
return true;
}
return super.onTouchEvent(event);
return mTouchEventHandler.handleTouchEvent(event);
}
@Override
@@ -261,11 +257,6 @@ public class PhoneStatusBarView extends PanelBar {
}
}
/** Set the {@link PanelEnabledProvider} to use. */
public void setPanelEnabledProvider(PanelEnabledProvider panelEnabledProvider) {
mPanelEnabledProvider = panelEnabledProvider;
}
public void updateResources() {
mCutoutSideNudge = getResources().getDimensionPixelSize(
R.dimen.display_cutout_margin_consumption);
@@ -345,15 +336,20 @@ public class PhoneStatusBarView extends PanelBar {
getPaddingBottom());
}
/** An interface that will provide whether panel is enabled. */
interface PanelEnabledProvider {
/** Returns true if the panel is enabled and false otherwise. */
boolean panelEnabled();
}
/** A listener that will be notified when a panel's expansion state may have changed. */
public interface PanelExpansionStateChangedListener {
/** Called when a panel's expansion state may have changed. */
void onPanelExpansionStateChanged();
}
/**
* A handler repsonsible for all touch event handling on the status bar.
*
* The handler will be notified each time {@link this#onTouchEvent} is called, and the return
* value from the handler will be returned from {@link this#onTouchEvent}.
**/
public interface TouchEventHandler {
/** Called each time {@link this#onTouchEvent} is called. */
boolean handleTouchEvent(MotionEvent event);
}
}

View File

@@ -26,18 +26,16 @@ import com.android.systemui.util.ViewController
/** Controller for [PhoneStatusBarView]. */
class PhoneStatusBarViewController(
view: PhoneStatusBarView,
commandQueue: CommandQueue,
statusBarMoveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
panelExpansionStateChangedListener: PhoneStatusBarView.PanelExpansionStateChangedListener,
touchEventHandler: PhoneStatusBarView.TouchEventHandler,
) : ViewController<PhoneStatusBarView>(view) {
override fun onViewAttached() {}
override fun onViewDetached() {}
init {
mView.setPanelEnabledProvider {
commandQueue.panelsEnabled()
}
mView.setTouchEventHandler(touchEventHandler)
mView.setPanelExpansionStateChangedListener(panelExpansionStateChangedListener)
statusBarMoveFromCenterAnimationController?.let { animationController ->

View File

@@ -1167,7 +1167,6 @@ public class StatusBar extends SystemUI implements
PhoneStatusBarView oldStatusBarView = mStatusBarView;
mStatusBarView = (PhoneStatusBarView) statusBarFragment.getView();
mStatusBarView.setBar(this);
mStatusBarView.setPanel(mNotificationPanelViewController);
mStatusBarView.setPanelStateChangeListener(
mNotificationPanelViewController.getPanelStateChangeListener());
mStatusBarView.setScrimController(mScrimController);
@@ -1176,6 +1175,8 @@ public class StatusBar extends SystemUI implements
sendInitialExpansionAmount(listener);
}
mNotificationPanelViewController.setBar(mStatusBarView);
StatusBarMoveFromCenterAnimationController moveFromCenterAnimation = null;
if (mUnfoldTransitionConfig.isEnabled()) {
moveFromCenterAnimation = mMoveFromCenterAnimation.get();
@@ -1183,9 +1184,10 @@ public class StatusBar extends SystemUI implements
mPhoneStatusBarViewController =
new PhoneStatusBarViewController(
mStatusBarView,
mCommandQueue,
moveFromCenterAnimation,
this::onPanelExpansionStateChanged);
this::onPanelExpansionStateChanged,
mNotificationPanelViewController.getStatusBarTouchEventHandler()
);
mPhoneStatusBarViewController.init();
mBatteryMeterViewController = new BatteryMeterViewController(
@@ -2208,6 +2210,8 @@ public class StatusBar extends SystemUI implements
/** Called when a touch event occurred on {@link PhoneStatusBarView}. */
public void onTouchEvent(MotionEvent event) {
// TODO(b/202981994): Move this touch debugging to a central location. (Right now, it's
// split between NotificationPanelViewController and here.)
if (DEBUG_GESTURES) {
if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH,

View File

@@ -518,6 +518,51 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse();
}
@Test
public void onTouchForwardedFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
when(mCommandQueue.panelsEnabled()).thenReturn(false);
boolean returnVal = mTouchHandler.onTouchForwardedFromStatusBar(
MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
assertThat(returnVal).isFalse();
verify(mView, never()).dispatchTouchEvent(any());
}
@Test
public void onTouchForwardedFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() {
when(mCommandQueue.panelsEnabled()).thenReturn(true);
when(mView.isEnabled()).thenReturn(false);
boolean returnVal = mTouchHandler.onTouchForwardedFromStatusBar(
MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0));
assertThat(returnVal).isTrue();
verify(mView, never()).dispatchTouchEvent(any());
}
@Test
public void onTouchForwardedFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
when(mCommandQueue.panelsEnabled()).thenReturn(true);
when(mView.isEnabled()).thenReturn(false);
MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0);
mTouchHandler.onTouchForwardedFromStatusBar(event);
verify(mView).dispatchTouchEvent(event);
}
@Test
public void onTouchForwardedFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
when(mCommandQueue.panelsEnabled()).thenReturn(true);
when(mView.isEnabled()).thenReturn(true);
MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0);
mTouchHandler.onTouchForwardedFromStatusBar(event);
verify(mView).dispatchTouchEvent(event);
}
@Test
public void testA11y_initializeNode() {
AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();

View File

@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
@@ -31,12 +32,14 @@ import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
@SmallTest
class PhoneStatusBarViewControllerTest : SysuiTestCase() {
private val stateChangeListener = TestStateChangedListener()
private val touchEventHandler = TestTouchEventHandler()
@Mock
private lateinit var commandQueue: CommandQueue
@@ -63,37 +66,31 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
val parent = FrameLayout(mContext) // add parent to keep layout params
view = LayoutInflater.from(mContext)
.inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
view.setPanel(panelViewController)
view.setScrimController(scrimController)
view.setBar(mock(StatusBar::class.java))
}
controller = PhoneStatusBarViewController(
view,
commandQueue,
null,
stateChangeListener
stateChangeListener,
touchEventHandler,
)
}
@Test
fun constructor_setsPanelEnabledProviderOnView() {
var providerUsed = false
`when`(commandQueue.panelsEnabled()).then {
providerUsed = true
true
}
fun constructor_setsTouchHandlerOnView() {
val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
// If the constructor correctly set a [PanelEnabledProvider], then it should be used
// when [PhoneStatusBarView.panelEnabled] is called.
view.panelEnabled()
view.onTouchEvent(event)
assertThat(providerUsed).isTrue()
assertThat(touchEventHandler.lastEvent).isEqualTo(event)
}
@Test
fun constructor_moveFromCenterAnimationIsNotNull_moveFromCenterAnimationInitialized() {
controller = PhoneStatusBarViewController(
view, commandQueue, moveFromCenterAnimation, stateChangeListener
view, moveFromCenterAnimation, stateChangeListener, touchEventHandler
)
verify(moveFromCenterAnimation).init(any(), any())
@@ -117,4 +114,13 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
stateChangeCalled = true
}
}
private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler {
var lastEvent: MotionEvent? = null
override fun handleTouchEvent(event: MotionEvent?): Boolean {
lastEvent = event
return false
}
}
}

View File

@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone
import android.view.MotionEvent
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -48,31 +49,10 @@ class PhoneStatusBarViewTest : SysuiTestCase() {
`when`(panelViewController.view).thenReturn(panelView)
view = PhoneStatusBarView(mContext, null)
view.setPanel(panelViewController)
view.setScrimController(scrimController)
view.setBar(statusBar)
}
@Test
fun panelEnabled_providerReturnsTrue_returnsTrue() {
view.setPanelEnabledProvider { true }
assertThat(view.panelEnabled()).isTrue()
}
@Test
fun panelEnabled_providerReturnsFalse_returnsFalse() {
view.setPanelEnabledProvider { false }
assertThat(view.panelEnabled()).isFalse()
}
@Test
fun panelEnabled_noProvider_noCrash() {
view.panelEnabled()
// No assert needed, just testing no crash
}
@Test
fun panelExpansionChanged_fracZero_stateChangeListenerNotified() {
val listener = TestExpansionStateChangedListener()
@@ -149,6 +129,45 @@ class PhoneStatusBarViewTest : SysuiTestCase() {
// No assert needed, just testing no crash
}
@Test
fun onTouchEvent_listenerNotified() {
val handler = TestTouchEventHandler()
view.setTouchEventHandler(handler)
val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
view.onTouchEvent(event)
assertThat(handler.lastEvent).isEqualTo(event)
}
@Test
fun onTouchEvent_listenerReturnsTrue_viewReturnsTrue() {
val handler = TestTouchEventHandler()
view.setTouchEventHandler(handler)
val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
handler.returnValue = true
assertThat(view.onTouchEvent(event)).isTrue()
}
@Test
fun onTouchEvent_listenerReturnsFalse_viewReturnsFalse() {
val handler = TestTouchEventHandler()
view.setTouchEventHandler(handler)
val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
handler.returnValue = false
assertThat(view.onTouchEvent(event)).isFalse()
}
@Test
fun onTouchEvent_noListener_noCrash() {
view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
// No assert needed, just testing no crash
}
private class TestExpansionStateChangedListener
: PhoneStatusBarView.PanelExpansionStateChangedListener {
var stateChangeCalled: Boolean = false
@@ -164,4 +183,13 @@ class PhoneStatusBarViewTest : SysuiTestCase() {
this.state = state
}
}
private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler {
var lastEvent: MotionEvent? = null
var returnValue: Boolean = false
override fun handleTouchEvent(event: MotionEvent?): Boolean {
lastEvent = event
return returnValue
}
}
}