Merge "Separate Notification Panel" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
47e2a13cab
@@ -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
|
||||
|
||||
@@ -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"/>
|
||||
@@ -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"
|
||||
|
||||
@@ -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. -->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,4 +120,9 @@ public class OverlayViewController {
|
||||
protected final View getLayout() {
|
||||
return mLayout;
|
||||
}
|
||||
|
||||
/** Returns the {@link OverlayViewGlobalStateController}. */
|
||||
protected final OverlayViewGlobalStateController getOverlayViewGlobalStateController() {
|
||||
return mOverlayViewGlobalStateController;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user