Merge "Separate Notification Panel" into rvc-dev

This commit is contained in:
TreeHugger Robot
2020-03-31 17:03:48 +00:00
committed by Android (Google) Code Review
17 changed files with 1048 additions and 852 deletions

View File

@@ -20,6 +20,8 @@
android:id="@+id/notification_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"
android:layout_marginBottom="@dimen/navigation_bar_height"
android:background="@color/notification_shade_background_color">
<View

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/notification_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@@ -22,6 +22,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewStub android:id="@+id/notification_panel_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/notification_panel_container"/>
<ViewStub android:id="@+id/fullscreen_user_switcher_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@@ -68,6 +68,7 @@
<!-- Car System UI's OverlayViewsMediator-->
<string-array name="config_carSystemUIOverlayViewsMediators" translatable="false">
<item>com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator</item>
<item>com.android.systemui.car.notification.NotificationPanelViewMediator</item>
</string-array>
<!-- SystemUI Services: The classes of the stuff to start. -->

View File

@@ -38,7 +38,6 @@ import com.android.systemui.stackdivider.DividerModule;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
import com.android.systemui.statusbar.car.CarShadeControllerImpl;
import com.android.systemui.statusbar.car.CarStatusBar;
import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -47,6 +46,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.ShadeControllerImpl;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -120,7 +120,7 @@ abstract class CarSystemUIModule {
KeyguardEnvironmentImpl keyguardEnvironment);
@Binds
abstract ShadeController provideShadeController(CarShadeControllerImpl shadeController);
abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
@Provides
@Singleton

View File

@@ -29,9 +29,16 @@ public interface CarDeviceProvisionedController extends DeviceProvisionedControl
boolean isUserSetupInProgress(int user);
/**
* Returns {@code true} then SUW is in progress for the current user.
* Returns {@code true} when SUW is in progress for the current user.
*/
default boolean isCurrentUserSetupInProgress() {
return isUserSetupInProgress(getCurrentUser());
}
/**
* Returns {@code true} when the user is setup and not currently in SUW.
*/
default boolean isCurrentUserFullySetup() {
return isCurrentUserSetup() && !isCurrentUserSetupInProgress();
}
}

View File

