/* * 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.launcher3.statehandlers; import static android.view.View.VISIBLE; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity; import android.os.Debug; import android.os.SystemProperties; import android.util.Log; import android.view.View; import androidx.annotation.Nullable; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.DisplayController; import com.android.quickstep.GestureState; import com.android.quickstep.SystemUiProxy; import com.android.wm.shell.desktopmode.IDesktopTaskListener; import java.util.HashSet; import java.util.Set; /** * Controls the visibility of the workspace and the resumed / paused state when desktop mode * is enabled. */ public class DesktopVisibilityController { private static final String TAG = "DesktopVisController"; private static final boolean DEBUG = false; private final Launcher mLauncher; private final Set mDesktopVisibilityListeners = new HashSet<>(); private int mVisibleDesktopTasksCount; private boolean mInOverviewState; private boolean mBackgroundStateEnabled; private boolean mGestureInProgress; @Nullable private IDesktopTaskListener mDesktopTaskListener; public DesktopVisibilityController(Launcher launcher) { mLauncher = launcher; } /** * Register a listener with System UI to receive updates about desktop tasks state */ public void registerSystemUiListener() { mDesktopTaskListener = new IDesktopTaskListener.Stub() { @Override public void onTasksVisibilityChanged(int displayId, int visibleTasksCount) { MAIN_EXECUTOR.execute(() -> { if (displayId == mLauncher.getDisplayId()) { if (DEBUG) { Log.d(TAG, "desktop visible tasks count changed=" + visibleTasksCount); } setVisibleDesktopTasksCount(visibleTasksCount); } }); } @Override public void onStashedChanged(int displayId, boolean stashed) { Log.w(TAG, "IDesktopTaskListener: onStashedChanged is deprecated"); } }; SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(mDesktopTaskListener); } /** * Clear listener from System UI that was set with {@link #registerSystemUiListener()} */ public void unregisterSystemUiListener() { SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(null); mDesktopTaskListener = null; } /** * Whether desktop tasks are visible in desktop mode. */ public boolean areDesktopTasksVisible() { boolean desktopTasksVisible = mVisibleDesktopTasksCount > 0; if (DEBUG) { Log.d(TAG, "areDesktopTasksVisible: desktopVisible=" + desktopTasksVisible + " overview=" + mInOverviewState); } return desktopTasksVisible && !mInOverviewState; } /** * Number of visible desktop windows in desktop mode. */ public int getVisibleDesktopTasksCount() { return mVisibleDesktopTasksCount; } /** Registers a listener for Desktop Mode visibility updates. */ public void registerDesktopVisibilityListener(DesktopVisibilityListener listener) { mDesktopVisibilityListeners.add(listener); } /** Removes a previously registered Desktop Mode visibility listener. */ public void unregisterDesktopVisibilityListener(DesktopVisibilityListener listener) { mDesktopVisibilityListeners.remove(listener); } /** * Sets the number of desktop windows that are visible and updates launcher visibility based on * it. */ public void setVisibleDesktopTasksCount(int visibleTasksCount) { if (DEBUG) { Log.d(TAG, "setVisibleDesktopTasksCount: visibleTasksCount=" + visibleTasksCount + " currentValue=" + mVisibleDesktopTasksCount); } if (visibleTasksCount != mVisibleDesktopTasksCount) { final boolean wasVisible = mVisibleDesktopTasksCount > 0; final boolean isVisible = visibleTasksCount > 0; final boolean wereDesktopTasksVisibleBefore = areDesktopTasksVisible(); mVisibleDesktopTasksCount = visibleTasksCount; final boolean areDesktopTasksVisibleNow = areDesktopTasksVisible(); if (wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow) { notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow); } if (!enableDesktopWindowingWallpaperActivity() && wasVisible != isVisible) { // TODO: b/333533253 - Remove after flag rollout if (mVisibleDesktopTasksCount > 0) { setLauncherViewsVisibility(View.INVISIBLE); if (!mInOverviewState) { // When desktop tasks are visible & we're not in overview, we want launcher // to appear paused, this ensures that taskbar displays. markLauncherPaused(); } } else { setLauncherViewsVisibility(View.VISIBLE); // If desktop tasks aren't visible, ensure that launcher appears resumed to // behave normally. markLauncherResumed(); } } } } /** * Process launcher state change and update launcher view visibility based on desktop state */ public void onLauncherStateChanged(LauncherState state) { if (DEBUG) { Log.d(TAG, "onLauncherStateChanged: newState=" + state); } setBackgroundStateEnabled(state == BACKGROUND_APP); // Desktop visibility tracks overview and background state separately setOverviewStateEnabled(state != BACKGROUND_APP && state.isRecentsViewVisible); } private void setOverviewStateEnabled(boolean overviewStateEnabled) { if (DEBUG) { Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled + " currentValue=" + mInOverviewState); } if (overviewStateEnabled != mInOverviewState) { final boolean wereDesktopTasksVisibleBefore = areDesktopTasksVisible(); mInOverviewState = overviewStateEnabled; final boolean areDesktopTasksVisibleNow = areDesktopTasksVisible(); if (wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow) { notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow); } if (enableDesktopWindowingWallpaperActivity()) { return; } // TODO: b/333533253 - Clean up after flag rollout if (mInOverviewState) { setLauncherViewsVisibility(View.VISIBLE); markLauncherResumed(); } else if (areDesktopTasksVisibleNow && !mGestureInProgress) { // Switching out of overview state and gesture finished. // If desktop tasks are still visible, hide launcher again. setLauncherViewsVisibility(View.INVISIBLE); markLauncherPaused(); } } } private void notifyDesktopVisibilityListeners(boolean areDesktopTasksVisible) { if (DEBUG) { Log.d(TAG, "notifyDesktopVisibilityListeners: visible=" + areDesktopTasksVisible); } for (DesktopVisibilityListener listener : mDesktopVisibilityListeners) { listener.onDesktopVisibilityChanged(areDesktopTasksVisible); } DisplayController.handleInfoChangeForDesktopMode(mLauncher); } /** * TODO: b/333533253 - Remove after flag rollout */ private void setBackgroundStateEnabled(boolean backgroundStateEnabled) { if (DEBUG) { Log.d(TAG, "setBackgroundStateEnabled: enabled=" + backgroundStateEnabled + " currentValue=" + mBackgroundStateEnabled); } if (backgroundStateEnabled != mBackgroundStateEnabled) { mBackgroundStateEnabled = backgroundStateEnabled; if (mBackgroundStateEnabled) { setLauncherViewsVisibility(View.VISIBLE); markLauncherResumed(); } else if (areDesktopTasksVisible() && !mGestureInProgress) { // Switching out of background state. If desktop tasks are visible, pause launcher. setLauncherViewsVisibility(View.INVISIBLE); markLauncherPaused(); } } } /** * Whether recents gesture is currently in progress. * * TODO: b/333533253 - Remove after flag rollout */ public boolean isRecentsGestureInProgress() { return mGestureInProgress; } /** * Notify controller that recents gesture has started. * * TODO: b/333533253 - Remove after flag rollout */ public void setRecentsGestureStart() { if (DEBUG) { Log.d(TAG, "setRecentsGestureStart"); } setRecentsGestureInProgress(true); } /** * Notify controller that recents gesture finished with the given * {@link com.android.quickstep.GestureState.GestureEndTarget} * * TODO: b/333533253 - Remove after flag rollout */ public void setRecentsGestureEnd(@Nullable GestureState.GestureEndTarget endTarget) { if (DEBUG) { Log.d(TAG, "setRecentsGestureEnd: endTarget=" + endTarget); } setRecentsGestureInProgress(false); if (endTarget == null) { // Gesture did not result in a new end target. Ensure launchers gets paused again. markLauncherPaused(); } } /** * TODO: b/333533253 - Remove after flag rollout */ private void setRecentsGestureInProgress(boolean gestureInProgress) { if (gestureInProgress != mGestureInProgress) { mGestureInProgress = gestureInProgress; } } /** * TODO: b/333533253 - Remove after flag rollout */ private void setLauncherViewsVisibility(int visibility) { if (enableDesktopWindowingWallpaperActivity()) { return; } if (DEBUG) { Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility + " " + Debug.getCaller()); } View workspaceView = mLauncher.getWorkspace(); if (workspaceView != null) { workspaceView.setVisibility(visibility); } View dragLayer = mLauncher.getDragLayer(); if (dragLayer != null) { dragLayer.setVisibility(visibility); } if (mLauncher instanceof QuickstepLauncher ql && ql.getTaskbarUIController() != null && mVisibleDesktopTasksCount != 0) { ql.getTaskbarUIController().onLauncherVisibilityChanged(visibility == VISIBLE); } } /** * TODO: b/333533253 - Remove after flag rollout */ private void markLauncherPaused() { if (enableDesktopWindowingWallpaperActivity()) { return; } if (DEBUG) { Log.d(TAG, "markLauncherPaused " + Debug.getCaller()); } StatefulActivity activity = QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); if (activity != null) { activity.setPaused(); } } /** * TODO: b/333533253 - Remove after flag rollout */ private void markLauncherResumed() { if (enableDesktopWindowingWallpaperActivity()) { return; } if (DEBUG) { Log.d(TAG, "markLauncherResumed " + Debug.getCaller()); } StatefulActivity activity = QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); // Check activity state before calling setResumed(). Launcher may have been actually // paused (eg fullscreen task moved to front). // In this case we should not mark the activity as resumed. if (activity != null && activity.isResumed()) { activity.setResumed(); } } /** A listener for when the user enters/exits Desktop Mode. */ public interface DesktopVisibilityListener { /** * Callback for when the user enters or exits Desktop Mode * * @param visible whether Desktop Mode is now visible */ void onDesktopVisibilityChanged(boolean visible); } }