1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.statehandlers; 17 18 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 19 20 import android.os.SystemProperties; 21 import android.util.Log; 22 import android.view.View; 23 24 import androidx.annotation.Nullable; 25 26 import com.android.launcher3.Launcher; 27 import com.android.launcher3.LauncherState; 28 import com.android.launcher3.statemanager.StatefulActivity; 29 import com.android.launcher3.uioverrides.QuickstepLauncher; 30 import com.android.quickstep.GestureState; 31 import com.android.quickstep.SystemUiProxy; 32 import com.android.quickstep.views.DesktopAppSelectView; 33 import com.android.wm.shell.desktopmode.IDesktopTaskListener; 34 35 /** 36 * Controls the visibility of the workspace and the resumed / paused state when desktop mode 37 * is enabled. 38 */ 39 public class DesktopVisibilityController { 40 41 private static final String TAG = "DesktopVisController"; 42 private static final boolean DEBUG = false; 43 private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean( 44 "persist.wm.debug.desktop_stashing", false); 45 private final Launcher mLauncher; 46 47 private boolean mFreeformTasksVisible; 48 private boolean mInOverviewState; 49 private boolean mGestureInProgress; 50 51 @Nullable 52 private IDesktopTaskListener mDesktopTaskListener; 53 private DesktopAppSelectView mSelectAppToast; 54 DesktopVisibilityController(Launcher launcher)55 public DesktopVisibilityController(Launcher launcher) { 56 mLauncher = launcher; 57 } 58 59 /** 60 * Register a listener with System UI to receive updates about desktop tasks state 61 */ registerSystemUiListener()62 public void registerSystemUiListener() { 63 mDesktopTaskListener = new IDesktopTaskListener.Stub() { 64 @Override 65 public void onVisibilityChanged(int displayId, boolean visible) { 66 MAIN_EXECUTOR.execute(() -> { 67 if (displayId == mLauncher.getDisplayId()) { 68 if (DEBUG) { 69 Log.d(TAG, "desktop visibility changed value=" + visible); 70 } 71 setFreeformTasksVisible(visible); 72 } 73 }); 74 } 75 76 @Override 77 public void onStashedChanged(int displayId, boolean stashed) { 78 if (!IS_STASHING_ENABLED) { 79 return; 80 } 81 MAIN_EXECUTOR.execute(() -> { 82 if (displayId == mLauncher.getDisplayId()) { 83 if (DEBUG) { 84 Log.d(TAG, "desktop stashed changed value=" + stashed); 85 } 86 if (stashed) { 87 showSelectAppToast(); 88 } else { 89 hideSelectAppToast(); 90 } 91 } 92 }); 93 } 94 }; 95 SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(mDesktopTaskListener); 96 } 97 98 /** 99 * Clear listener from System UI that was set with {@link #registerSystemUiListener()} 100 */ unregisterSystemUiListener()101 public void unregisterSystemUiListener() { 102 SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(null); 103 } 104 105 /** 106 * Whether desktop mode is supported. 107 */ isDesktopModeSupported()108 private boolean isDesktopModeSupported() { 109 return SystemProperties.getBoolean("persist.wm.debug.desktop_mode", false) 110 || SystemProperties.getBoolean("persist.wm.debug.desktop_mode_2", false); 111 } 112 113 /** 114 * Whether freeform windows are visible in desktop mode. 115 */ areFreeformTasksVisible()116 public boolean areFreeformTasksVisible() { 117 return mFreeformTasksVisible; 118 } 119 120 /** 121 * Sets whether freeform windows are visible and updates launcher visibility based on that. 122 */ setFreeformTasksVisible(boolean freeformTasksVisible)123 public void setFreeformTasksVisible(boolean freeformTasksVisible) { 124 if (DEBUG) { 125 Log.d(TAG, "setFreeformTasksVisible: visible=" + freeformTasksVisible); 126 } 127 if (!isDesktopModeSupported()) { 128 return; 129 } 130 131 if (freeformTasksVisible != mFreeformTasksVisible) { 132 mFreeformTasksVisible = freeformTasksVisible; 133 if (mFreeformTasksVisible) { 134 setLauncherViewsVisibility(View.INVISIBLE); 135 if (!mInOverviewState) { 136 // When freeform is visible & we're not in overview, we want launcher to appear 137 // paused, this ensures that taskbar displays. 138 markLauncherPaused(); 139 } 140 } else { 141 setLauncherViewsVisibility(View.VISIBLE); 142 // If freeform isn't visible ensure that launcher appears resumed to behave 143 // normally. 144 markLauncherResumed(); 145 } 146 } 147 } 148 149 /** 150 * Sets whether the overview is visible and updates launcher visibility based on that. 151 */ setOverviewStateEnabled(boolean overviewStateEnabled)152 public void setOverviewStateEnabled(boolean overviewStateEnabled) { 153 if (DEBUG) { 154 Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled); 155 } 156 if (!isDesktopModeSupported()) { 157 return; 158 } 159 if (overviewStateEnabled != mInOverviewState) { 160 mInOverviewState = overviewStateEnabled; 161 if (mInOverviewState) { 162 setLauncherViewsVisibility(View.VISIBLE); 163 markLauncherResumed(); 164 } else if (mFreeformTasksVisible) { 165 setLauncherViewsVisibility(View.INVISIBLE); 166 markLauncherPaused(); 167 } 168 } 169 } 170 171 /** 172 * Whether recents gesture is currently in progress. 173 */ isRecentsGestureInProgress()174 public boolean isRecentsGestureInProgress() { 175 return mGestureInProgress; 176 } 177 178 /** 179 * Notify controller that recents gesture has started. 180 */ setRecentsGestureStart()181 public void setRecentsGestureStart() { 182 if (!isDesktopModeSupported()) { 183 return; 184 } 185 setRecentsGestureInProgress(true); 186 } 187 188 /** 189 * Notify controller that recents gesture finished with the given 190 * {@link com.android.quickstep.GestureState.GestureEndTarget} 191 */ setRecentsGestureEnd(@ullable GestureState.GestureEndTarget endTarget)192 public void setRecentsGestureEnd(@Nullable GestureState.GestureEndTarget endTarget) { 193 if (!isDesktopModeSupported()) { 194 return; 195 } 196 setRecentsGestureInProgress(false); 197 198 if (endTarget == null) { 199 // Gesture did not result in a new end target. Ensure launchers gets paused again. 200 markLauncherPaused(); 201 } 202 } 203 setRecentsGestureInProgress(boolean gestureInProgress)204 private void setRecentsGestureInProgress(boolean gestureInProgress) { 205 if (DEBUG) { 206 Log.d(TAG, "setGestureInProgress: inProgress=" + gestureInProgress); 207 } 208 if (gestureInProgress != mGestureInProgress) { 209 mGestureInProgress = gestureInProgress; 210 } 211 } 212 213 /** 214 * Handle launcher moving to home due to home gesture or home button press. 215 */ onHomeActionTriggered()216 public void onHomeActionTriggered() { 217 if (IS_STASHING_ENABLED && areFreeformTasksVisible()) { 218 SystemUiProxy.INSTANCE.get(mLauncher).stashDesktopApps(mLauncher.getDisplayId()); 219 } 220 } 221 setLauncherViewsVisibility(int visibility)222 private void setLauncherViewsVisibility(int visibility) { 223 if (DEBUG) { 224 Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility); 225 } 226 View workspaceView = mLauncher.getWorkspace(); 227 if (workspaceView != null) { 228 workspaceView.setVisibility(visibility); 229 } 230 View dragLayer = mLauncher.getDragLayer(); 231 if (dragLayer != null) { 232 dragLayer.setVisibility(visibility); 233 } 234 } 235 markLauncherPaused()236 private void markLauncherPaused() { 237 if (DEBUG) { 238 Log.d(TAG, "markLauncherPaused"); 239 } 240 StatefulActivity<LauncherState> activity = 241 QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); 242 if (activity != null) { 243 activity.setPaused(); 244 } 245 } 246 markLauncherResumed()247 private void markLauncherResumed() { 248 if (DEBUG) { 249 Log.d(TAG, "markLauncherResumed"); 250 } 251 StatefulActivity<LauncherState> activity = 252 QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); 253 // Check activity state before calling setResumed(). Launcher may have been actually 254 // paused (eg fullscreen task moved to front). 255 // In this case we should not mark the activity as resumed. 256 if (activity != null && activity.isResumed()) { 257 activity.setResumed(); 258 } 259 } 260 showSelectAppToast()261 private void showSelectAppToast() { 262 if (mSelectAppToast != null) { 263 return; 264 } 265 if (DEBUG) { 266 Log.d(TAG, "show toast to select desktop apps"); 267 } 268 Runnable onCloseCallback = () -> { 269 SystemUiProxy.INSTANCE.get(mLauncher).hideStashedDesktopApps(mLauncher.getDisplayId()); 270 }; 271 mSelectAppToast = DesktopAppSelectView.show(mLauncher, onCloseCallback); 272 } 273 hideSelectAppToast()274 private void hideSelectAppToast() { 275 if (mSelectAppToast == null) { 276 return; 277 } 278 if (DEBUG) { 279 Log.d(TAG, "hide toast to select desktop apps"); 280 } 281 mSelectAppToast.hide(); 282 mSelectAppToast = null; 283 } 284 } 285