@@ -0,0 +1,778 @@
/*
* 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 android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.car.Car;
import android.car.drivingstate.CarUxRestrictionsManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.util.Log;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.android.car.notification.CarNotificationListener;
import com.android.car.notification.CarNotificationView;
import com.android.car.notification.CarUxRestrictionManagerWrapper;
import com.android.car.notification.NotificationClickHandlerFactory;
import com.android.car.notification.NotificationDataManager;
import com.android.car.notification.NotificationViewController;
import com.android.car.notification.PreprocessingManager;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.window.OverlayViewController;
import com.android.systemui.window.OverlayViewGlobalStateController;
import javax.inject.Inject;
import javax.inject.Singleton;
/** View controller for the notification panel. */
@Singleton
public class NotificationPanelViewController extends OverlayViewController {
// used to calculate how fast to open or close the window
private static final float DEFAULT_FLING_VELOCITY = 0;
// max time a fling animation takes
private static final float FLING_ANIMATION_MAX_TIME = 0.5f;
// acceleration rate for the fling animation
private static final float FLING_SPEED_UP_FACTOR = 0.6f;
private static final int SWIPE_DOWN_MIN_DISTANCE = 25;
private static final int SWIPE_MAX_OFF_PATH = 75;
private static final int SWIPE_THRESHOLD_VELOCITY = 200;
private static final boolean DEBUG = true;
private static final String TAG = "NotificationPanelViewController";
private final Context mContext;
private final Resources mResources;
private final CarServiceProvider mCarServiceProvider;
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
private final IStatusBarService mBarService;
private final CommandQueue mCommandQueue;
private final NotificationDataManager mNotificationDataManager;
private final CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
private final CarNotificationListener mCarNotificationListener;
private final NotificationClickHandlerFactory mNotificationClickHandlerFactory;
private final FlingAnimationUtils mFlingAnimationUtils;
private final StatusBarStateController mStatusBarStateController;
private final int mSettleClosePercentage;
private float mOpeningVelocity = DEFAULT_FLING_VELOCITY;
private float mClosingVelocity = DEFAULT_FLING_VELOCITY;
private float mInitialBackgroundAlpha;
private float mBackgroundAlphaDiff;
private CarNotificationView mNotificationView;
private View mHandleBar;
private RecyclerView mNotificationList;
private NotificationViewController mNotificationViewController;
private boolean mIsTracking;
private boolean mNotificationListAtBottom;
private float mFirstTouchDownOnGlassPane;
private boolean mNotificationListAtBottomAtTimeOfTouch;
private boolean mIsSwipingVerticallyToClose;
private int mPercentageFromBottom;
private boolean mIsNotificationAnimating;
private boolean mIsNotificationCardSwiping;
private boolean mPanelExpanded = false;
private View.OnTouchListener mTopNavBarNotificationTouchListener;
private View.OnTouchListener mNavBarNotificationTouchListener;
private OnUnseenCountUpdateListener mUnseenCountUpdateListener;
@Inject
public NotificationPanelViewController(
Context context,
@Main Resources resources,
OverlayViewGlobalStateController overlayViewGlobalStateController,
/* Other things */
CarServiceProvider carServiceProvider,
CarDeviceProvisionedController carDeviceProvisionedController,
/* Things needed for notifications */
IStatusBarService barService,
CommandQueue commandQueue,
NotificationDataManager notificationDataManager,
CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
CarNotificationListener carNotificationListener,
NotificationClickHandlerFactory notificationClickHandlerFactory,
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
/* Things that need to be replaced */
StatusBarStateController statusBarStateController
) {
super(R.id.notification_panel_stub, overlayViewGlobalStateController);
mContext = context;
mResources = resources;
mCarServiceProvider = carServiceProvider;
mCarDeviceProvisionedController = carDeviceProvisionedController;
mBarService = barService;
mCommandQueue = commandQueue;
mNotificationDataManager = notificationDataManager;
mCarUxRestrictionManagerWrapper = carUxRestrictionManagerWrapper;
mCarNotificationListener = carNotificationListener;
mNotificationClickHandlerFactory = notificationClickHandlerFactory;
mFlingAnimationUtils = flingAnimationUtilsBuilder
.setMaxLengthSeconds(FLING_ANIMATION_MAX_TIME)
.setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
.build();
mStatusBarStateController = statusBarStateController;
// Notification background setup.
mInitialBackgroundAlpha = (float) mResources.getInteger(
R.integer.config_initialNotificationBackgroundAlpha) / 100;
if (mInitialBackgroundAlpha < 0 || mInitialBackgroundAlpha > 100) {
throw new RuntimeException(
"Unable to setup notification bar due to incorrect initial background alpha"
+ " percentage");
}
float finalBackgroundAlpha = Math.max(
mInitialBackgroundAlpha,
(float) mResources.getInteger(
R.integer.config_finalNotificationBackgroundAlpha) / 100);
if (finalBackgroundAlpha < 0 || finalBackgroundAlpha > 100) {
throw new RuntimeException(
"Unable to setup notification bar due to incorrect final background alpha"
+ " percentage");
}
mBackgroundAlphaDiff = finalBackgroundAlpha - mInitialBackgroundAlpha;
// Notification Panel param setup
mSettleClosePercentage = mResources.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.
GestureDetector openGestureDetector = new GestureDetector(mContext,
new OpenNotificationGestureListener() {
@Override
protected void openNotification() {
animateExpandNotificationsPanel();
}
});
// Attached to the NavBars to close the notification shade
GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(mContext,
new NavBarCloseNotificationGestureListener() {
@Override
protected void close() {
if (mPanelExpanded) {
animateCollapsePanels();
}
}
});
mTopNavBarNotificationTouchListener = (v, event) -> {
if (!isInflated()) {
getOverlayViewGlobalStateController().inflateView(this);
}
if (!mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
return true;
}
boolean consumed = openGestureDetector.onTouchEvent(event);
if (consumed) {
return true;
}
maybeCompleteAnimation(event);
return true;
};
mNavBarNotificationTouchListener =
(v, event) -> {
boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
if (consumed) {
return true;
}
maybeCompleteAnimation(event);
return true;
};
}
@Override
protected void onFinishInflate() {
reinflate();
}
/** Reinflates the view. */
public void reinflate() {
ViewGroup container = (ViewGroup) getLayout();
container.removeView(mNotificationView);
mNotificationView = (CarNotificationView) LayoutInflater.from(mContext).inflate(
R.layout.notification_center_activity, container,
/* attachToRoot= */ false);
container.addView(mNotificationView);
onNotificationViewInflated();
}
private void onNotificationViewInflated() {
// Find views.
mNotificationView = getLayout().findViewById(R.id.notification_view);
View glassPane = mNotificationView.findViewById(R.id.glass_pane);
mHandleBar = mNotificationView.findViewById(R.id.handle_bar);
mNotificationList = mNotificationView.findViewById(R.id.notifications);
mNotificationClickHandlerFactory.registerClickListener((launchResult, alertEntry) -> {
if (launchResult == ActivityManager.START_TASK_TO_FRONT
|| launchResult == ActivityManager.START_SUCCESS) {
animateCollapsePanels();
}
});
mNotificationDataManager.setOnUnseenCountUpdateListener(() -> {
if (mUnseenCountUpdateListener != null) {
mUnseenCountUpdateListener.onUnseenCountUpdate(
mNotificationDataManager.getUnseenNotificationCount());
}
});
mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory);
mNotificationView.setNotificationDataManager(mNotificationDataManager);
mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!mNotificationList.canScrollVertically(1)) {
mNotificationListAtBottom = true;
return;
}
mNotificationListAtBottom = false;
mIsSwipingVerticallyToClose = false;
mNotificationListAtBottomAtTimeOfTouch = false;
}
});
// Attached to the notification ui to detect close request of the notification shade.
GestureDetector closeGestureDetector = new GestureDetector(mContext,
new CloseNotificationGestureListener() {
@Override
protected void close() {
if (mPanelExpanded) {
animateCollapsePanels();
}
}
});
// Attached to the Handle bar to close the notification shade
GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
new HandleBarCloseNotificationGestureListener());
// The glass pane is used to view touch events before passed to the notification list.
// 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;
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mFirstTouchDownOnGlassPane = event.getRawX();
mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom;
// 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
// started.
closeGestureDetector.onTouchEvent(event);
}
return false;
});
mNotificationList.setOnTouchListener((v, event) -> {
mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX())
> SWIPE_MAX_OFF_PATH;
if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) {
// We need to save the state here as if notification card is swiping we will
// change the mNotificationListAtBottomAtTimeOfTouch. 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
// 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;
}
boolean handled = closeGestureDetector.onTouchEvent(event);
boolean isTracking = mIsTracking;
Rect rect = mNotificationView.getClipBounds();
float clippedHeight = 0;
if (rect != null) {
clippedHeight = rect.bottom;
}
if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
&& mIsSwipingVerticallyToClose) {
if (mSettleClosePercentage < mPercentageFromBottom && isTracking) {
animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
} else if (clippedHeight != mNotificationView.getHeight() && isTracking) {
// this can be caused when user is at the end of the list and trying to
// fling to top of the list by scrolling down.
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
}
}
// Updating the mNotificationListAtBottomAtTimeOfTouch 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;
}
return handled || isTracking;
});
mCarServiceProvider.addListener(car -> {
CarUxRestrictionsManager carUxRestrictionsManager =
(CarUxRestrictionsManager)
car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager(
carUxRestrictionsManager);
mNotificationViewController = new NotificationViewController(
mNotificationView,
PreprocessingManager.getInstance(mContext),
mCarNotificationListener,
mCarUxRestrictionManagerWrapper,
mNotificationDataManager);
mNotificationViewController.enable();
});
mHandleBar.setOnTouchListener((v, event) -> {
handleBarCloseNotificationGestureDetector.onTouchEvent(event);
maybeCompleteAnimation(event);
return true;
});
}
/** Called when the car power state is changed to ON. */
public void onCarPowerStateOn() {
if (mNotificationClickHandlerFactory != null) {
mNotificationClickHandlerFactory.clearAllNotifications();
}
mNotificationDataManager.clearAll();
}
View.OnTouchListener getTopNavBarNotificationTouchListener() {
return mTopNavBarNotificationTouchListener;
}
View.OnTouchListener getNavBarNotificationTouchListener() {
return mNavBarNotificationTouchListener;
}
private void maybeCompleteAnimation(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_UP
&& mNotificationView.getVisibility() == View.VISIBLE) {
if (mSettleClosePercentage < mPercentageFromBottom) {
animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
} else {
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
}
}
}
/**
* Animates the notification shade from one position to other. This is used to either open or
* close the notification shade completely with a velocity. If the animation is to close the
* notification shade this method also makes the view invisible after animation ends.
*/
private void animateNotificationPanel(float velocity, boolean isClosing) {
float to = 0;
if (!isClosing) {
to = mNotificationView.getHeight();
}
Rect rect = mNotificationView.getClipBounds();
if (rect != null && rect.bottom != to) {
float from = rect.bottom;
animate(from, to, velocity, isClosing);
return;
}
// We will only be here if the shade is being opened programmatically or via button when
// height of the layout was not calculated.
ViewTreeObserver notificationTreeObserver = mNotificationView.getViewTreeObserver();
notificationTreeObserver.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
ViewTreeObserver obs = mNotificationView.getViewTreeObserver();
obs.removeOnGlobalLayoutListener(this);
float to = mNotificationView.getHeight();
animate(/* from= */ 0, to, velocity, isClosing);
}
});
}
private void animateCollapsePanels() {
if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) {
return;
}
getOverlayViewGlobalStateController().setWindowFocusable(false);
animateNotificationPanel(mClosingVelocity, true);
}
private void animateExpandNotificationsPanel() {
if (!mCommandQueue.panelsEnabled()
|| !mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
return;
}
// scroll to top
mNotificationList.scrollToPosition(0);
setPanelVisible(true);
mNotificationView.setVisibility(View.VISIBLE);
animateNotificationPanel(mOpeningVelocity, false);
setPanelExpanded(true);
}
private void animate(float from, float to, float velocity, boolean isClosing) {
if (mIsNotificationAnimating) {
return;
}
mIsNotificationAnimating = true;
mIsTracking = true;
ValueAnimator animator = ValueAnimator.ofFloat(from, to);
animator.addUpdateListener(
animation -> {
float animatedValue = (Float) animation.getAnimatedValue();
setNotificationViewClipBounds((int) animatedValue);
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mIsNotificationAnimating = false;
mIsTracking = false;
mOpeningVelocity = DEFAULT_FLING_VELOCITY;
mClosingVelocity = DEFAULT_FLING_VELOCITY;
if (isClosing) {
setPanelVisible(false);
mNotificationView.setVisibility(View.INVISIBLE);
mNotificationView.setClipBounds(null);
mNotificationViewController.onVisibilityChanged(false);
// let the status bar know that the panel is closed
setPanelExpanded(false);
} else {
mNotificationViewController.onVisibilityChanged(true);
// let the status bar know that the panel is open
mNotificationView.setVisibleNotificationsAsSeen();
setPanelExpanded(true);
}
}
});
mFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity));
animator.start();
}
/**
* Set the panel view to be visible.
*/
public void setPanelVisible(boolean visible) {
if (visible && !getOverlayViewGlobalStateController().isWindowVisible()) {
getOverlayViewGlobalStateController().setWindowVisible(true);
}
if (!visible && getOverlayViewGlobalStateController().isWindowVisible()) {
getOverlayViewGlobalStateController().setWindowVisible(false);
}
getLayout().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
getOverlayViewGlobalStateController().setWindowFocusable(visible);
}
/**
* Set the panel state to expanded. This will expand or collapse the overlay window if
* necessary.
*/
public void setPanelExpanded(boolean expand) {
mPanelExpanded = expand;
if (expand && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
if (DEBUG) {
Log.v(TAG, "clearing notification effects from setExpandedHeight");
}
clearNotificationEffects();
}
}
/**
* Clear Buzz/Beep/Blink.
*/
private void clearNotificationEffects() {
try {
mBarService.clearNotificationEffects();
} catch (RemoteException e) {
// Won't fail unless the world has ended.
}
}
private void setNotificationViewClipBounds(int height) {
if (height > mNotificationView.getHeight()) {
height = mNotificationView.getHeight();
}
Rect clipBounds = new Rect();
clipBounds.set(0, 0, mNotificationView.getWidth(), height);
// Sets the clip region on the notification list view.
mNotificationView.setClipBounds(clipBounds);
if (mHandleBar != null) {
ViewGroup.MarginLayoutParams lp =
(ViewGroup.MarginLayoutParams) mHandleBar.getLayoutParams();
mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin);
}
if (mNotificationView.getHeight() > 0) {
Drawable background = mNotificationView.getBackground().mutate();
background.setAlpha((int) (getBackgroundAlpha(height) * 255));
mNotificationView.setBackground(background);
}
}
/**
* Calculates the alpha value for the background based on how much of the notification
* 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 void calculatePercentageFromBottom(float height) {
if (mNotificationView.getHeight() > 0) {
mPercentageFromBottom = (int) Math.abs(
height / mNotificationView.getHeight() * 100);
}
}
/** Toggles the visibility of the notification panel. */
public void toggle() {
if (!isInflated()) {
getOverlayViewGlobalStateController().inflateView(this);
}
if (mPanelExpanded) {
animateCollapsePanels();
} else {
animateExpandNotificationsPanel();
}
}
/** Sets the unseen count listener. */
public void setOnUnseenCountUpdateListener(OnUnseenCountUpdateListener listener) {
mUnseenCountUpdateListener = listener;
}
/** Listener that is updated when the number of unseen notifications changes. */
public interface OnUnseenCountUpdateListener {
/**
* This method is automatically called whenever there is an update to the number of unseen
* notifications. This method can be extended by OEMs to customize the desired logic.
*/
void onUnseenCountUpdate(int unseenNotificationCount);
}
/**
* Only responsible for open hooks. Since once the panel opens it covers all elements
* there is no need to merge with close.
*/
private abstract class OpenNotificationGestureListener extends
GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
float distanceY) {
if (mNotificationView.getVisibility() == View.INVISIBLE) {
// when the on-scroll is called for the first time to open.
mNotificationList.scrollToPosition(0);
}
setPanelVisible(true);
mNotificationView.setVisibility(View.VISIBLE);
// clips the view for the notification shade when the user scrolls to open.
setNotificationViewClipBounds((int) event2.getRawY());
// Initially the scroll starts with height being zero. This checks protects from divide
// by zero error.
calculatePercentageFromBottom(event2.getRawY());
mIsTracking = true;
return true;
}
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
if (velocityY > SWIPE_THRESHOLD_VELOCITY) {
mOpeningVelocity = velocityY;
openNotification();
return true;
}
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
return false;
}
protected abstract void openNotification();
}
/**
* To be installed on the open panel notification panel
*/
private abstract class CloseNotificationGestureListener extends
GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent motionEvent) {
if (mPanelExpanded) {
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
}
return true;
}
@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
float distanceY) {
// should not clip while scroll to the bottom of the list.
if (!mNotificationListAtBottomAtTimeOfTouch) {
return false;
}
float actualNotificationHeight =
mNotificationView.getHeight() - (event1.getRawY() - event2.getRawY());
if (actualNotificationHeight > mNotificationView.getHeight()) {
actualNotificationHeight = mNotificationView.getHeight();
}
if (mNotificationView.getHeight() > 0) {
mPercentageFromBottom = (int) Math.abs(
actualNotificationHeight / mNotificationView.getHeight() * 100);
boolean isUp = 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
// close. We are also checking for the upwards swipe gesture here because it is
// 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(mNotificationView.getHeight() - actualNotificationHeight)
> SWIPE_DOWN_MIN_DISTANCE && isUp) {
setNotificationViewClipBounds((int) actualNotificationHeight);
mIsTracking = true;
} else if (!isUp) {
setNotificationViewClipBounds((int) actualNotificationHeight);
}
}
// if we return true the items in RV won't be scrollable.
return false;
}
@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.
if (!mNotificationListAtBottomAtTimeOfTouch) {
return false;
}
if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH
|| Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
// swipe was not vertical or was not fast enough
return false;
}
boolean isUp = velocityY < 0;
if (isUp) {
close();
return true;
} else {
// we should close the shade
animateNotificationPanel(velocityY, false);
}
return false;
}
protected abstract void close();
}
/**
* To be installed on the nav bars.
*/
private abstract class NavBarCloseNotificationGestureListener extends
CloseNotificationGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
mClosingVelocity = DEFAULT_FLING_VELOCITY;
if (mPanelExpanded) {
close();
}
return super.onSingleTapUp(e);
}
@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
float distanceY) {
calculatePercentageFromBottom(event2.getRawY());
setNotificationViewClipBounds((int) event2.getRawY());
return true;
}
}
/**
* To be installed on the handle bar.
*/
private class HandleBarCloseNotificationGestureListener extends
GestureDetector.SimpleOnGestureListener {
@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
// 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 =
mNotificationView.getHeight() - (int) (event1.getRawY() - event2.getRawY());
setNotificationViewClipBounds(clipHeight);
return true;
}
}
}

