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 android.view.View.VISIBLE; 19 20 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 21 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 22 import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity; 23 24 import android.os.Debug; 25 import android.os.SystemProperties; 26 import android.util.Log; 27 import android.view.View; 28 29 import androidx.annotation.Nullable; 30 31 import com.android.launcher3.Launcher; 32 import com.android.launcher3.LauncherState; 33 import com.android.launcher3.statemanager.StatefulActivity; 34 import com.android.launcher3.uioverrides.QuickstepLauncher; 35 import com.android.launcher3.util.DisplayController; 36 import com.android.quickstep.GestureState; 37 import com.android.quickstep.SystemUiProxy; 38 import com.android.wm.shell.desktopmode.IDesktopTaskListener; 39 40 import java.util.HashSet; 41 import java.util.Set; 42 43 /** 44 * Controls the visibility of the workspace and the resumed / paused state when desktop mode 45 * is enabled. 46 */ 47 public class DesktopVisibilityController { 48 49 private static final String TAG = "DesktopVisController"; 50 private static final boolean DEBUG = false; 51 private final Launcher mLauncher; 52 private final Set<DesktopVisibilityListener> mDesktopVisibilityListeners = new HashSet<>(); 53 54 private int mVisibleDesktopTasksCount; 55 private boolean mInOverviewState; 56 private boolean mBackgroundStateEnabled; 57 private boolean mGestureInProgress; 58 59 @Nullable 60 private IDesktopTaskListener mDesktopTaskListener; 61 DesktopVisibilityController(Launcher launcher)62 public DesktopVisibilityController(Launcher launcher) { 63 mLauncher = launcher; 64 } 65 66 /** 67 * Register a listener with System UI to receive updates about desktop tasks state 68 */ registerSystemUiListener()69 public void registerSystemUiListener() { 70 mDesktopTaskListener = new IDesktopTaskListener.Stub() { 71 @Override 72 public void onTasksVisibilityChanged(int displayId, int visibleTasksCount) { 73 MAIN_EXECUTOR.execute(() -> { 74 if (displayId == mLauncher.getDisplayId()) { 75 if (DEBUG) { 76 Log.d(TAG, "desktop visible tasks count changed=" + visibleTasksCount); 77 } 78 setVisibleDesktopTasksCount(visibleTasksCount); 79 } 80 }); 81 } 82 83 @Override 84 public void onStashedChanged(int displayId, boolean stashed) { 85 Log.w(TAG, "IDesktopTaskListener: onStashedChanged is deprecated"); 86 } 87 }; 88 SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(mDesktopTaskListener); 89 } 90 91 /** 92 * Clear listener from System UI that was set with {@link #registerSystemUiListener()} 93 */ unregisterSystemUiListener()94 public void unregisterSystemUiListener() { 95 SystemUiProxy.INSTANCE.get(mLauncher).setDesktopTaskListener(null); 96 mDesktopTaskListener = null; 97 } 98 99 /** 100 * Whether desktop tasks are visible in desktop mode. 101 */ areDesktopTasksVisible()102 public boolean areDesktopTasksVisible() { 103 boolean desktopTasksVisible = mVisibleDesktopTasksCount > 0; 104 if (DEBUG) { 105 Log.d(TAG, "areDesktopTasksVisible: desktopVisible=" + desktopTasksVisible 106 + " overview=" + mInOverviewState); 107 } 108 return desktopTasksVisible && !mInOverviewState; 109 } 110 111 /** 112 * Number of visible desktop windows in desktop mode. 113 */ getVisibleDesktopTasksCount()114 public int getVisibleDesktopTasksCount() { 115 return mVisibleDesktopTasksCount; 116 } 117 118 /** Registers a listener for Desktop Mode visibility updates. */ registerDesktopVisibilityListener(DesktopVisibilityListener listener)119 public void registerDesktopVisibilityListener(DesktopVisibilityListener listener) { 120 mDesktopVisibilityListeners.add(listener); 121 } 122 123 /** Removes a previously registered Desktop Mode visibility listener. */ unregisterDesktopVisibilityListener(DesktopVisibilityListener listener)124 public void unregisterDesktopVisibilityListener(DesktopVisibilityListener listener) { 125 mDesktopVisibilityListeners.remove(listener); 126 } 127 128 /** 129 * Sets the number of desktop windows that are visible and updates launcher visibility based on 130 * it. 131 */ setVisibleDesktopTasksCount(int visibleTasksCount)132 public void setVisibleDesktopTasksCount(int visibleTasksCount) { 133 if (DEBUG) { 134 Log.d(TAG, "setVisibleDesktopTasksCount: visibleTasksCount=" + visibleTasksCount 135 + " currentValue=" + mVisibleDesktopTasksCount); 136 } 137 138 if (visibleTasksCount != mVisibleDesktopTasksCount) { 139 final boolean wasVisible = mVisibleDesktopTasksCount > 0; 140 final boolean isVisible = visibleTasksCount > 0; 141 final boolean wereDesktopTasksVisibleBefore = areDesktopTasksVisible(); 142 mVisibleDesktopTasksCount = visibleTasksCount; 143 final boolean areDesktopTasksVisibleNow = areDesktopTasksVisible(); 144 if (wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow) { 145 notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow); 146 } 147 148 if (!enableDesktopWindowingWallpaperActivity() && wasVisible != isVisible) { 149 // TODO: b/333533253 - Remove after flag rollout 150 if (mVisibleDesktopTasksCount > 0) { 151 setLauncherViewsVisibility(View.INVISIBLE); 152 if (!mInOverviewState) { 153 // When desktop tasks are visible & we're not in overview, we want launcher 154 // to appear paused, this ensures that taskbar displays. 155 markLauncherPaused(); 156 } 157 } else { 158 setLauncherViewsVisibility(View.VISIBLE); 159 // If desktop tasks aren't visible, ensure that launcher appears resumed to 160 // behave normally. 161 markLauncherResumed(); 162 } 163 } 164 } 165 } 166 167 /** 168 * Process launcher state change and update launcher view visibility based on desktop state 169 */ onLauncherStateChanged(LauncherState state)170 public void onLauncherStateChanged(LauncherState state) { 171 if (DEBUG) { 172 Log.d(TAG, "onLauncherStateChanged: newState=" + state); 173 } 174 setBackgroundStateEnabled(state == BACKGROUND_APP); 175 // Desktop visibility tracks overview and background state separately 176 setOverviewStateEnabled(state != BACKGROUND_APP && state.isRecentsViewVisible); 177 } 178 setOverviewStateEnabled(boolean overviewStateEnabled)179 private void setOverviewStateEnabled(boolean overviewStateEnabled) { 180 if (DEBUG) { 181 Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled 182 + " currentValue=" + mInOverviewState); 183 } 184 if (overviewStateEnabled != mInOverviewState) { 185 final boolean wereDesktopTasksVisibleBefore = areDesktopTasksVisible(); 186 mInOverviewState = overviewStateEnabled; 187 final boolean areDesktopTasksVisibleNow = areDesktopTasksVisible(); 188 if (wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow) { 189 notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow); 190 } 191 192 if (enableDesktopWindowingWallpaperActivity()) { 193 return; 194 } 195 // TODO: b/333533253 - Clean up after flag rollout 196 197 if (mInOverviewState) { 198 setLauncherViewsVisibility(View.VISIBLE); 199 markLauncherResumed(); 200 } else if (areDesktopTasksVisibleNow && !mGestureInProgress) { 201 // Switching out of overview state and gesture finished. 202 // If desktop tasks are still visible, hide launcher again. 203 setLauncherViewsVisibility(View.INVISIBLE); 204 markLauncherPaused(); 205 } 206 } 207 } 208 notifyDesktopVisibilityListeners(boolean areDesktopTasksVisible)209 private void notifyDesktopVisibilityListeners(boolean areDesktopTasksVisible) { 210 if (DEBUG) { 211 Log.d(TAG, "notifyDesktopVisibilityListeners: visible=" + areDesktopTasksVisible); 212 } 213 for (DesktopVisibilityListener listener : mDesktopVisibilityListeners) { 214 listener.onDesktopVisibilityChanged(areDesktopTasksVisible); 215 } 216 DisplayController.handleInfoChangeForDesktopMode(mLauncher); 217 } 218 219 /** 220 * TODO: b/333533253 - Remove after flag rollout 221 */ setBackgroundStateEnabled(boolean backgroundStateEnabled)222 private void setBackgroundStateEnabled(boolean backgroundStateEnabled) { 223 if (DEBUG) { 224 Log.d(TAG, "setBackgroundStateEnabled: enabled=" + backgroundStateEnabled 225 + " currentValue=" + mBackgroundStateEnabled); 226 } 227 if (backgroundStateEnabled != mBackgroundStateEnabled) { 228 mBackgroundStateEnabled = backgroundStateEnabled; 229 if (mBackgroundStateEnabled) { 230 setLauncherViewsVisibility(View.VISIBLE); 231 markLauncherResumed(); 232 } else if (areDesktopTasksVisible() && !mGestureInProgress) { 233 // Switching out of background state. If desktop tasks are visible, pause launcher. 234 setLauncherViewsVisibility(View.INVISIBLE); 235 markLauncherPaused(); 236 } 237 } 238 } 239 240 /** 241 * Whether recents gesture is currently in progress. 242 * 243 * TODO: b/333533253 - Remove after flag rollout 244 */ isRecentsGestureInProgress()245 public boolean isRecentsGestureInProgress() { 246 return mGestureInProgress; 247 } 248 249 /** 250 * Notify controller that recents gesture has started. 251 * 252 * TODO: b/333533253 - Remove after flag rollout 253 */ setRecentsGestureStart()254 public void setRecentsGestureStart() { 255 if (DEBUG) { 256 Log.d(TAG, "setRecentsGestureStart"); 257 } 258 setRecentsGestureInProgress(true); 259 } 260 261 /** 262 * Notify controller that recents gesture finished with the given 263 * {@link com.android.quickstep.GestureState.GestureEndTarget} 264 * 265 * TODO: b/333533253 - Remove after flag rollout 266 */ setRecentsGestureEnd(@ullable GestureState.GestureEndTarget endTarget)267 public void setRecentsGestureEnd(@Nullable GestureState.GestureEndTarget endTarget) { 268 if (DEBUG) { 269 Log.d(TAG, "setRecentsGestureEnd: endTarget=" + endTarget); 270 } 271 setRecentsGestureInProgress(false); 272 273 if (endTarget == null) { 274 // Gesture did not result in a new end target. Ensure launchers gets paused again. 275 markLauncherPaused(); 276 } 277 } 278 279 /** 280 * TODO: b/333533253 - Remove after flag rollout 281 */ setRecentsGestureInProgress(boolean gestureInProgress)282 private void setRecentsGestureInProgress(boolean gestureInProgress) { 283 if (gestureInProgress != mGestureInProgress) { 284 mGestureInProgress = gestureInProgress; 285 } 286 } 287 288 /** 289 * TODO: b/333533253 - Remove after flag rollout 290 */ setLauncherViewsVisibility(int visibility)291 private void setLauncherViewsVisibility(int visibility) { 292 if (enableDesktopWindowingWallpaperActivity()) { 293 return; 294 } 295 if (DEBUG) { 296 Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility + " " 297 + Debug.getCaller()); 298 } 299 View workspaceView = mLauncher.getWorkspace(); 300 if (workspaceView != null) { 301 workspaceView.setVisibility(visibility); 302 } 303 View dragLayer = mLauncher.getDragLayer(); 304 if (dragLayer != null) { 305 dragLayer.setVisibility(visibility); 306 } 307 if (mLauncher instanceof QuickstepLauncher ql && ql.getTaskbarUIController() != null 308 && mVisibleDesktopTasksCount != 0) { 309 ql.getTaskbarUIController().onLauncherVisibilityChanged(visibility == VISIBLE); 310 } 311 } 312 313 /** 314 * TODO: b/333533253 - Remove after flag rollout 315 */ markLauncherPaused()316 private void markLauncherPaused() { 317 if (enableDesktopWindowingWallpaperActivity()) { 318 return; 319 } 320 if (DEBUG) { 321 Log.d(TAG, "markLauncherPaused " + Debug.getCaller()); 322 } 323 StatefulActivity<LauncherState> activity = 324 QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); 325 if (activity != null) { 326 activity.setPaused(); 327 } 328 } 329 330 /** 331 * TODO: b/333533253 - Remove after flag rollout 332 */ markLauncherResumed()333 private void markLauncherResumed() { 334 if (enableDesktopWindowingWallpaperActivity()) { 335 return; 336 } 337 if (DEBUG) { 338 Log.d(TAG, "markLauncherResumed " + Debug.getCaller()); 339 } 340 StatefulActivity<LauncherState> activity = 341 QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); 342 // Check activity state before calling setResumed(). Launcher may have been actually 343 // paused (eg fullscreen task moved to front). 344 // In this case we should not mark the activity as resumed. 345 if (activity != null && activity.isResumed()) { 346 activity.setResumed(); 347 } 348 } 349 350 /** A listener for when the user enters/exits Desktop Mode. */ 351 public interface DesktopVisibilityListener { 352 /** 353 * Callback for when the user enters or exits Desktop Mode 354 * 355 * @param visible whether Desktop Mode is now visible 356 */ onDesktopVisibilityChanged(boolean visible)357 void onDesktopVisibilityChanged(boolean visible); 358 } 359 } 360