• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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