View File

@@ -0,0 +1,126 @@
/*
* 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 android.car.hardware.power.CarPowerManager;
import android.content.res.Configuration;
import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.navigationbar.car.CarNavigationBarController;
import com.android.systemui.statusbar.car.PowerManagerHelper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.window.OverlayViewMediator;
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,
ConfigurationController.ConfigurationListener {
private final CarNavigationBarController mCarNavigationBarController;
private final NotificationPanelViewController mNotificationPanelViewController;
private final CarServiceProvider mCarServiceProvider;
private final PowerManagerHelper mPowerManagerHelper;
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
private final ConfigurationController mConfigurationController;
@Inject
public NotificationPanelViewMediator(
CarNavigationBarController carNavigationBarController,
NotificationPanelViewController notificationPanelViewController,
CarServiceProvider carServiceProvider,
PowerManagerHelper powerManagerHelper,
CarDeviceProvisionedController carDeviceProvisionedController,
ConfigurationController configurationController
) {
mCarNavigationBarController = carNavigationBarController;
mNotificationPanelViewController = notificationPanelViewController;
mCarServiceProvider = carServiceProvider;
mPowerManagerHelper = powerManagerHelper;
mCarDeviceProvisionedController = carDeviceProvisionedController;
mConfigurationController = configurationController;
}
@Override
public void registerListeners() {
mCarNavigationBarController.registerTopBarTouchListener(
mNotificationPanelViewController.getTopNavBarNotificationTouchListener());
mCarNavigationBarController.registerBottomBarTouchListener(
mNotificationPanelViewController.getNavBarNotificationTouchListener());
mCarNavigationBarController.registerLeftBarTouchListener(
mNotificationPanelViewController.getNavBarNotificationTouchListener());
mCarNavigationBarController.registerRightBarTouchListener(
mNotificationPanelViewController.getNavBarNotificationTouchListener());
mCarNavigationBarController.registerNotificationController(
() -> mNotificationPanelViewController.toggle());
}
@Override
public void setupOverlayContentViewControllers() {
mNotificationPanelViewController.setOnUnseenCountUpdateListener(unseenNotificationCount -> {
boolean hasUnseen = unseenNotificationCount > 0;
mCarNavigationBarController.toggleAllNotificationsUnseenIndicator(
mCarDeviceProvisionedController.isCurrentUserFullySetup(), hasUnseen);
});
mPowerManagerHelper.setCarPowerStateListener(state -> {
if (state == CarPowerManager.CarPowerStateListener.ON) {
mNotificationPanelViewController.onCarPowerStateOn();
}
});
mPowerManagerHelper.connectToCarService();
mConfigurationController.addCallback(this);
}
@Override
public void onConfigChanged(Configuration newConfig) {
// No op.
}
@Override
public void onDensityOrFontScaleChanged() {
registerListeners();
}
@Override
public void onOverlayChanged() {
// No op.
}
@Override
public void onUiModeChanged() {
// No op.
}
@Override
public void onThemeChanged() {
// No op.
}
@Override
public void onLocaleListChanged() {
mNotificationPanelViewController.reinflate();
registerListeners();
}
}

View File

@@ -1,85 +0,0 @@
/*
* Copyright (C) 2019 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.car;
import android.view.View;
import android.view.WindowManager;
import com.android.car.notification.CarNotificationView;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.ShadeControllerImpl;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Lazy;
/** Car specific implementation of {@link com.android.systemui.statusbar.phone.ShadeController}. */
@Singleton
public class CarShadeControllerImpl extends ShadeControllerImpl {
@Inject
public CarShadeControllerImpl(CommandQueue commandQueue,
StatusBarStateController statusBarStateController,
NotificationShadeWindowController notificationShadeWindowController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
WindowManager windowManager,
Lazy<StatusBar> statusBarLazy,
Lazy<AssistManager> assistManagerLazy,
Lazy<BubbleController> bubbleControllerLazy) {
super(commandQueue, statusBarStateController, notificationShadeWindowController,
statusBarKeyguardViewManager, windowManager,
statusBarLazy, assistManagerLazy, bubbleControllerLazy);
}
@Override
public void animateCollapsePanels(int flags, boolean force, boolean delayed,
float speedUpFactor) {
super.animateCollapsePanels(flags, force, delayed, speedUpFactor);
if (!getCarStatusBar().isPanelExpanded()
|| getCarNotificationView().getVisibility() == View.INVISIBLE) {
return;
}
mNotificationShadeWindowController.setNotificationShadeFocusable(false);
getCarStatusBar().getNotificationShadeWindowViewController().cancelExpandHelper();
getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor);
getCarStatusBar().animateNotificationPanel(getCarStatusBar().getClosingVelocity(), true);
if (!getCarStatusBar().isTracking()) {
mNotificationShadeWindowController.setPanelVisible(false);
getCarNotificationView().setVisibility(View.INVISIBLE);
}
getCarStatusBar().setPanelExpanded(false);
}
private CarStatusBar getCarStatusBar() {
return (CarStatusBar) mStatusBarLazy.get();
}
private CarNotificationView getCarNotificationView() {
return getCarStatusBar().getCarNotificationView();
}
}

