diff options
17 files changed, 1048 insertions, 852 deletions
diff --git a/packages/CarSystemUI/res/layout/notification_center_activity.xml b/packages/CarSystemUI/res/layout/notification_center_activity.xml index 0af74c4462a6..4fef48918c97 100644 --- a/packages/CarSystemUI/res/layout/notification_center_activity.xml +++ b/packages/CarSystemUI/res/layout/notification_center_activity.xml @@ -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 diff --git a/packages/CarSystemUI/res/layout/notification_panel_container.xml b/packages/CarSystemUI/res/layout/notification_panel_container.xml new file mode 100644 index 000000000000..bf71396984b8 --- /dev/null +++ b/packages/CarSystemUI/res/layout/notification_panel_container.xml @@ -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"/> diff --git a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml index cc36e87eb480..1b0a211b733d 100644 --- a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml +++ b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml @@ -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" diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index aaa65de2fa1d..b629e1417f35 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -61,6 +61,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. --> diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java index 14d5bd53a9ab..5547fee0159c 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java @@ -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 diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java index c870cec67314..b057198d5177 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java @@ -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(); + } } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java new file mode 100644 index 000000000000..f6679c0e681f --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java @@ -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; + } + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java new file mode 100644 index 000000000000..1cfc83293acb --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java @@ -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(); + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarShadeControllerImpl.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarShadeControllerImpl.java deleted file mode 100644 index 755ed25d64e2..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarShadeControllerImpl.java +++ /dev/null @@ -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(); - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 411f14d1d1ed..02604d870986 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -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; - } - } } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java index 160268b8f461..1baa1f6891ee 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java @@ -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); } } diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java index 71847bbac9fd..615a7bae2930 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/PowerManagerHelper.java @@ -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); } } diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewController.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewController.java index 6e0db4f317ba..15ef0be38d4d 100644 --- a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewController.java @@ -120,4 +120,9 @@ public class OverlayViewController { protected final View getLayout() { return mLayout; } + + /** Returns the {@link OverlayViewGlobalStateController}. */ + protected final OverlayViewGlobalStateController getOverlayViewGlobalStateController() { + return mOverlayViewGlobalStateController; + } } diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java index 2d4a9e6331d2..402d742cb949 100644 --- a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java +++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java @@ -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()); + } + } } diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayWindowModule.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayWindowModule.java index b0e308966477..dd29f8dac387 100644 --- a/packages/CarSystemUI/src/com/android/systemui/window/OverlayWindowModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayWindowModule.java @@ -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); } diff --git a/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java index 9c456eecc1ac..0dbe1a3ea1dd 100644 --- a/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java +++ b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java @@ -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 visible state. */ + public void setWindowVisible(boolean visible) { + mVisible = visible; + if (visible) { + mBaseLayout.setVisibility(View.VISIBLE); + } else { + mBaseLayout.setVisibility(View.INVISIBLE); + } + updateWindow(); } - /** 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 be focusable. */ + public void setWindowFocusable(boolean focusable) { + mFocusable = focusable; + if (focusable) { + mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; } 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; + mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; } updateWindow(); } - /** Returns {@code true} if the window is expanded */ - public boolean isWindowExpanded() { - return mLp.height != mStatusBarHeight; + /** Returns {@code true} if the window is visible */ + public boolean isWindowVisible() { + return mVisible; + } + + public boolean isWindowFocusable() { + return mFocusable; } private void updateWindow() { diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewGlobalStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewGlobalStateControllerTest.java index 03354d1aa9d7..a96b90636891 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewGlobalStateControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewGlobalStateControllerTest.java @@ -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); } } |