blob: 9e0c49641e72933a4b3100b373f11bd856922053 [file] [log] [blame]
/*
* Copyright 2021 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.navigationbar;
import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.containsType;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.systemui.navigationbar.NavBarHelper.transitionMode;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IMMERSIVE_MODE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import android.app.StatusBarManager;
import android.app.StatusBarManager.WindowVisibleState;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.Display;
import android.view.InsetsVisibilities;
import android.view.View;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
import androidx.annotation.NonNull;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
import java.io.PrintWriter;
import java.util.Optional;
import java.util.function.Consumer;
import javax.inject.Inject;
/** */
@SysUISingleton
public class TaskbarDelegate implements CommandQueue.Callbacks,
OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
ComponentCallbacks, Dumpable {
private static final String TAG = TaskbarDelegate.class.getSimpleName();
private final EdgeBackGestureHandler mEdgeBackGestureHandler;
private final LightBarTransitionsController.Factory mLightBarTransitionsControllerFactory;
private boolean mInitialized;
private CommandQueue mCommandQueue;
private OverviewProxyService mOverviewProxyService;
private NavBarHelper mNavBarHelper;
private NavigationModeController mNavigationModeController;
private SysUiState mSysUiState;
private AutoHideController mAutoHideController;
private LightBarController mLightBarController;
private LightBarTransitionsController mLightBarTransitionsController;
private Optional<Pip> mPipOptional;
private int mDisplayId;
private int mNavigationIconHints;
private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater =
new NavBarHelper.NavbarTaskbarStateUpdater() {
@Override
public void updateAccessibilityServicesState() {
updateSysuiFlags();
}
@Override
public void updateAssistantAvailable(boolean available) {
updateAssistantAvailability(available);
}
};
private int mDisabledFlags;
private @WindowVisibleState int mTaskBarWindowState = WINDOW_STATE_SHOWING;
private @TransitionMode int mTransitionMode;
private @Appearance int mAppearance;
private @Behavior int mBehavior;
private final Context mContext;
private final DisplayManager mDisplayManager;
private Context mWindowContext;
private ScreenPinningNotify mScreenPinningNotify;
private int mNavigationMode;
private final Consumer<Rect> mPipListener;
/**
* Tracks the system calls for when taskbar should transiently show or hide so we can return
* this value in {@link AutoHideUiElement#isVisible()} below.
*
* This also gets set by {@link #onTaskbarAutohideSuspend(boolean)} to force show the transient
* taskbar if launcher has requested to suspend auto-hide behavior.
*/
private boolean mTaskbarTransientShowing;
private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() {
@Override
public void synchronizeState() {
}
@Override
public boolean isVisible() {
return mTaskbarTransientShowing;
}
@Override
public void hide() {
clearTransient();
}
};
private BackAnimation mBackAnimation;
@Inject
public TaskbarDelegate(Context context,
EdgeBackGestureHandler.Factory edgeBackGestureHandlerFactory,
LightBarTransitionsController.Factory lightBarTransitionsControllerFactory) {
mLightBarTransitionsControllerFactory = lightBarTransitionsControllerFactory;
mEdgeBackGestureHandler = edgeBackGestureHandlerFactory.create(context);
mContext = context;
mDisplayManager = mContext.getSystemService(DisplayManager.class);
mPipListener = mEdgeBackGestureHandler::setPipStashExclusionBounds;
}
public void setDependencies(CommandQueue commandQueue,
OverviewProxyService overviewProxyService,
NavBarHelper navBarHelper,
NavigationModeController navigationModeController,
SysUiState sysUiState, DumpManager dumpManager,
AutoHideController autoHideController,
LightBarController lightBarController,
Optional<Pip> pipOptional,
BackAnimation backAnimation) {
// TODO: adding this in the ctor results in a dagger dependency cycle :(
mCommandQueue = commandQueue;
mOverviewProxyService = overviewProxyService;
mNavBarHelper = navBarHelper;
mNavigationModeController = navigationModeController;
mSysUiState = sysUiState;
dumpManager.registerDumpable(this);
mAutoHideController = autoHideController;
mLightBarController = lightBarController;
mPipOptional = pipOptional;
mBackAnimation = backAnimation;
mLightBarTransitionsController = createLightBarTransitionsController();
}
// Separated into a method to keep setDependencies() clean/readable.
private LightBarTransitionsController createLightBarTransitionsController() {
LightBarTransitionsController controller = mLightBarTransitionsControllerFactory.create(
new LightBarTransitionsController.DarkIntensityApplier() {
@Override
public void applyDarkIntensity(float darkIntensity) {
mOverviewProxyService.onNavButtonsDarkIntensityChanged(darkIntensity);
}
@Override
public int getTintAnimationDuration() {
return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION;
}
});
controller.overrideIconTintForNavMode(true);
return controller;
}
public void init(int displayId) {
if (mInitialized) {
return;
}
mDisplayId = displayId;
mCommandQueue.addCallback(this);
mOverviewProxyService.addCallback(this);
mEdgeBackGestureHandler.onNavigationModeChanged(
mNavigationModeController.addListener(this));
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
mNavBarHelper.init();
mEdgeBackGestureHandler.onNavBarAttached();
// Initialize component callback
Display display = mDisplayManager.getDisplay(displayId);
mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
mWindowContext.registerComponentCallbacks(this);
mScreenPinningNotify = new ScreenPinningNotify(mWindowContext);
// Set initial state for any listeners
updateSysuiFlags();
mAutoHideController.setNavigationBar(mAutoHideUiElement);
mLightBarController.setNavigationBar(mLightBarTransitionsController);
mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener);
mEdgeBackGestureHandler.setBackAnimation(mBackAnimation);
mInitialized = true;
}
public void destroy() {
if (!mInitialized) {
return;
}
mCommandQueue.removeCallback(this);
mOverviewProxyService.removeCallback(this);
mNavigationModeController.removeListener(this);
mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
mNavBarHelper.destroy();
mEdgeBackGestureHandler.onNavBarDetached();
mScreenPinningNotify = null;
if (mWindowContext != null) {
mWindowContext.unregisterComponentCallbacks(this);
mWindowContext = null;
}
mAutoHideController.setNavigationBar(null);
mLightBarTransitionsController.destroy();
mLightBarController.setNavigationBar(null);
mPipOptional.ifPresent(this::removePipExclusionBoundsChangeListener);
mInitialized = false;
}
void addPipExclusionBoundsChangeListener(Pip pip) {
pip.addPipExclusionBoundsChangeListener(mPipListener);
}
void removePipExclusionBoundsChangeListener(Pip pip) {
pip.removePipExclusionBoundsChangeListener(mPipListener);
}
/**
* Returns {@code true} if this taskBar is {@link #init(int)}. Returns {@code false} if this
* taskbar has not yet been {@link #init(int)} or has been {@link #destroy()}.
*/
public boolean isInitialized() {
return mInitialized;
}
private void updateSysuiFlags() {
int a11yFlags = mNavBarHelper.getA11yButtonState();
boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
mSysUiState.setFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE, clickable)
.setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable)
.setFlag(SYSUI_STATE_IME_SHOWING,
(mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0)
.setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING,
(mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0)
.setFlag(SYSUI_STATE_OVERVIEW_DISABLED,
(mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0)
.setFlag(SYSUI_STATE_HOME_DISABLED,
(mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0)
.setFlag(SYSUI_STATE_BACK_DISABLED,
(mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
.setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isWindowVisible())
.setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
allowSystemGestureIgnoringBarVisibility())
.setFlag(SYSUI_STATE_SCREEN_PINNING,
ActivityManagerWrapper.getInstance().isScreenPinningActive())
.setFlag(SYSUI_STATE_IMMERSIVE_MODE, isImmersiveMode())
.commitUpdate(mDisplayId);
}
boolean isOverviewEnabled() {
return (mSysUiState.getFlags() & View.STATUS_BAR_DISABLE_RECENT) == 0;
}
private void updateAssistantAvailability(boolean assistantAvailable) {
if (mOverviewProxyService.getProxy() == null) {
return;
}
try {
mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable);
} catch (RemoteException e) {
Log.e(TAG, "onAssistantAvailable() failed, available: " + assistantAvailable, e);
}
}
@Override
public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
boolean showImeSwitcher) {
boolean imeShown = mNavBarHelper.isImeShown(vis);
if (!imeShown) {
// Count imperceptible changes as visible so we transition taskbar out quickly.
imeShown = (vis & InputMethodService.IME_VISIBLE_IMPERCEPTIBLE) != 0;
}
showImeSwitcher = imeShown && showImeSwitcher;
int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition,
imeShown, showImeSwitcher);
if (hints != mNavigationIconHints) {
mNavigationIconHints = hints;
updateSysuiFlags();
}
}
@Override
public void setWindowState(int displayId, int window, int state) {
if (displayId == mDisplayId
&& window == StatusBarManager.WINDOW_NAVIGATION_BAR
&& mTaskBarWindowState != state) {
mTaskBarWindowState = state;
updateSysuiFlags();
}
}
@Override
public void onRotationProposal(int rotation, boolean isValid) {
mOverviewProxyService.onRotationProposal(rotation, isValid);
}
@Override
public void disable(int displayId, int state1, int state2, boolean animate) {
mDisabledFlags = state1;
updateSysuiFlags();
mOverviewProxyService.disable(displayId, state1, state2, animate);
}
@Override
public void onSystemBarAttributesChanged(int displayId, int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
InsetsVisibilities requestedVisibilities, String packageName,
LetterboxDetails[] letterboxDetails) {
mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
boolean nbModeChanged = false;
if (mAppearance != appearance) {
mAppearance = appearance;
nbModeChanged = updateTransitionMode(
transitionMode(mTaskbarTransientShowing, appearance));
}
if (displayId == mDisplayId) {
mLightBarController.onNavigationBarAppearanceChanged(appearance, nbModeChanged,
BarTransitions.MODE_TRANSPARENT, navbarColorManagedByIme);
}
if (mBehavior != behavior) {
mBehavior = behavior;
updateSysuiFlags();
}
}
@Override
public void showTransient(int displayId, int[] types, boolean isGestureOnSystemBar) {
if (displayId != mDisplayId) {
return;
}
if (!containsType(types, ITYPE_EXTRA_NAVIGATION_BAR)) {
return;
}
if (!mTaskbarTransientShowing) {
mTaskbarTransientShowing = true;
onTransientStateChanged();
}
}
@Override
public void abortTransient(int displayId, int[] types) {
if (displayId != mDisplayId) {
return;
}
if (!containsType(types, ITYPE_EXTRA_NAVIGATION_BAR)) {
return;
}
clearTransient();
}
@Override
public void onTaskbarAutohideSuspend(boolean suspend) {
if (suspend) {
mAutoHideController.suspendAutoHide();
} else {
mAutoHideController.resumeSuspendedAutoHide();
}
}
private void clearTransient() {
if (mTaskbarTransientShowing) {
mTaskbarTransientShowing = false;
onTransientStateChanged();
}
}
private void onTransientStateChanged() {
mEdgeBackGestureHandler.onNavBarTransientStateChanged(mTaskbarTransientShowing);
final int transitionMode = transitionMode(mTaskbarTransientShowing, mAppearance);
if (updateTransitionMode(transitionMode)) {
mLightBarController.onNavigationBarModeChanged(transitionMode);
}
}
private boolean updateTransitionMode(int barMode) {
if (mTransitionMode != barMode) {
mTransitionMode = barMode;
if (mAutoHideController != null) {
mAutoHideController.touchAutoHide();
}
return true;
}
return false;
}
@Override
public void onRecentsAnimationStateChanged(boolean running) {
}
@Override
public void onNavigationModeChanged(int mode) {
mNavigationMode = mode;
mEdgeBackGestureHandler.onNavigationModeChanged(mode);
}
private boolean isWindowVisible() {
return mTaskBarWindowState == WINDOW_STATE_SHOWING;
}
private boolean allowSystemGestureIgnoringBarVisibility() {
return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
}
private boolean isImmersiveMode() {
return mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
}
@Override
public void onConfigurationChanged(Configuration configuration) {
mEdgeBackGestureHandler.onConfigurationChanged(configuration);
}
@Override
public void onLowMemory() {}
@Override
public void showPinningEnterExitToast(boolean entering) {
updateSysuiFlags();
if (mScreenPinningNotify == null) {
return;
}
if (entering) {
mScreenPinningNotify.showPinningStartToast();
} else {
mScreenPinningNotify.showPinningExitToast();
}
}
@Override
public void showPinningEscapeToast() {
updateSysuiFlags();
if (mScreenPinningNotify == null) {
return;
}
mScreenPinningNotify.showEscapeToast(QuickStepContract.isGesturalMode(mNavigationMode),
!QuickStepContract.isGesturalMode(mNavigationMode));
}
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("TaskbarDelegate (displayId=" + mDisplayId + "):");
pw.println(" mNavigationIconHints=" + mNavigationIconHints);
pw.println(" mNavigationMode=" + mNavigationMode);
pw.println(" mDisabledFlags=" + mDisabledFlags);
pw.println(" mTaskBarWindowState=" + mTaskBarWindowState);
pw.println(" mBehavior=" + mBehavior);
pw.println(" mTaskbarTransientShowing=" + mTaskbarTransientShowing);
mEdgeBackGestureHandler.dump(pw);
}
}