View File

@@ -18,38 +18,15 @@ package com.android.systemui.statusbar.car;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.car.Car;
import android.car.drivingstate.CarUxRestrictionsManager;
import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.PowerManager;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.android.car.notification.CarNotificationListener;
import com.android.car.notification.CarNotificationView;
import com.android.car.notification.CarUxRestrictionManagerWrapper;
import com.android.car.notification.NotificationClickHandlerFactory;
import com.android.car.notification.NotificationDataManager;
import com.android.car.notification.NotificationViewController;
import com.android.car.notification.PreprocessingManager;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -64,7 +41,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.car.CarDeviceProvisionedListener;
import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -84,7 +60,6 @@ import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NavigationBarController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -156,22 +131,9 @@ import dagger.Lazy;
*/
public class CarStatusBar extends StatusBar implements CarBatteryController.BatteryViewHandler {
private static final String TAG = "CarStatusBar";
// used to calculate how fast to open or close the window
private static final float DEFAULT_FLING_VELOCITY = 0;
// max time a fling animation takes
private static final float FLING_ANIMATION_MAX_TIME = 0.5f;
// acceleration rate for the fling animation
private static final float FLING_SPEED_UP_FACTOR = 0.6f;
private final UserSwitcherController mUserSwitcherController;
private final ScrimController mScrimController;
private final LockscreenLockIconController mLockscreenLockIconController;
private float mOpeningVelocity = DEFAULT_FLING_VELOCITY;
private float mClosingVelocity = DEFAULT_FLING_VELOCITY;
private float mBackgroundAlphaDiff;
private float mInitialBackgroundAlpha;
private CarBatteryController mCarBatteryController;
private BatteryMeterView mBatteryMeterView;
@@ -179,58 +141,11 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt
private final Object mQueueLock = new Object();
private final CarNavigationBarController mCarNavigationBarController;
private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
private final Lazy<PowerManagerHelper> mPowerManagerHelperLazy;
private final ShadeController mShadeController;
private final CarServiceProvider mCarServiceProvider;
private final NotificationDataManager mNotificationDataManager;
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
private final ScreenLifecycle mScreenLifecycle;
private final CarNotificationListener mCarNotificationListener;
private boolean mDeviceIsSetUpForUser = true;
private boolean mIsUserSetupInProgress = false;
private PowerManagerHelper mPowerManagerHelper;
private FlingAnimationUtils mFlingAnimationUtils;
private NotificationClickHandlerFactory mNotificationClickHandlerFactory;
// The container for the notifications.
private CarNotificationView mNotificationView;
private RecyclerView mNotificationList;
// The handler bar view at the bottom of notification shade.
private View mHandleBar;
// The controller for the notification view.
private NotificationViewController mNotificationViewController;
// The state of if the notification list is currently showing the bottom.
private boolean mNotificationListAtBottom;
// Was the notification list at the bottom when the user first touched the screen
private boolean mNotificationListAtBottomAtTimeOfTouch;
// To be attached to the top navigation bar (i.e. status bar) to pull down the notification
// panel.
private View.OnTouchListener mTopNavBarNotificationTouchListener;
// To be attached to the navigation bars such that they can close the notification panel if
// it's open.
private View.OnTouchListener mNavBarNotificationTouchListener;
// Percentage from top of the screen after which the notification shade will open. This value
// will be used while opening the notification shade.
private int mSettleOpenPercentage;
// Percentage from top of the screen below which the notification shade will close. This
// value will be used while closing the notification shade.
private int mSettleClosePercentage;
// Percentage of notification shade open from top of the screen.
private int mPercentageFromBottom;
// If notification shade is animation to close or to open.
private boolean mIsNotificationAnimating;
// Tracks when the notification shade is being scrolled. This refers to the glass pane being
// scrolled not the recycler view.
private boolean mIsTracking;
private float mFirstTouchDownOnGlassPane;
// If the notification card inside the recycler view is being swiped.
private boolean mIsNotificationCardSwiping;
// If notification shade is being swiped vertically to close.
private boolean mIsSwipingVerticallyToClose;
private final CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
public CarStatusBar(
Context context,
@@ -311,13 +226,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt
DismissCallbackRegistry dismissCallbackRegistry,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
/* Car Settings injected components. */
CarServiceProvider carServiceProvider,
Lazy<PowerManagerHelper> powerManagerHelperLazy,
CarNavigationBarController carNavigationBarController,
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
NotificationDataManager notificationDataManager,
CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
CarNotificationListener carNotificationListener) {
CarNavigationBarController carNavigationBarController) {
super(
context,
notificationsController,
@@ -398,17 +307,9 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt
statusBarTouchableRegionManager);
mUserSwitcherController = userSwitcherController;
mScrimController = scrimController;
mLockscreenLockIconController = lockscreenLockIconController;
mCarDeviceProvisionedController = carDeviceProvisionedController;
mShadeController = shadeController;
mCarServiceProvider = carServiceProvider;
mPowerManagerHelperLazy = powerManagerHelperLazy;
mCarNavigationBarController = carNavigationBarController;
mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
mScreenLifecycle = screenLifecycle;
mNotificationDataManager = notificationDataManager;
mCarUxRestrictionManagerWrapper = carUxRestrictionManagerWrapper;
mCarNotificationListener = carNotificationListener;
}
@Override
@@ -416,54 +317,11 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt
mDeviceIsSetUpForUser = mCarDeviceProvisionedController.isCurrentUserSetup();
mIsUserSetupInProgress = mCarDeviceProvisionedController.isCurrentUserSetupInProgress();
// Notification bar related setup.
mInitialBackgroundAlpha = (float) mContext.getResources().getInteger(
R.integer.config_initialNotificationBackgroundAlpha) / 100;
if (mInitialBackgroundAlpha < 0 || mInitialBackgroundAlpha > 100) {
throw new RuntimeException(
"Unable to setup notification bar due to incorrect initial background alpha"
+ " percentage");
}
float finalBackgroundAlpha = Math.max(
mInitialBackgroundAlpha,
(float) mContext.getResources().getInteger(
R.integer.config_finalNotificationBackgroundAlpha) / 100);
if (finalBackgroundAlpha < 0 || finalBackgroundAlpha > 100) {
throw new RuntimeException(
"Unable to setup notification bar due to incorrect final background alpha"
+ " percentage");
}
mBackgroundAlphaDiff = finalBackgroundAlpha - mInitialBackgroundAlpha;
super.start();
mNotificationPanelViewController.setScrollingEnabled(true);
mSettleOpenPercentage = mContext.getResources().getInteger(
R.integer.notification_settle_open_percentage);
mSettleClosePercentage = mContext.getResources().getInteger(
R.integer.notification_settle_close_percentage);
mFlingAnimationUtils = mFlingAnimationUtilsBuilder
.setMaxLengthSeconds(FLING_ANIMATION_MAX_TIME)
.setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
.build();
createBatteryController();
mCarBatteryController.startListening();
mPowerManagerHelper = mPowerManagerHelperLazy.get();
mPowerManagerHelper.setCarPowerStateListener(
state -> {
// When the car powers on, clear all notifications and mute/unread states.
Log.d(TAG, "New car power state: " + state);
if (state == CarPowerStateListener.ON) {
if (mNotificationClickHandlerFactory != null) {
mNotificationClickHandlerFactory.clearAllNotifications();
}
mNotificationDataManager.clearAll();
}
});
mPowerManagerHelper.connectToCarService();
mCarDeviceProvisionedController.addCallback(
new CarDeviceProvisionedListener() {
@Override
@@ -540,318 +398,11 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt
// when a device has connected by bluetooth.
mBatteryMeterView.setVisibility(View.GONE);
});
connectNotificationsUI();
}
/**
* Attach the notification listeners and controllers to the UI as well as build all the
* touch listeners needed for opening and closing the notification panel
*/
private void connectNotificationsUI() {
// Attached to the top navigation bar (i.e. status bar) to detect pull down of the
// notification shade.
GestureDetector openGestureDetector = new GestureDetector(mContext,
new OpenNotificationGestureListener() {
@Override
protected void openNotification() {
animateExpandNotificationsPanel();
}
});
// Attached to the notification ui to detect close request of the notification shade.
GestureDetector closeGestureDetector = new GestureDetector(mContext,
new CloseNotificationGestureListener() {
@Override
protected void close() {
if (mPanelExpanded) {
mShadeController.animateCollapsePanels();
}
}
});
// Attached to the NavBars to close the notification shade
GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(mContext,
new NavBarCloseNotificationGestureListener() {
@Override
protected void close() {
if (mPanelExpanded) {
mShadeController.animateCollapsePanels();
}
}
});
// Attached to the Handle bar to close the notification shade
GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
new HandleBarCloseNotificationGestureListener());
mTopNavBarNotificationTouchListener = (v, event) -> {
if (!isDeviceSetupForUser()) {
return true;
}
boolean consumed = openGestureDetector.onTouchEvent(event);
if (consumed) {
return true;
}
maybeCompleteAnimation(event);
return true;
};
mNavBarNotificationTouchListener =
(v, event) -> {
boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
if (consumed) {
return true;
}
maybeCompleteAnimation(event);
return true;
};
mNotificationClickHandlerFactory = new NotificationClickHandlerFactory(mBarService);
mNotificationClickHandlerFactory.registerClickListener((launchResult, alertEntry) -> {
if (launchResult == ActivityManager.START_TASK_TO_FRONT
|| launchResult == ActivityManager.START_SUCCESS) {
mShadeController.animateCollapsePanels();
}
});
mNotificationDataManager.setOnUnseenCountUpdateListener(() -> {
onUseenCountUpdate(mNotificationDataManager.getUnseenNotificationCount());
});
mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
final View glassPane = mNotificationShadeWindowView.findViewById(R.id.glass_pane);
mNotificationView = mNotificationShadeWindowView.findViewById(R.id.notification_view);
mHandleBar = mNotificationShadeWindowView.findViewById(R.id.handle_bar);
mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory);
mNotificationView.setNotificationDataManager(mNotificationDataManager);
// The glass pane is used to view touch events before passed to the notification list.
// 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;
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mFirstTouchDownOnGlassPane = event.getRawX();
mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom;
// 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
// started.
closeGestureDetector.onTouchEvent(event);
}
return false;
});
mHandleBar.setOnTouchListener((v, event) -> {
handleBarCloseNotificationGestureDetector.onTouchEvent(event);
maybeCompleteAnimation(event);
return true;
});
mNotificationList = mNotificationView.findViewById(R.id.notifications);
mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!mNotificationList.canScrollVertically(1)) {
mNotificationListAtBottom = true;
return;
}
mNotificationListAtBottom = false;
mIsSwipingVerticallyToClose = false;
mNotificationListAtBottomAtTimeOfTouch = false;
}
});
mNotificationList.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX())
> SWIPE_MAX_OFF_PATH;
if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) {
// We need to save the state here as if notification card is swiping we will
// change the mNotificationListAtBottomAtTimeOfTouch. 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
// 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;
}
boolean handled = closeGestureDetector.onTouchEvent(event);
boolean isTracking = mIsTracking;
Rect rect = mNotificationView.getClipBounds();
float clippedHeight = 0;
if (rect != null) {
clippedHeight = rect.bottom;
}
if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
&& mIsSwipingVerticallyToClose) {
if (mSettleClosePercentage < mPercentageFromBottom && isTracking) {
animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
} else if (clippedHeight != mNotificationView.getHeight() && isTracking) {
// this can be caused when user is at the end of the list and trying to
// fling to top of the list by scrolling down.
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
}
}
// Updating the mNotificationListAtBottomAtTimeOfTouch 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;
}
return handled || isTracking;
}
});
mCarServiceProvider.addListener(car -> {
CarUxRestrictionsManager carUxRestrictionsManager =
(CarUxRestrictionsManager)
car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager(
carUxRestrictionsManager);
mNotificationViewController = new NotificationViewController(
mNotificationView,
PreprocessingManager.getInstance(mContext),
mCarNotificationListener,
mCarUxRestrictionManagerWrapper,
mNotificationDataManager);
mNotificationViewController.enable();
});
}
/**
* This method is automatically called whenever there is an update to the number of unseen
* notifications. This method can be extended by OEMs to customize the desired logic.
*/
protected void onUseenCountUpdate(int unseenNotificationCount) {
boolean hasUnseen = unseenNotificationCount > 0;
mCarNavigationBarController.toggleAllNotificationsUnseenIndicator(isDeviceSetupForUser(),
hasUnseen);
}
/**
* @return true if the notification panel is currently visible
*/
boolean isNotificationPanelOpen() {
return mPanelExpanded;
}
@Override
public void animateExpandNotificationsPanel() {
if (!mCommandQueue.panelsEnabled() || !mUserSetup) {
return;
}
// scroll to top
mNotificationList.scrollToPosition(0);
mNotificationShadeWindowController.setPanelVisible(true);
mNotificationView.setVisibility(View.VISIBLE);
animateNotificationPanel(mOpeningVelocity, false);
setPanelExpanded(true);
}
public CarNotificationView getCarNotificationView() {
return mNotificationView;
}
public float getClosingVelocity() {
return mClosingVelocity;
}
public boolean isTracking() {
return mIsTracking;
}
private void maybeCompleteAnimation(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_UP
&& mNotificationView.getVisibility() == View.VISIBLE) {
if (mSettleClosePercentage < mPercentageFromBottom) {
animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
} else {
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
}
}
}
/**
* Animates the notification shade from one position to other. This is used to either open or
* close the notification shade completely with a velocity. If the animation is to close the
* notification shade this method also makes the view invisible after animation ends.
*/
void animateNotificationPanel(float velocity, boolean isClosing) {
float to = 0;
if (!isClosing) {
to = mNotificationView.getHeight();
}
Rect rect = mNotificationView.getClipBounds();
if (rect != null && rect.bottom != to) {
float from = rect.bottom;
animate(from, to, velocity, isClosing);
return;
}
// We will only be here if the shade is being opened programmatically or via button when
// height of the layout was not calculated.
ViewTreeObserver notificationTreeObserver = mNotificationView.getViewTreeObserver();
notificationTreeObserver.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
ViewTreeObserver obs = mNotificationView.getViewTreeObserver();
obs.removeOnGlobalLayoutListener(this);
float to = mNotificationView.getHeight();
animate(/* from= */ 0, to, velocity, isClosing);
}
});
}
private void animate(float from, float to, float velocity, boolean isClosing) {
if (mIsNotificationAnimating) {
return;
}
mIsNotificationAnimating = true;
mIsTracking = true;
ValueAnimator animator = ValueAnimator.ofFloat(from, to);
animator.addUpdateListener(
animation -> {
float animatedValue = (Float) animation.getAnimatedValue();
setNotificationViewClipBounds((int) animatedValue);
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mIsNotificationAnimating = false;
mIsTracking = false;
mOpeningVelocity = DEFAULT_FLING_VELOCITY;
mClosingVelocity = DEFAULT_FLING_VELOCITY;
if (isClosing) {
mNotificationShadeWindowController.setPanelVisible(false);
mNotificationView.setVisibility(View.INVISIBLE);
mNotificationView.setClipBounds(null);
mNotificationViewController.onVisibilityChanged(false);
// let the status bar know that the panel is closed
setPanelExpanded(false);
} else {
mNotificationViewController.onVisibilityChanged(true);
// let the status bar know that the panel is open
mNotificationView.setVisibleNotificationsAsSeen();
setPanelExpanded(true);
}
}
});
mFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity));
animator.start();
// No op.
}
@Override
@@ -867,25 +418,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt
@Override
protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
registerNavBarListeners();
}
private void registerNavBarListeners() {
// In CarStatusBar, navigation bars are built by NavigationBar.java
// Instead, we register necessary callbacks to the navigation bar controller.
mCarNavigationBarController.registerTopBarTouchListener(
mTopNavBarNotificationTouchListener);
mCarNavigationBarController.registerBottomBarTouchListener(
mNavBarNotificationTouchListener);
mCarNavigationBarController.registerLeftBarTouchListener(
mNavBarNotificationTouchListener);
mCarNavigationBarController.registerRightBarTouchListener(
mNavBarNotificationTouchListener);
mCarNavigationBarController.registerNotificationController(() -> togglePanel());
// No op.
}
@Override
@@ -966,245 +499,16 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt
@Override
public void onDensityOrFontScaleChanged() {
super.onDensityOrFontScaleChanged();
registerNavBarListeners();
// Need to update the background on density changed in case the change was due to night
// mode.
mNotificationPanelBackground = getDefaultWallpaper();
mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
}
@Override
public void onLocaleListChanged() {
connectNotificationsUI();
registerNavBarListeners();
}
/**
* Returns the {@link Drawable} that represents the wallpaper that the user has currently set.
*/
private Drawable getDefaultWallpaper() {
return mContext.getDrawable(com.android.internal.R.drawable.default_wallpaper);
}
private void setNotificationViewClipBounds(int height) {
if (height > mNotificationView.getHeight()) {
height = mNotificationView.getHeight();
}
Rect clipBounds = new Rect();
clipBounds.set(0, 0, mNotificationView.getWidth(), height);
// Sets the clip region on the notification list view.
mNotificationView.setClipBounds(clipBounds);
if (mHandleBar != null) {
ViewGroup.MarginLayoutParams lp =
(ViewGroup.MarginLayoutParams) mHandleBar.getLayoutParams();
mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin);
}
if (mNotificationView.getHeight() > 0) {
Drawable background = mNotificationView.getBackground().mutate();
background.setAlpha((int) (getBackgroundAlpha(height) * 255));
mNotificationView.setBackground(background);
}
}
/**
* Calculates the alpha value for the background based on how much of the notification
* 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);
}
@Override
public void onConfigChanged(Configuration newConfig) {
super.onConfigChanged(newConfig);
int uiModeNightMask = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
boolean dayNightModeChanged = uiModeNightMask == Configuration.UI_MODE_NIGHT_YES
|| uiModeNightMask == Configuration.UI_MODE_NIGHT_NO;
if (dayNightModeChanged) {
mNotificationView.setBackgroundColor(
mContext.getColor(R.color.notification_shade_background_color));
}
}
private void calculatePercentageFromBottom(float height) {
if (mNotificationView.getHeight() > 0) {
mPercentageFromBottom = (int) Math.abs(
height / mNotificationView.getHeight() * 100);
}
}
private static final int SWIPE_DOWN_MIN_DISTANCE = 25;
private static final int SWIPE_MAX_OFF_PATH = 75;
private static final int SWIPE_THRESHOLD_VELOCITY = 200;
/**
* Only responsible for open hooks. Since once the panel opens it covers all elements
* there is no need to merge with close.
*/
private abstract class OpenNotificationGestureListener extends
GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
float distanceY) {
if (mNotificationView.getVisibility() == View.INVISIBLE) {
// when the on-scroll is called for the first time to open.
mNotificationList.scrollToPosition(0);
}
mNotificationShadeWindowController.setPanelVisible(true);
mNotificationView.setVisibility(View.VISIBLE);
// clips the view for the notification shade when the user scrolls to open.
setNotificationViewClipBounds((int) event2.getRawY());
// Initially the scroll starts with height being zero. This checks protects from divide
// by zero error.
calculatePercentageFromBottom(event2.getRawY());
mIsTracking = true;
return true;
}
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
if (velocityY > SWIPE_THRESHOLD_VELOCITY) {
mOpeningVelocity = velocityY;
openNotification();
return true;
}
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
return false;
}
protected abstract void openNotification();
}
/**
* To be installed on the open panel notification panel
*/
private abstract class CloseNotificationGestureListener extends
GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent motionEvent) {
if (mPanelExpanded) {
animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
}
return true;
}
@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
float distanceY) {
// should not clip while scroll to the bottom of the list.
if (!mNotificationListAtBottomAtTimeOfTouch) {
return false;
}
float actualNotificationHeight =
mNotificationView.getHeight() - (event1.getRawY() - event2.getRawY());
if (actualNotificationHeight > mNotificationView.getHeight()) {
actualNotificationHeight = mNotificationView.getHeight();
}
if (mNotificationView.getHeight() > 0) {
mPercentageFromBottom = (int) Math.abs(
actualNotificationHeight / mNotificationView.getHeight() * 100);
boolean isUp = 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
// close. We are also checking for the upwards swipe gesture here because it is
// 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(mNotificationView.getHeight() - actualNotificationHeight)
> SWIPE_DOWN_MIN_DISTANCE && isUp) {
setNotificationViewClipBounds((int) actualNotificationHeight);
mIsTracking = true;
} else if (!isUp) {
setNotificationViewClipBounds((int) actualNotificationHeight);
}
}
// if we return true the the items in RV won't be scrollable.
return false;
}
@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.
if (!mNotificationListAtBottomAtTimeOfTouch) {
return false;
}
if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH
|| Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
// swipe was not vertical or was not fast enough
return false;
}
boolean isUp = velocityY < 0;
if (isUp) {
close();
return true;
} else {
// we should close the shade
animateNotificationPanel(velocityY, false);
}
return false;
}
protected abstract void close();
}
/**
* To be installed on the nav bars.
*/
private abstract class NavBarCloseNotificationGestureListener extends
CloseNotificationGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
mClosingVelocity = DEFAULT_FLING_VELOCITY;
if (mPanelExpanded) {
close();
}
return super.onSingleTapUp(e);
}
@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
float distanceY) {
calculatePercentageFromBottom(event2.getRawY());
setNotificationViewClipBounds((int) event2.getRawY());
return true;
}
}
/**
* To be installed on the handle bar.
*/
private class HandleBarCloseNotificationGestureListener extends
GestureDetector.SimpleOnGestureListener {
@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
// 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 =
mNotificationView.getHeight() - (int) (event1.getRawY() - event2.getRawY());
setNotificationViewClipBounds(clipHeight);
return true;
}
}
}

