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