/* * Copyright (C) 2022 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.shade; import android.content.ComponentCallbacks2; import android.os.Looper; import android.util.Log; import android.view.MotionEvent; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.WindowManagerGlobal; import com.android.systemui.assist.AssistManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.dagger.ShadeTouchLog; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.window.StatusBarWindowController; import dagger.Lazy; import java.util.ArrayList; import java.util.concurrent.Executor; import javax.inject.Inject; /** An implementation of {@link ShadeController}. */ @SysUISingleton public final class ShadeControllerImpl implements ShadeController { private static final String TAG = "ShadeControllerImpl"; private static final boolean SPEW = false; private final int mDisplayId; private final CommandQueue mCommandQueue; private final Executor mMainExecutor; private final LogBuffer mTouchLog; private final KeyguardStateController mKeyguardStateController; private final NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarStateController mStatusBarStateController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final StatusBarWindowController mStatusBarWindowController; private final DeviceProvisionedController mDeviceProvisionedController; private final Lazy mShadeViewControllerLazy; private final Lazy mAssistManagerLazy; private final Lazy mGutsManager; private final ArrayList mPostCollapseRunnables = new ArrayList<>(); private boolean mExpandedVisible; private NotificationPresenter mPresenter; private NotificationShadeWindowViewController mNotificationShadeWindowViewController; private ShadeVisibilityListener mShadeVisibilityListener; @Inject public ShadeControllerImpl( CommandQueue commandQueue, @Main Executor mainExecutor, @ShadeTouchLog LogBuffer touchLog, KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, StatusBarWindowController statusBarWindowController, DeviceProvisionedController deviceProvisionedController, NotificationShadeWindowController notificationShadeWindowController, WindowManager windowManager, Lazy shadeViewControllerLazy, Lazy assistManagerLazy, Lazy gutsManager ) { mCommandQueue = commandQueue; mMainExecutor = mainExecutor; mTouchLog = touchLog; mShadeViewControllerLazy = shadeViewControllerLazy; mStatusBarStateController = statusBarStateController; mStatusBarWindowController = statusBarWindowController; mDeviceProvisionedController = deviceProvisionedController; mGutsManager = gutsManager; mNotificationShadeWindowController = notificationShadeWindowController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mDisplayId = windowManager.getDefaultDisplay().getDisplayId(); mKeyguardStateController = keyguardStateController; mAssistManagerLazy = assistManagerLazy; } @Override public void instantExpandShade() { // Make our window larger and the panel expanded. makeExpandedVisible(true /* force */); getShadeViewController().expand(false /* animate */); mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */); } @Override public void animateCollapseShade(int flags, boolean force, boolean delayed, float speedUpFactor) { if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) { runPostCollapseRunnables(); return; } if (SPEW) { Log.d(TAG, "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags); } if (getNotificationShadeWindowView() != null && getShadeViewController().canBeCollapsed() && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) { // release focus immediately to kick off focus change transition mNotificationShadeWindowController.setNotificationShadeFocusable(false); mNotificationShadeWindowViewController.cancelExpandHelper(); getShadeViewController().collapse(true, delayed, speedUpFactor); } } @Override public void animateExpandShade() { if (!mCommandQueue.panelsEnabled()) { return; } getShadeViewController().expandToNotifications(); } @Override public void animateExpandQs() { if (!mCommandQueue.panelsEnabled()) { return; } // Settings are not available in setup if (!mDeviceProvisionedController.isCurrentUserSetup()) return; getShadeViewController().expandToQs(); } @Override public boolean closeShadeIfOpen() { if (!getShadeViewController().isFullyCollapsed()) { mCommandQueue.animateCollapsePanels( CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); notifyVisibilityChanged(false); mAssistManagerLazy.get().hideAssist(); } return false; } @Override public boolean isKeyguard() { return mStatusBarStateController.getState() == StatusBarState.KEYGUARD; } @Override public boolean isShadeFullyOpen() { return getShadeViewController().isShadeFullyExpanded(); } @Override public boolean isExpandingOrCollapsing() { return getShadeViewController().isExpandingOrCollapsing(); } @Override public void postAnimateCollapseShade() { mMainExecutor.execute(this::animateCollapseShade); } @Override public void postAnimateForceCollapseShade() { mMainExecutor.execute(this::animateCollapseShadeForced); } @Override public void postAnimateExpandQs() { mMainExecutor.execute(this::animateExpandQs); } @Override public void postOnShadeExpanded(Runnable executable) { getShadeViewController().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (getNotificationShadeWindowView().isVisibleToUser()) { getShadeViewController().removeOnGlobalLayoutListener(this); getShadeViewController().postToView(executable); } } }); } @Override public void addPostCollapseAction(Runnable action) { mPostCollapseRunnables.add(action); } @Override public void runPostCollapseRunnables() { ArrayList clonedList = new ArrayList<>(mPostCollapseRunnables); mPostCollapseRunnables.clear(); int size = clonedList.size(); for (int i = 0; i < size; i++) { clonedList.get(i).run(); } mStatusBarKeyguardViewManager.readyForKeyguardDone(); } @Override public boolean collapseShade() { if (!getShadeViewController().isFullyCollapsed()) { // close the shade if it was open animateCollapseShadeForcedDelayed(); notifyVisibilityChanged(false); return true; } else { return false; } } @Override public void collapseShade(boolean animate) { if (animate) { boolean willCollapse = collapseShade(); if (!willCollapse) { runPostCollapseRunnables(); } } else if (!mPresenter.isPresenterFullyCollapsed()) { instantCollapseShade(); notifyVisibilityChanged(false); } else { runPostCollapseRunnables(); } } @Override public void cancelExpansionAndCollapseShade() { if (getShadeViewController().isTracking()) { mNotificationShadeWindowViewController.cancelCurrentTouch(); } if (getShadeViewController().isPanelExpanded() && mStatusBarStateController.getState() == StatusBarState.SHADE) { animateCollapseShade(); } } @Override public void collapseOnMainThread() { if (Looper.getMainLooper().isCurrentThread()) { collapseShade(); } else { mMainExecutor.execute(this::collapseShade); } } @Override public void onStatusBarTouch(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { if (mExpandedVisible) { animateCollapseShade(); } } } private void onClosingFinished() { runPostCollapseRunnables(); if (!mPresenter.isPresenterFullyCollapsed()) { // if we set it not to be focusable when collapsing, we have to undo it when we aborted // the closing mNotificationShadeWindowController.setNotificationShadeFocusable(true); } } @Override public void onLaunchAnimationCancelled(boolean isLaunchForActivity) { if (mPresenter.isPresenterFullyCollapsed() && !mPresenter.isCollapsing() && isLaunchForActivity) { onClosingFinished(); } else { collapseShade(true /* animate */); } } @Override public void onLaunchAnimationEnd(boolean launchIsFullScreen) { if (!mPresenter.isCollapsing()) { onClosingFinished(); } if (launchIsFullScreen) { instantCollapseShade(); } } @Override public void instantCollapseShade() { getShadeViewController().instantCollapse(); runPostCollapseRunnables(); } @Override public void makeExpandedVisible(boolean force) { if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) { return; } mExpandedVisible = true; // Expand the window to encompass the full screen in anticipation of the drag. // It's only possible to do atomically because the status bar is at the top of the screen! mNotificationShadeWindowController.setPanelVisible(true); notifyVisibilityChanged(true); mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */); notifyExpandedVisibleChanged(true); } @Override public void makeExpandedInvisible() { if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible); if (!mExpandedVisible || getNotificationShadeWindowView() == null) { return; } // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868) getShadeViewController().collapse(false, false, 1.0f); mExpandedVisible = false; notifyVisibilityChanged(false); // Update the visibility of notification shade and status bar window. mNotificationShadeWindowController.setPanelVisible(false); mStatusBarWindowController.setForceStatusBarVisible(false); // Close any guts that might be visible mGutsManager.get().closeAndSaveGuts( true /* removeLeavebehind */, true /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */); runPostCollapseRunnables(); notifyExpandedVisibleChanged(false); mCommandQueue.recomputeDisableFlags( mDisplayId, getShadeViewController().shouldHideStatusBarIconsWhenExpanded()); // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in // the bouncer appear animation. if (!mKeyguardStateController.isShowing()) { WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); } } @Override public boolean isExpandedVisible() { return mExpandedVisible; } @Override public void setVisibilityListener(ShadeVisibilityListener listener) { mShadeVisibilityListener = listener; } private void notifyVisibilityChanged(boolean visible) { mShadeVisibilityListener.visibilityChanged(visible); } private void notifyExpandedVisibleChanged(boolean expandedVisible) { mShadeVisibilityListener.expandedVisibleChanged(expandedVisible); } @Override public void setNotificationPresenter(NotificationPresenter presenter) { mPresenter = presenter; } @Override public void setNotificationShadeWindowViewController( NotificationShadeWindowViewController controller) { mNotificationShadeWindowViewController = controller; } private NotificationShadeWindowView getNotificationShadeWindowView() { return mNotificationShadeWindowViewController.getView(); } private ShadeViewController getShadeViewController() { return mShadeViewControllerLazy.get(); } @Override public void start() { TouchLogger.logTouchesTo(mTouchLog); getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables); getShadeViewController().setOpenCloseListener( new OpenCloseListener() { @Override public void onClosingFinished() { ShadeControllerImpl.this.onClosingFinished(); } @Override public void onOpenStarted() { makeExpandedVisible(false); } }); } }