View File

@@ -23,9 +23,6 @@ import android.os.Handler;
import android.os.PowerManager;
import android.util.DisplayMetrics;
import com.android.car.notification.CarNotificationListener;
import com.android.car.notification.CarUxRestrictionManagerWrapper;
import com.android.car.notification.NotificationDataManager;
import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
@@ -34,7 +31,6 @@ import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.keyguard.DismissCallbackRegistry;
@@ -50,7 +46,6 @@ import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NavigationBarController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -205,13 +200,7 @@ public class CarStatusBarModule {
KeyguardIndicationController keyguardIndicationController,
DismissCallbackRegistry dismissCallbackRegistry,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
CarServiceProvider carServiceProvider,
Lazy<PowerManagerHelper> powerManagerHelperLazy,
CarNavigationBarController carNavigationBarController,
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
NotificationDataManager notificationDataManager,
CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
CarNotificationListener carNotificationListener) {
CarNavigationBarController carNavigationBarController) {
return new CarStatusBar(
context,
notificationsController,
@@ -289,12 +278,6 @@ public class CarStatusBarModule {
keyguardIndicationController,
dismissCallbackRegistry,
statusBarTouchableRegionManager,
carServiceProvider,
powerManagerHelperLazy,
carNavigationBarController,
flingAnimationUtilsBuilder,
notificationDataManager,
carUxRestrictionManagerWrapper,
carNotificationListener);
carNavigationBarController);
}
}

