Merge "Updated OverlayPanelViewController to allow for showing overlay panel from the bottom navigation bar." into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
c65d2c3e06
@@ -72,11 +72,21 @@
|
||||
|
||||
<!-- Car System UI's OverlayViewsMediator-->
|
||||
<string-array name="config_carSystemUIOverlayViewsMediators" translatable="false">
|
||||
<item>com.android.systemui.car.notification.NotificationPanelViewMediator</item>
|
||||
<item>@string/config_notificationPanelViewMediator</item>
|
||||
<item>com.android.systemui.car.keyguard.CarKeyguardViewMediator</item>
|
||||
<item>com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator</item>
|
||||
</string-array>
|
||||
|
||||
<!--
|
||||
Car SystemUI's notification mediator. Replace with other notification mediators to have
|
||||
the notification panel show from another system bar. The system bar should be enabled to
|
||||
use the mediator with that system bar.
|
||||
Example: config_enableBottomNavigationBar=true
|
||||
config_notificationPanelViewMediator=
|
||||
com.android.systemui.car.notification.BottomNotificationPanelViewMediator -->
|
||||
<string name="config_notificationPanelViewMediator" translatable="false">
|
||||
com.android.systemui.car.notification.TopNotificationPanelViewMediator</string>
|
||||
|
||||
<!-- List of package names that are allowed sources of app installation. -->
|
||||
<string-array name="config_allowedAppInstallSources" translatable="false">
|
||||
<item>com.android.vending</item>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.car.notification;
|
||||
|
||||
import com.android.systemui.car.CarDeviceProvisionedController;
|
||||
import com.android.systemui.car.navigationbar.CarNavigationBarController;
|
||||
import com.android.systemui.car.window.OverlayPanelViewController;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Implementation of NotificationPanelViewMediator that sets the notification panel to be opened
|
||||
* from the top navigation bar.
|
||||
*/
|
||||
@Singleton
|
||||
public class BottomNotificationPanelViewMediator extends NotificationPanelViewMediator {
|
||||
|
||||
@Inject
|
||||
public BottomNotificationPanelViewMediator(
|
||||
CarNavigationBarController carNavigationBarController,
|
||||
NotificationPanelViewController notificationPanelViewController,
|
||||
|
||||
PowerManagerHelper powerManagerHelper,
|
||||
|
||||
CarDeviceProvisionedController carDeviceProvisionedController,
|
||||
ConfigurationController configurationController
|
||||
) {
|
||||
super(carNavigationBarController,
|
||||
notificationPanelViewController,
|
||||
powerManagerHelper,
|
||||
carDeviceProvisionedController,
|
||||
configurationController);
|
||||
notificationPanelViewController.setOverlayDirection(
|
||||
OverlayPanelViewController.OVERLAY_FROM_BOTTOM_BAR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
super.registerListeners();
|
||||
getCarNavigationBarController().registerBottomBarTouchListener(
|
||||
getNotificationPanelViewController().getDragOpenTouchListener());
|
||||
}
|
||||
}
|
||||
@@ -83,9 +83,9 @@ public class NotificationPanelViewController extends OverlayPanelViewController
|
||||
private NotificationViewController mNotificationViewController;
|
||||
|
||||
private boolean mIsTracking;
|
||||
private boolean mNotificationListAtBottom;
|
||||
private boolean mNotificationListAtEnd;
|
||||
private float mFirstTouchDownOnGlassPane;
|
||||
private boolean mNotificationListAtBottomAtTimeOfTouch;
|
||||
private boolean mNotificationListAtEndAtTimeOfTouch;
|
||||
private boolean mIsSwipingVerticallyToClose;
|
||||
private boolean mIsNotificationCardSwiping;
|
||||
|
||||
@@ -233,11 +233,11 @@ public class NotificationPanelViewController extends OverlayPanelViewController
|
||||
// This allows us to initialize gesture listeners and detect when to close the notifications
|
||||
glassPane.setOnTouchListener((v, event) -> {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
|
||||
mNotificationListAtBottomAtTimeOfTouch = false;
|
||||
mNotificationListAtEndAtTimeOfTouch = false;
|
||||
}
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
mFirstTouchDownOnGlassPane = event.getRawX();
|
||||
mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom;
|
||||
mNotificationListAtEndAtTimeOfTouch = mNotificationListAtEnd;
|
||||
// Reset the tracker when there is a touch down on the glass pane.
|
||||
mIsTracking = false;
|
||||
// Pass the down event to gesture detector so that it knows where the touch event
|
||||
@@ -251,34 +251,34 @@ public class NotificationPanelViewController extends OverlayPanelViewController
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
// Check if we can scroll vertically downwards.
|
||||
if (!mNotificationList.canScrollVertically(/* direction= */ 1)) {
|
||||
mNotificationListAtBottom = true;
|
||||
// Check if we can scroll vertically in the animation direction.
|
||||
if (!mNotificationList.canScrollVertically(mAnimateDirection)) {
|
||||
mNotificationListAtEnd = true;
|
||||
return;
|
||||
}
|
||||
mNotificationListAtBottom = false;
|
||||
mNotificationListAtEnd = false;
|
||||
mIsSwipingVerticallyToClose = false;
|
||||
mNotificationListAtBottomAtTimeOfTouch = false;
|
||||
mNotificationListAtEndAtTimeOfTouch = false;
|
||||
}
|
||||
});
|
||||
|
||||
mNotificationList.setOnTouchListener((v, event) -> {
|
||||
mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX())
|
||||
> SWIPE_MAX_OFF_PATH;
|
||||
if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) {
|
||||
if (mNotificationListAtEndAtTimeOfTouch && mNotificationListAtEnd) {
|
||||
// We need to save the state here as if notification card is swiping we will
|
||||
// change the mNotificationListAtBottomAtTimeOfTouch. This is to protect
|
||||
// change the mNotificationListAtEndAtTimeOfTouch. This is to protect
|
||||
// closing the notification shade while the notification card is being swiped.
|
||||
mIsSwipingVerticallyToClose = true;
|
||||
}
|
||||
|
||||
// If the card is swiping we should not allow the notification shade to close.
|
||||
// Hence setting mNotificationListAtBottomAtTimeOfTouch to false will stop that
|
||||
// Hence setting mNotificationListAtEndAtTimeOfTouch to false will stop that
|
||||
// for us. We are also checking for mIsTracking because while swiping the
|
||||
// notification shade to close if the user goes a bit horizontal while swiping
|
||||
// upwards then also this should close.
|
||||
if (mIsNotificationCardSwiping && !mIsTracking) {
|
||||
mNotificationListAtBottomAtTimeOfTouch = false;
|
||||
mNotificationListAtEndAtTimeOfTouch = false;
|
||||
}
|
||||
|
||||
boolean handled = closeGestureDetector.onTouchEvent(event);
|
||||
@@ -290,7 +290,7 @@ public class NotificationPanelViewController extends OverlayPanelViewController
|
||||
}
|
||||
if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
|
||||
&& mIsSwipingVerticallyToClose) {
|
||||
if (getSettleClosePercentage() < getPercentageFromBottom() && isTracking) {
|
||||
if (getSettleClosePercentage() < getPercentageFromEndingEdge() && isTracking) {
|
||||
animatePanel(DEFAULT_FLING_VELOCITY, false);
|
||||
} else if (clippedHeight != getLayout().getHeight() && isTracking) {
|
||||
// this can be caused when user is at the end of the list and trying to
|
||||
@@ -299,11 +299,11 @@ public class NotificationPanelViewController extends OverlayPanelViewController
|
||||
}
|
||||
}
|
||||
|
||||
// Updating the mNotificationListAtBottomAtTimeOfTouch state has to be done after
|
||||
// Updating the mNotificationListAtEndAtTimeOfTouch state has to be done after
|
||||
// the event has been passed to the closeGestureDetector above, such that the
|
||||
// closeGestureDetector sees the up event before the state has changed.
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
|
||||
mNotificationListAtBottomAtTimeOfTouch = false;
|
||||
mNotificationListAtEndAtTimeOfTouch = false;
|
||||
}
|
||||
return handled || isTracking;
|
||||
});
|
||||
@@ -377,25 +377,31 @@ public class NotificationPanelViewController extends OverlayPanelViewController
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onScroll(int height) {
|
||||
protected void onScroll(int y) {
|
||||
if (mHandleBar != null) {
|
||||
ViewGroup.MarginLayoutParams lp =
|
||||
(ViewGroup.MarginLayoutParams) mHandleBar.getLayoutParams();
|
||||
mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin);
|
||||
// Adjust handlebar to new pointer position, and a little more depending on the
|
||||
// animate direction so the bar can be seen fully.
|
||||
if (mAnimateDirection > 0) {
|
||||
mHandleBar.setTranslationY(y - mHandleBar.getHeight() - lp.bottomMargin);
|
||||
} else {
|
||||
mHandleBar.setTranslationY(y + mHandleBar.getHeight() + lp.topMargin);
|
||||
}
|
||||
}
|
||||
|
||||
if (mNotificationView.getHeight() > 0) {
|
||||
Drawable background = mNotificationView.getBackground().mutate();
|
||||
background.setAlpha((int) (getBackgroundAlpha(height) * 255));
|
||||
background.setAlpha((int) (getBackgroundAlpha(y) * 255));
|
||||
mNotificationView.setBackground(background);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldAllowClosingScroll() {
|
||||
// Unless the notification list is at the bottom, the panel shouldn't be allowed to
|
||||
// Unless the notification list is at the end, the panel shouldn't be allowed to
|
||||
// collapse on scroll.
|
||||
return mNotificationListAtBottomAtTimeOfTouch;
|
||||
return mNotificationListAtEndAtTimeOfTouch;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -403,9 +409,11 @@ public class NotificationPanelViewController extends OverlayPanelViewController
|
||||
* shade is visible to the user. When the notification shade is completely open then
|
||||
* alpha value will be 1.
|
||||
*/
|
||||
private float getBackgroundAlpha(int height) {
|
||||
return mInitialBackgroundAlpha
|
||||
+ ((float) height / mNotificationView.getHeight() * mBackgroundAlphaDiff);
|
||||
private float getBackgroundAlpha(int y) {
|
||||
float fractionCovered =
|
||||
((float) (mAnimateDirection > 0 ? y : mNotificationView.getHeight() - y))
|
||||
/ mNotificationView.getHeight();
|
||||
return mInitialBackgroundAlpha + fractionCovered * mBackgroundAlphaDiff;
|
||||
}
|
||||
|
||||
/** Sets the unseen count listener. */
|
||||
@@ -431,13 +439,18 @@ public class NotificationPanelViewController extends OverlayPanelViewController
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
|
||||
float distanceY) {
|
||||
calculatePercentageFromBottom(event2.getRawY());
|
||||
// To prevent the jump in the clip bounds while closing the notification shade using
|
||||
calculatePercentageFromEndingEdge(event2.getRawY());
|
||||
// To prevent the jump in the clip bounds while closing the notification panel using
|
||||
// the handle bar we should calculate the height using the diff of event1 and event2.
|
||||
// This will help the notification shade to clip smoothly as the event2 value changes
|
||||
// as event1 value will be fixed.
|
||||
int clipHeight = getLayout().getHeight() - (int) (event1.getRawY() - event2.getRawY());
|
||||
setViewClipBounds(clipHeight);
|
||||
float diff = mAnimateDirection * (event1.getRawY() - event2.getRawY());
|
||||
float y = mAnimateDirection > 0
|
||||
? getLayout().getHeight() - diff
|
||||
: diff;
|
||||
// Ensure the position is within the overlay panel.
|
||||
y = Math.max(0, Math.min(y, getLayout().getHeight()));
|
||||
setViewClipBounds((int) y);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,17 +19,15 @@ package com.android.systemui.car.notification;
|
||||
import android.car.hardware.power.CarPowerManager;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
|
||||
import com.android.systemui.car.CarDeviceProvisionedController;
|
||||
import com.android.systemui.car.navigationbar.CarNavigationBarController;
|
||||
import com.android.systemui.car.window.OverlayViewMediator;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/** The view mediator which attaches the view controller to other elements of the system ui. */
|
||||
@Singleton
|
||||
public class NotificationPanelViewMediator implements OverlayViewMediator,
|
||||
public abstract class NotificationPanelViewMediator implements OverlayViewMediator,
|
||||
ConfigurationController.ConfigurationListener {
|
||||
|
||||
private final CarNavigationBarController mCarNavigationBarController;
|
||||
@@ -38,7 +36,6 @@ public class NotificationPanelViewMediator implements OverlayViewMediator,
|
||||
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
|
||||
private final ConfigurationController mConfigurationController;
|
||||
|
||||
@Inject
|
||||
public NotificationPanelViewMediator(
|
||||
CarNavigationBarController carNavigationBarController,
|
||||
NotificationPanelViewController notificationPanelViewController,
|
||||
@@ -56,9 +53,10 @@ public class NotificationPanelViewMediator implements OverlayViewMediator,
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void registerListeners() {
|
||||
mCarNavigationBarController.registerTopBarTouchListener(
|
||||
mNotificationPanelViewController.getDragOpenTouchListener());
|
||||
mNotificationPanelViewController.getDragCloseTouchListener());
|
||||
mCarNavigationBarController.registerBottomBarTouchListener(
|
||||
mNotificationPanelViewController.getDragCloseTouchListener());
|
||||
mCarNavigationBarController.registerLeftBarTouchListener(
|
||||
@@ -128,4 +126,12 @@ public class NotificationPanelViewMediator implements OverlayViewMediator,
|
||||
mNotificationPanelViewController.reinflate();
|
||||
registerListeners();
|
||||
}
|
||||
|
||||
protected final CarNavigationBarController getCarNavigationBarController() {
|
||||
return mCarNavigationBarController;
|
||||
}
|
||||
|
||||
protected final NotificationPanelViewController getNotificationPanelViewController() {
|
||||
return mNotificationPanelViewController;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.car.notification;
|
||||
|
||||
import com.android.systemui.car.CarDeviceProvisionedController;
|
||||
import com.android.systemui.car.navigationbar.CarNavigationBarController;
|
||||
import com.android.systemui.car.window.OverlayPanelViewController;
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Implementation of NotificationPanelViewMediator that sets the notification panel to be opened
|
||||
* from the top navigation bar.
|
||||
*/
|
||||
@Singleton
|
||||
public class TopNotificationPanelViewMediator extends NotificationPanelViewMediator {
|
||||
|
||||
@Inject
|
||||
public TopNotificationPanelViewMediator(
|
||||
CarNavigationBarController carNavigationBarController,
|
||||
NotificationPanelViewController notificationPanelViewController,
|
||||
|
||||
PowerManagerHelper powerManagerHelper,
|
||||
|
||||
CarDeviceProvisionedController carDeviceProvisionedController,
|
||||
ConfigurationController configurationController
|
||||
) {
|
||||
super(carNavigationBarController,
|
||||
notificationPanelViewController,
|
||||
powerManagerHelper,
|
||||
carDeviceProvisionedController,
|
||||
configurationController);
|
||||
notificationPanelViewController.setOverlayDirection(
|
||||
OverlayPanelViewController.OVERLAY_FROM_TOP_BAR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListeners() {
|
||||
super.registerListeners();
|
||||
getCarNavigationBarController().registerBottomBarTouchListener(
|
||||
getNotificationPanelViewController().getDragOpenTouchListener());
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package com.android.systemui.car.window;
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.IntDef;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
@@ -35,13 +36,36 @@ import com.android.systemui.car.CarDeviceProvisionedController;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.statusbar.FlingAnimationUtils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* The {@link OverlayPanelViewController} provides additional dragging animation capabilities to
|
||||
* {@link OverlayViewController}.
|
||||
*/
|
||||
public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
|
||||
private static final boolean DEBUG = true;
|
||||
/** @hide */
|
||||
@IntDef(flag = true, prefix = { "OVERLAY_" }, value = {
|
||||
OVERLAY_FROM_TOP_BAR,
|
||||
OVERLAY_FROM_BOTTOM_BAR
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface OverlayDirection {}
|
||||
|
||||
/**
|
||||
* Indicates that the overlay panel should be opened from the top bar and expanded by dragging
|
||||
* towards the bottom bar.
|
||||
*/
|
||||
public static final int OVERLAY_FROM_TOP_BAR = 0;
|
||||
|
||||
/**
|
||||
* Indicates that the overlay panel should be opened from the bottom bar and expanded by
|
||||
* dragging towards the top bar.
|
||||
*/
|
||||
public static final int OVERLAY_FROM_BOTTOM_BAR = 1;
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "OverlayPanelViewController";
|
||||
|
||||
// used to calculate how fast to open or close the window
|
||||
@@ -54,14 +78,18 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
protected static final int SWIPE_DOWN_MIN_DISTANCE = 25;
|
||||
protected static final int SWIPE_MAX_OFF_PATH = 75;
|
||||
protected static final int SWIPE_THRESHOLD_VELOCITY = 200;
|
||||
private static final int POSITIVE_DIRECTION = 1;
|
||||
private static final int NEGATIVE_DIRECTION = -1;
|
||||
|
||||
private final FlingAnimationUtils mFlingAnimationUtils;
|
||||
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
|
||||
private final View.OnTouchListener mDragOpenTouchListener;
|
||||
private final View.OnTouchListener mDragCloseTouchListener;
|
||||
|
||||
protected int mAnimateDirection = POSITIVE_DIRECTION;
|
||||
|
||||
private final int mSettleClosePercentage;
|
||||
private int mPercentageFromBottom;
|
||||
private int mPercentageFromEndingEdge;
|
||||
|
||||
private boolean mPanelVisible;
|
||||
private boolean mPanelExpanded;
|
||||
@@ -91,8 +119,7 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
mSettleClosePercentage = resources.getInteger(
|
||||
R.integer.notification_settle_close_percentage);
|
||||
|
||||
// Attached to the top navigation bar (i.e. status bar) to detect pull down of the
|
||||
// notification shade.
|
||||
// Attached to a navigation bar to open the overlay panel
|
||||
GestureDetector openGestureDetector = new GestureDetector(context,
|
||||
new OpenGestureListener() {
|
||||
@Override
|
||||
@@ -101,8 +128,8 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
}
|
||||
});
|
||||
|
||||
// Attached to the NavBars to close the notification shade
|
||||
GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(context,
|
||||
// Attached to the other navigation bars to close the overlay panel
|
||||
GestureDetector closeGestureDetector = new GestureDetector(context,
|
||||
new SystemBarCloseGestureListener() {
|
||||
@Override
|
||||
protected void close() {
|
||||
@@ -132,7 +159,7 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
if (!isInflated()) {
|
||||
return true;
|
||||
}
|
||||
boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
|
||||
boolean consumed = closeGestureDetector.onTouchEvent(event);
|
||||
if (consumed) {
|
||||
return true;
|
||||
}
|
||||
@@ -141,6 +168,17 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
};
|
||||
}
|
||||
|
||||
/** Sets the overlay panel animation direction along the x or y axis. */
|
||||
public void setOverlayDirection(@OverlayDirection int direction) {
|
||||
if (direction == OVERLAY_FROM_TOP_BAR) {
|
||||
mAnimateDirection = POSITIVE_DIRECTION;
|
||||
} else if (direction == OVERLAY_FROM_BOTTOM_BAR) {
|
||||
mAnimateDirection = NEGATIVE_DIRECTION;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Direction not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/** Toggles the visibility of the panel. */
|
||||
public void toggle() {
|
||||
if (!isInflated()) {
|
||||
@@ -207,7 +245,7 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
protected void maybeCompleteAnimation(MotionEvent event) {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_UP
|
||||
&& isPanelVisible()) {
|
||||
if (mSettleClosePercentage < mPercentageFromBottom) {
|
||||
if (mSettleClosePercentage < mPercentageFromEndingEdge) {
|
||||
animatePanel(DEFAULT_FLING_VELOCITY, false);
|
||||
} else {
|
||||
animatePanel(DEFAULT_FLING_VELOCITY, true);
|
||||
@@ -221,16 +259,15 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
* panel this method also makes the view invisible after animation ends.
|
||||
*/
|
||||
protected void animatePanel(float velocity, boolean isClosing) {
|
||||
float to = 0;
|
||||
if (!isClosing) {
|
||||
to = getLayout().getHeight();
|
||||
}
|
||||
float to = getEndPosition(isClosing);
|
||||
|
||||
Rect rect = getLayout().getClipBounds();
|
||||
if (rect != null && rect.bottom != to) {
|
||||
float from = rect.bottom;
|
||||
animate(from, to, velocity, isClosing);
|
||||
return;
|
||||
if (rect != null) {
|
||||
float from = getCurrentStartPosition(rect);
|
||||
if (from != to) {
|
||||
animate(from, to, velocity, isClosing);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We will only be here if the shade is being opened programmatically or via button when
|
||||
@@ -242,12 +279,32 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
public void onGlobalLayout() {
|
||||
ViewTreeObserver obs = getLayout().getViewTreeObserver();
|
||||
obs.removeOnGlobalLayoutListener(this);
|
||||
float to = getLayout().getHeight();
|
||||
animate(/* from= */ 0, to, velocity, isClosing);
|
||||
animate(
|
||||
getDefaultStartPosition(),
|
||||
getEndPosition(/* isClosing= */ false),
|
||||
velocity,
|
||||
isClosing
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Returns the start position if the user has not started swiping. */
|
||||
private int getDefaultStartPosition() {
|
||||
return mAnimateDirection > 0 ? 0 : getLayout().getHeight();
|
||||
}
|
||||
|
||||
/** Returns the start position if we are in the middle of swiping. */
|
||||
private int getCurrentStartPosition(Rect clipBounds) {
|
||||
return mAnimateDirection > 0 ? clipBounds.bottom : clipBounds.top;
|
||||
}
|
||||
|
||||
private int getEndPosition(boolean isClosing) {
|
||||
return (mAnimateDirection > 0 && !isClosing) || (mAnimateDirection == -1 && isClosing)
|
||||
? getLayout().getHeight()
|
||||
: 0;
|
||||
}
|
||||
|
||||
private void animate(float from, float to, float velocity, boolean isClosing) {
|
||||
if (mIsAnimating) {
|
||||
return;
|
||||
@@ -356,25 +413,44 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
* Misc
|
||||
* ***************************************************************************************** */
|
||||
|
||||
protected void calculatePercentageFromBottom(float height) {
|
||||
/**
|
||||
* Given the position of the pointer dragging the panel, return the percentage of its closeness
|
||||
* to the ending edge.
|
||||
*/
|
||||
protected void calculatePercentageFromEndingEdge(float y) {
|
||||
if (getLayout().getHeight() > 0) {
|
||||
mPercentageFromBottom = (int) Math.abs(
|
||||
height / getLayout().getHeight() * 100);
|
||||
float height = getVisiblePanelHeight(y);
|
||||
mPercentageFromEndingEdge = (int) Math.abs(height / getLayout().getHeight() * 100);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setViewClipBounds(int height) {
|
||||
if (height > getLayout().getHeight()) {
|
||||
height = getLayout().getHeight();
|
||||
}
|
||||
private float getVisiblePanelHeight(float y) {
|
||||
return mAnimateDirection > 0 ? y : getLayout().getHeight() - y;
|
||||
}
|
||||
|
||||
/** Sets the boundaries of the overlay panel that can be seen based on pointer position. */
|
||||
protected void setViewClipBounds(int y) {
|
||||
// Bound the pointer position to be within the overlay panel.
|
||||
y = Math.max(0, Math.min(y, getLayout().getHeight()));
|
||||
Rect clipBounds = new Rect();
|
||||
clipBounds.set(0, 0, getLayout().getWidth(), height);
|
||||
int top, bottom;
|
||||
if (mAnimateDirection > 0) {
|
||||
top = 0;
|
||||
bottom = y;
|
||||
} else {
|
||||
top = y;
|
||||
bottom = getLayout().getHeight();
|
||||
}
|
||||
clipBounds.set(0, top, getLayout().getWidth(), bottom);
|
||||
getLayout().setClipBounds(clipBounds);
|
||||
onScroll(height);
|
||||
onScroll(y);
|
||||
}
|
||||
|
||||
/** Called while scrolling. */
|
||||
protected abstract void onScroll(int height);
|
||||
/**
|
||||
* Called while scrolling, this passes the position of the clip boundary that is currently
|
||||
* changing.
|
||||
*/
|
||||
protected abstract void onScroll(int y);
|
||||
|
||||
/* ***************************************************************************************** *
|
||||
* Getters
|
||||
@@ -406,8 +482,8 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
}
|
||||
|
||||
/** Returns the percentage of the panel that is open from the bottom. */
|
||||
protected final int getPercentageFromBottom() {
|
||||
return mPercentageFromBottom;
|
||||
protected final int getPercentageFromEndingEdge() {
|
||||
return mPercentageFromEndingEdge;
|
||||
}
|
||||
|
||||
/** Returns the percentage at which we've determined whether to open or close the panel. */
|
||||
@@ -443,7 +519,7 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
|
||||
// Initially the scroll starts with height being zero. This checks protects from divide
|
||||
// by zero error.
|
||||
calculatePercentageFromBottom(event2.getRawY());
|
||||
calculatePercentageFromEndingEdge(event2.getRawY());
|
||||
|
||||
mIsTracking = true;
|
||||
return true;
|
||||
@@ -453,7 +529,7 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
@Override
|
||||
public boolean onFling(MotionEvent event1, MotionEvent event2,
|
||||
float velocityX, float velocityY) {
|
||||
if (velocityY > SWIPE_THRESHOLD_VELOCITY) {
|
||||
if (mAnimateDirection * velocityY > SWIPE_THRESHOLD_VELOCITY) {
|
||||
mOpeningVelocity = velocityY;
|
||||
open();
|
||||
return true;
|
||||
@@ -483,19 +559,14 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
|
||||
float distanceY) {
|
||||
// should not clip while scroll to the bottom of the list.
|
||||
if (!shouldAllowClosingScroll()) {
|
||||
return false;
|
||||
}
|
||||
float actualNotificationHeight =
|
||||
getLayout().getHeight() - (event1.getRawY() - event2.getRawY());
|
||||
if (actualNotificationHeight > getLayout().getHeight()) {
|
||||
actualNotificationHeight = getLayout().getHeight();
|
||||
}
|
||||
float y = getYPositionOfPanelEndingEdge(event1, event2);
|
||||
if (getLayout().getHeight() > 0) {
|
||||
mPercentageFromBottom = (int) Math.abs(
|
||||
actualNotificationHeight / getLayout().getHeight() * 100);
|
||||
boolean isUp = distanceY > 0;
|
||||
mPercentageFromEndingEdge = (int) Math.abs(
|
||||
y / getLayout().getHeight() * 100);
|
||||
boolean isInClosingDirection = mAnimateDirection * distanceY > 0;
|
||||
|
||||
// This check is to figure out if onScroll was called while swiping the card at
|
||||
// bottom of the list. At that time we should not allow notification shade to
|
||||
@@ -503,23 +574,37 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
// possible if a user is closing the notification shade and while swiping starts
|
||||
// to open again but does not fling. At that time we should allow the
|
||||
// notification shade to close fully or else it would stuck in between.
|
||||
if (Math.abs(getLayout().getHeight() - actualNotificationHeight)
|
||||
> SWIPE_DOWN_MIN_DISTANCE && isUp) {
|
||||
setViewClipBounds((int) actualNotificationHeight);
|
||||
if (Math.abs(getLayout().getHeight() - y)
|
||||
> SWIPE_DOWN_MIN_DISTANCE && isInClosingDirection) {
|
||||
setViewClipBounds((int) y);
|
||||
mIsTracking = true;
|
||||
} else if (!isUp) {
|
||||
setViewClipBounds((int) actualNotificationHeight);
|
||||
} else if (!isInClosingDirection) {
|
||||
setViewClipBounds((int) y);
|
||||
}
|
||||
}
|
||||
// if we return true the items in RV won't be scrollable.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* To prevent the jump in the clip bounds while closing the panel we should calculate the y
|
||||
* position using the diff of event1 and event2. This will help the panel clip smoothly as
|
||||
* the event2 value changes while event1 value will be fixed.
|
||||
* @param event1 MotionEvent that contains the position of where the event2 started.
|
||||
* @param event2 MotionEvent that contains the position of where the user has scrolled to
|
||||
* on the screen.
|
||||
*/
|
||||
private float getYPositionOfPanelEndingEdge(MotionEvent event1, MotionEvent event2) {
|
||||
float diff = mAnimateDirection * (event1.getRawY() - event2.getRawY());
|
||||
float y = mAnimateDirection > 0 ? getLayout().getHeight() - diff : diff;
|
||||
y = Math.max(0, Math.min(y, getLayout().getHeight()));
|
||||
return y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent event1, MotionEvent event2,
|
||||
float velocityX, float velocityY) {
|
||||
// should not fling if the touch does not start when view is at the bottom of the list.
|
||||
// should not fling if the touch does not start when view is at the end of the list.
|
||||
if (!shouldAllowClosingScroll()) {
|
||||
return false;
|
||||
}
|
||||
@@ -528,8 +613,8 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
// swipe was not vertical or was not fast enough
|
||||
return false;
|
||||
}
|
||||
boolean isUp = velocityY < 0;
|
||||
if (isUp) {
|
||||
boolean isInClosingDirection = mAnimateDirection * velocityY < 0;
|
||||
if (isInClosingDirection) {
|
||||
close();
|
||||
return true;
|
||||
} else {
|
||||
@@ -555,7 +640,7 @@ public abstract class OverlayPanelViewController extends OverlayViewController {
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
|
||||
float distanceY) {
|
||||
calculatePercentageFromBottom(event2.getRawY());
|
||||
calculatePercentageFromEndingEdge(event2.getRawY());
|
||||
setViewClipBounds((int) event2.getRawY());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
package com.android.systemui.car.window;
|
||||
|
||||
import com.android.systemui.car.keyguard.CarKeyguardViewMediator;
|
||||
import com.android.systemui.car.notification.NotificationPanelViewMediator;
|
||||
import com.android.systemui.car.notification.BottomNotificationPanelViewMediator;
|
||||
import com.android.systemui.car.notification.TopNotificationPanelViewMediator;
|
||||
import com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator;
|
||||
|
||||
import dagger.Binds;
|
||||
@@ -31,12 +32,19 @@ import dagger.multibindings.IntoMap;
|
||||
@Module
|
||||
public abstract class OverlayWindowModule {
|
||||
|
||||
/** Injects NotificationPanelViewMediator. */
|
||||
/** Injects TopNotificationPanelViewMediator. */
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(NotificationPanelViewMediator.class)
|
||||
public abstract OverlayViewMediator bindNotificationPanelViewMediator(
|
||||
NotificationPanelViewMediator notificationPanelViewMediator);
|
||||
@ClassKey(TopNotificationPanelViewMediator.class)
|
||||
public abstract OverlayViewMediator bindTopNotificationPanelViewMediator(
|
||||
TopNotificationPanelViewMediator topNotificationPanelViewMediator);
|
||||
|
||||
/** Injects BottomNotificationPanelViewMediator. */
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(BottomNotificationPanelViewMediator.class)
|
||||
public abstract OverlayViewMediator bindBottomNotificationPanelViewMediator(
|
||||
BottomNotificationPanelViewMediator bottomNotificationPanelViewMediator);
|
||||
|
||||
/** Inject into CarKeyguardViewMediator. */
|
||||
@Binds
|
||||
|
||||
@@ -18,13 +18,16 @@ package com.android.systemui.car.window;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyFloat;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -42,6 +45,7 @@ import com.android.systemui.tests.R;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@@ -172,6 +176,51 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
|
||||
assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateCollapsePanel_withOverlayFromTopBar_collapsesTowardsTopBar() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
// Mock a panel that has layout size 50 and where the panel is opened.
|
||||
int size = 50;
|
||||
mockPanelWithSize(size);
|
||||
mOverlayPanelViewController.getLayout().setClipBounds(
|
||||
new Rect(0, 0, size, size));
|
||||
mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
|
||||
mOverlayPanelViewController.setPanelExpanded(true);
|
||||
mOverlayPanelViewController.setPanelVisible(true);
|
||||
mOverlayPanelViewController.setOverlayDirection(
|
||||
OverlayPanelViewController.OVERLAY_FROM_TOP_BAR);
|
||||
|
||||
mOverlayPanelViewController.animateCollapsePanel();
|
||||
|
||||
ArgumentCaptor<Float> endValueCaptor = ArgumentCaptor.forClass(Float.class);
|
||||
verify(mFlingAnimationUtils).apply(
|
||||
any(Animator.class), anyFloat(), endValueCaptor.capture(), anyFloat());
|
||||
assertThat(endValueCaptor.getValue().intValue()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateCollapsePanel_withOverlayFromBottomBar_collapsesTowardsBottomBar() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
// Mock a panel that has layout size 50 and where the panel is opened.
|
||||
int size = 50;
|
||||
mockPanelWithSize(size);
|
||||
mOverlayPanelViewController.getLayout().setClipBounds(
|
||||
new Rect(0, 0, size, size));
|
||||
mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
|
||||
mOverlayPanelViewController.setPanelExpanded(true);
|
||||
mOverlayPanelViewController.setPanelVisible(true);
|
||||
mOverlayPanelViewController.setOverlayDirection(
|
||||
OverlayPanelViewController.OVERLAY_FROM_BOTTOM_BAR);
|
||||
|
||||
mOverlayPanelViewController.animateCollapsePanel();
|
||||
|
||||
ArgumentCaptor<Float> endValueCaptor = ArgumentCaptor.forClass(Float.class);
|
||||
verify(mFlingAnimationUtils).apply(
|
||||
any(Animator.class), anyFloat(), endValueCaptor.capture(), anyFloat());
|
||||
assertThat(endValueCaptor.getValue().intValue()).isEqualTo(
|
||||
mOverlayPanelViewController.getLayout().getHeight());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateCollapsePanel_removesWindowFocus() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
@@ -218,6 +267,49 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
|
||||
assertThat(mOverlayPanelViewController.mOnAnimateExpandPanelCalled).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateExpandPanel_withOverlayFromTopBar_expandsToBottom() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
// Mock a panel that has layout size 50 and where the panel is not opened.
|
||||
int size = 50;
|
||||
mockPanelWithSize(size);
|
||||
mOverlayPanelViewController.getLayout().setClipBounds(
|
||||
new Rect(0, 0, size, 0));
|
||||
mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
|
||||
when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
|
||||
mOverlayPanelViewController.setOverlayDirection(
|
||||
OverlayPanelViewController.OVERLAY_FROM_TOP_BAR);
|
||||
|
||||
mOverlayPanelViewController.animateExpandPanel();
|
||||
|
||||
ArgumentCaptor<Float> endValueCaptor = ArgumentCaptor.forClass(Float.class);
|
||||
verify(mFlingAnimationUtils).apply(
|
||||
any(Animator.class), anyFloat(), endValueCaptor.capture(), anyFloat());
|
||||
assertThat(endValueCaptor.getValue().intValue()).isEqualTo(
|
||||
mOverlayPanelViewController.getLayout().getHeight());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateExpandPanel_withOverlayFromBottomBar_expandsToTop() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
// Mock a panel that has layout size 50 and where the panel is not opened.
|
||||
int size = 50;
|
||||
mockPanelWithSize(size);
|
||||
mOverlayPanelViewController.getLayout().setClipBounds(
|
||||
new Rect(0, size, size, size));
|
||||
mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
|
||||
when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
|
||||
mOverlayPanelViewController.setOverlayDirection(
|
||||
OverlayPanelViewController.OVERLAY_FROM_BOTTOM_BAR);
|
||||
|
||||
mOverlayPanelViewController.animateExpandPanel();
|
||||
|
||||
ArgumentCaptor<Float> endValueCaptor = ArgumentCaptor.forClass(Float.class);
|
||||
verify(mFlingAnimationUtils).apply(
|
||||
any(Animator.class), anyFloat(), endValueCaptor.capture(), anyFloat());
|
||||
assertThat(endValueCaptor.getValue().intValue()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void animateExpandPanel_setsPanelVisible() {
|
||||
mOverlayPanelViewController.inflate(mBaseLayout);
|
||||
@@ -330,6 +422,10 @@ public class OverlayPanelViewControllerTest extends SysuiTestCase {
|
||||
verify(mOverlayViewGlobalStateController).inflateView(mOverlayPanelViewController);
|
||||
}
|
||||
|
||||
private void mockPanelWithSize(int size) {
|
||||
mOverlayPanelViewController.getLayout().setLeftTopRightBottom(0, 0, size, size);
|
||||
}
|
||||
|
||||
private static class TestOverlayPanelViewController extends OverlayPanelViewController {
|
||||
|
||||
private boolean mShouldAnimateCollapsePanel;
|
||||
|
||||
Reference in New Issue
Block a user