View File

@@ -42,7 +42,7 @@ public class PowerManagerHelper {
private final CarServiceProvider.CarServiceOnConnectedListener mCarServiceLifecycleListener;
@Inject
PowerManagerHelper(CarServiceProvider carServiceProvider) {
public PowerManagerHelper(CarServiceProvider carServiceProvider) {
mCarServiceProvider = carServiceProvider;
mCarServiceLifecycleListener = car -> {
Log.d(TAG, "Car Service connected");
@@ -58,14 +58,14 @@ public class PowerManagerHelper {
/**
* Sets a {@link CarPowerStateListener}. Should be set before {@link #connectToCarService()}.
*/
void setCarPowerStateListener(@NonNull CarPowerStateListener listener) {
public void setCarPowerStateListener(@NonNull CarPowerStateListener listener) {
mCarPowerStateListener = listener;
}
/**
* Connect to Car service.
*/
void connectToCarService() {
public void connectToCarService() {
mCarServiceProvider.addListener(mCarServiceLifecycleListener);
}
}

View File

@@ -120,4 +120,9 @@ public class OverlayViewController {
protected final View getLayout() {
return mLayout;
}
/** Returns the {@link OverlayViewGlobalStateController}. */
protected final OverlayViewGlobalStateController getOverlayViewGlobalStateController() {
return mOverlayViewGlobalStateController;
}
}

View File

@@ -71,12 +71,10 @@ public class OverlayViewGlobalStateController {
public void showView(OverlayViewController viewController, Runnable show) {
if (mShownSet.isEmpty()) {
mCarNavigationBarController.hideBars();
mSystemUIOverlayWindowController.setWindowExpanded(true);
setWindowVisible(true);
}
if (!viewController.isInflated()) {
viewController.inflate(mSystemUIOverlayWindowController.getBaseLayout());
}
inflateView(viewController);
show.run();
mShownSet.add(viewController.getClass().getName());
@@ -104,9 +102,36 @@ public class OverlayViewGlobalStateController {
if (mShownSet.isEmpty()) {
mCarNavigationBarController.showBars();
mSystemUIOverlayWindowController.setWindowExpanded(false);
setWindowVisible(false);
}
Log.d(TAG, "Content hidden: " + viewController.getClass().getName());
}
/** Sets the window visibility state. */
public void setWindowVisible(boolean expanded) {
mSystemUIOverlayWindowController.setWindowVisible(expanded);
}
/** Returns {@code true} is the window is visible. */
public boolean isWindowVisible() {
return mSystemUIOverlayWindowController.isWindowVisible();
}
/** Sets the focusable flag of the sysui overlawy window. */
public void setWindowFocusable(boolean focusable) {
mSystemUIOverlayWindowController.setWindowFocusable(focusable);
}
/** Returns {@code true} if the window is focusable. */
public boolean isWindowFocusable() {
return mSystemUIOverlayWindowController.isWindowFocusable();
}
/** Inflates the view controlled by the given view controller. */
public void inflateView(OverlayViewController viewController) {
if (!viewController.isInflated()) {
viewController.inflate(mSystemUIOverlayWindowController.getBaseLayout());
}
}
}

View File

@@ -16,6 +16,7 @@
package com.android.systemui.window;
import com.android.systemui.car.notification.NotificationPanelViewMediator;
import com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator;
import dagger.Binds;
@@ -28,10 +29,17 @@ import dagger.multibindings.IntoMap;
*/
@Module
public abstract class OverlayWindowModule {
/** Inject into FullscreenUserSwitcherViewsMediator. */
/** Injects FullscreenUserSwitcherViewsMediator. */
@Binds
@IntoMap
@ClassKey(FullscreenUserSwitcherViewMediator.class)
public abstract OverlayViewMediator bindFullscreenUserSwitcherViewsMediator(
FullscreenUserSwitcherViewMediator overlayViewsMediator);
/** Injects NotificationPanelViewMediator. */
@Binds
@IntoMap
@ClassKey(NotificationPanelViewMediator.class)
public abstract OverlayViewMediator bindNotificationPanelViewMediator(
NotificationPanelViewMediator notificationPanelViewMediator);
}

View File

@@ -19,17 +19,15 @@ package com.android.systemui.window;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.os.Binder;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.policy.ConfigurationController;
import javax.inject.Inject;
@@ -45,36 +43,24 @@ public class SystemUIOverlayWindowController implements
ConfigurationController.ConfigurationListener {
private final Context mContext;
private final Resources mResources;
private final WindowManager mWindowManager;
private final int mStatusBarHeight;
private final int mNavBarHeight;
private final int mDisplayHeight;
private ViewGroup mBaseLayout;
private WindowManager.LayoutParams mLp;
private WindowManager.LayoutParams mLpChanged;
private boolean mIsAttached = false;
private boolean mVisible = false;
private boolean mFocusable = false;
@Inject
public SystemUIOverlayWindowController(
Context context,
@Main Resources resources,
WindowManager windowManager,
ConfigurationController configurationController
) {
mContext = context;
mResources = resources;
mWindowManager = windowManager;
Point display = new Point();
mWindowManager.getDefaultDisplay().getSize(display);
mDisplayHeight = display.y;
mStatusBarHeight = mResources.getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height);
mNavBarHeight = mResources.getDimensionPixelSize(R.dimen.navigation_bar_height);
mLpChanged = new WindowManager.LayoutParams();
mBaseLayout = (ViewGroup) LayoutInflater.from(context)
.inflate(R.layout.sysui_overlay_window, /* root= */ null, false);
@@ -103,13 +89,13 @@ public class SystemUIOverlayWindowController implements
// hardware-accelerated.
mLp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
mStatusBarHeight,
ViewGroup.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
| WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
PixelFormat.TRANSLUCENT);
mLp.token = new Binder();
mLp.gravity = Gravity.TOP;
@@ -121,26 +107,38 @@ public class SystemUIOverlayWindowController implements
mWindowManager.addView(mBaseLayout, mLp);
mLpChanged.copyFrom(mLp);
setWindowVisible(false);
}
/** Sets the window to the expanded state. */
public void setWindowExpanded(boolean expanded) {
if (expanded) {
// TODO: Update this so that the windowing type gets the full height of the display
// when we use MATCH_PARENT.
mLpChanged.height = mDisplayHeight + mNavBarHeight;
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
/** Sets the window to the visible state. */
public void setWindowVisible(boolean visible) {
mVisible = visible;
if (visible) {
mBaseLayout.setVisibility(View.VISIBLE);
} else {
mLpChanged.height = mStatusBarHeight;
// TODO: Allow touches to go through to the status bar to handle notification panel.
mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
mBaseLayout.setVisibility(View.INVISIBLE);
}
updateWindow();
}
/** Returns {@code true} if the window is expanded */
public boolean isWindowExpanded() {
return mLp.height != mStatusBarHeight;
/** Sets the window to be focusable. */
public void setWindowFocusable(boolean focusable) {
mFocusable = focusable;
if (focusable) {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
} else {
mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
}
updateWindow();
}
/** Returns {@code true} if the window is visible */
public boolean isWindowVisible() {
return mVisible;
}
public boolean isWindowFocusable() {
return mFocusable;
}
private void updateWindow() {

View File

@@ -97,7 +97,7 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
public void showView_nothingAlreadyShown_windowIsExpanded() {
mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
verify(mSystemUIOverlayWindowController).setWindowExpanded(true);
verify(mSystemUIOverlayWindowController).setWindowVisible(true);
}
@Test
@@ -115,7 +115,7 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
mOverlayViewGlobalStateController.showView(mOverlayViewController, mRunnable);
verify(mSystemUIOverlayWindowController, never()).setWindowExpanded(true);
verify(mSystemUIOverlayWindowController, never()).setWindowVisible(true);
}
@Test
@@ -223,7 +223,7 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
verify(mSystemUIOverlayWindowController, never()).setWindowExpanded(false);
verify(mSystemUIOverlayWindowController, never()).setWindowVisible(false);
}
@Test
@@ -245,6 +245,24 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
mOverlayViewGlobalStateController.hideView(mOverlayViewController, mRunnable);
verify(mSystemUIOverlayWindowController).setWindowExpanded(false);
verify(mSystemUIOverlayWindowController).setWindowVisible(false);
}
@Test
public void inflateView_notInflated_inflates() {
when(mOverlayViewController.isInflated()).thenReturn(false);
mOverlayViewGlobalStateController.inflateView(mOverlayViewController);
verify(mOverlayViewController).inflate(mBaseLayout);
}
@Test
public void inflateView_alreadyInflated_doesNotInflate() {
when(mOverlayViewController.isInflated()).thenReturn(true);
mOverlayViewGlobalStateController.inflateView(mOverlayViewController);
verify(mOverlayViewController, never()).inflate(mBaseLayout);
}
}