1 /*
2  * Copyright 2021 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 
17 package com.android.systemui.navigationbar;
18 
19 import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
20 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
21 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
22 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
23 import static android.view.InsetsState.containsType;
24 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
25 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
26 
27 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
28 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
29 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
30 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
31 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
32 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
33 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
34 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
35 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
36 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
37 
38 import android.app.StatusBarManager;
39 import android.app.StatusBarManager.WindowVisibleState;
40 import android.content.ComponentCallbacks;
41 import android.content.Context;
42 import android.content.res.Configuration;
43 import android.graphics.Rect;
44 import android.hardware.display.DisplayManager;
45 import android.inputmethodservice.InputMethodService;
46 import android.os.IBinder;
47 import android.os.RemoteException;
48 import android.util.Log;
49 import android.view.Display;
50 import android.view.InsetsVisibilities;
51 import android.view.View;
52 import android.view.WindowInsetsController.Behavior;
53 
54 import androidx.annotation.NonNull;
55 
56 import com.android.internal.view.AppearanceRegion;
57 import com.android.systemui.Dependency;
58 import com.android.systemui.Dumpable;
59 import com.android.systemui.dump.DumpManager;
60 import com.android.systemui.model.SysUiState;
61 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
62 import com.android.systemui.recents.OverviewProxyService;
63 import com.android.systemui.shared.recents.utilities.Utilities;
64 import com.android.systemui.shared.system.ActivityManagerWrapper;
65 import com.android.systemui.shared.system.QuickStepContract;
66 import com.android.systemui.statusbar.AutoHideUiElement;
67 import com.android.systemui.statusbar.CommandQueue;
68 import com.android.systemui.statusbar.phone.AutoHideController;
69 import com.android.systemui.statusbar.phone.BarTransitions;
70 import com.android.systemui.statusbar.phone.LightBarController;
71 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
72 import com.android.wm.shell.pip.Pip;
73 
74 import java.io.FileDescriptor;
75 import java.io.PrintWriter;
76 import java.util.Optional;
77 import java.util.function.Consumer;
78 
79 import javax.inject.Inject;
80 import javax.inject.Singleton;
81 
82 @Singleton
83 public class TaskbarDelegate implements CommandQueue.Callbacks,
84         OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
85         ComponentCallbacks, Dumpable {
86     private static final String TAG = TaskbarDelegate.class.getSimpleName();
87 
88     private final EdgeBackGestureHandler mEdgeBackGestureHandler;
89     private boolean mInitialized;
90     private CommandQueue mCommandQueue;
91     private OverviewProxyService mOverviewProxyService;
92     private NavBarHelper mNavBarHelper;
93     private NavigationModeController mNavigationModeController;
94     private SysUiState mSysUiState;
95     private AutoHideController mAutoHideController;
96     private LightBarController mLightBarController;
97     private LightBarTransitionsController mLightBarTransitionsController;
98     private Optional<Pip> mPipOptional;
99     private int mDisplayId;
100     private int mNavigationIconHints;
101     private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater =
102             new NavBarHelper.NavbarTaskbarStateUpdater() {
103                 @Override
104                 public void updateAccessibilityServicesState() {
105                     updateSysuiFlags();
106                 }
107 
108                 @Override
109                 public void updateAssistantAvailable(boolean available) {
110                     updateAssistantAvailability(available);
111                 }
112             };
113     private int mDisabledFlags;
114     private @WindowVisibleState int mTaskBarWindowState = WINDOW_STATE_SHOWING;
115     private @Behavior int mBehavior;
116     private final Context mContext;
117     private final DisplayManager mDisplayManager;
118     private Context mWindowContext;
119     private ScreenPinningNotify mScreenPinningNotify;
120     private int mNavigationMode;
121     private final Consumer<Rect> mPipListener;
122 
123     /**
124      * Tracks the system calls for when taskbar should transiently show or hide so we can return
125      * this value in {@link AutoHideUiElement#isVisible()} below.
126      *
127      * This also gets set by {@link #onTaskbarAutohideSuspend(boolean)} to force show the transient
128      * taskbar if launcher has requested to suspend auto-hide behavior.
129      */
130     private boolean mTaskbarTransientShowing;
131     private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() {
132         @Override
133         public void synchronizeState() {
134         }
135 
136         @Override
137         public boolean isVisible() {
138             return mTaskbarTransientShowing;
139         }
140 
141         @Override
142         public void hide() {
143         }
144     };
145 
146     @Inject
TaskbarDelegate(Context context)147     public TaskbarDelegate(Context context) {
148         mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class)
149                 .create(context);
150         mContext = context;
151         mDisplayManager = mContext.getSystemService(DisplayManager.class);
152         mPipListener = mEdgeBackGestureHandler::setPipStashExclusionBounds;
153     }
154 
setDependencies(CommandQueue commandQueue, OverviewProxyService overviewProxyService, NavBarHelper navBarHelper, NavigationModeController navigationModeController, SysUiState sysUiState, DumpManager dumpManager, AutoHideController autoHideController, LightBarController lightBarController, Optional<Pip> pipOptional)155     public void setDependencies(CommandQueue commandQueue,
156             OverviewProxyService overviewProxyService,
157             NavBarHelper navBarHelper,
158             NavigationModeController navigationModeController,
159             SysUiState sysUiState, DumpManager dumpManager,
160             AutoHideController autoHideController,
161             LightBarController lightBarController,
162             Optional<Pip> pipOptional) {
163         // TODO: adding this in the ctor results in a dagger dependency cycle :(
164         mCommandQueue = commandQueue;
165         mOverviewProxyService = overviewProxyService;
166         mNavBarHelper = navBarHelper;
167         mNavigationModeController = navigationModeController;
168         mSysUiState = sysUiState;
169         dumpManager.registerDumpable(this);
170         mAutoHideController = autoHideController;
171         mLightBarController = lightBarController;
172         mLightBarTransitionsController = createLightBarTransitionsController();
173         mPipOptional = pipOptional;
174     }
175 
176     // Separated into a method to keep setDependencies() clean/readable.
createLightBarTransitionsController()177     private LightBarTransitionsController createLightBarTransitionsController() {
178         return new LightBarTransitionsController(mContext,
179                 new LightBarTransitionsController.DarkIntensityApplier() {
180                     @Override
181                     public void applyDarkIntensity(float darkIntensity) {
182                         mOverviewProxyService.onNavButtonsDarkIntensityChanged(darkIntensity);
183                     }
184 
185                     @Override
186                     public int getTintAnimationDuration() {
187                         return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION;
188                     }
189                 }, mCommandQueue) {
190             @Override
191             public boolean supportsIconTintForNavMode(int navigationMode) {
192                 // Always tint taskbar nav buttons (region sampling handles gesture bar separately).
193                 return true;
194             }
195         };
196     }
197 
198     public void init(int displayId) {
199         if (mInitialized) {
200             return;
201         }
202         mDisplayId = displayId;
203         mCommandQueue.addCallback(this);
204         mOverviewProxyService.addCallback(this);
205         mEdgeBackGestureHandler.onNavigationModeChanged(
206                 mNavigationModeController.addListener(this));
207         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
208         mNavBarHelper.init();
209         mEdgeBackGestureHandler.onNavBarAttached();
210         // Initialize component callback
211         Display display = mDisplayManager.getDisplay(displayId);
212         mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
213         mWindowContext.registerComponentCallbacks(this);
214         mScreenPinningNotify = new ScreenPinningNotify(mWindowContext);
215         // Set initial state for any listeners
216         updateSysuiFlags();
217         mAutoHideController.setNavigationBar(mAutoHideUiElement);
218         mLightBarController.setNavigationBar(mLightBarTransitionsController);
219         mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener);
220         mInitialized = true;
221     }
222 
223     public void destroy() {
224         if (!mInitialized) {
225             return;
226         }
227         mCommandQueue.removeCallback(this);
228         mOverviewProxyService.removeCallback(this);
229         mNavigationModeController.removeListener(this);
230         mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
231         mNavBarHelper.destroy();
232         mEdgeBackGestureHandler.onNavBarDetached();
233         mScreenPinningNotify = null;
234         if (mWindowContext != null) {
235             mWindowContext.unregisterComponentCallbacks(this);
236             mWindowContext = null;
237         }
238         mAutoHideController.setNavigationBar(null);
239         mLightBarTransitionsController.destroy(mContext);
240         mLightBarController.setNavigationBar(null);
241         mPipOptional.ifPresent(this::removePipExclusionBoundsChangeListener);
242         mInitialized = false;
243     }
244 
245     void addPipExclusionBoundsChangeListener(Pip pip) {
246         pip.addPipExclusionBoundsChangeListener(mPipListener);
247     }
248 
249     void removePipExclusionBoundsChangeListener(Pip pip) {
250         pip.removePipExclusionBoundsChangeListener(mPipListener);
251     }
252 
253     /**
254      * Returns {@code true} if this taskBar is {@link #init(int)}. Returns {@code false} if this
255      * taskbar has not yet been {@link #init(int)} or has been {@link #destroy()}.
256      */
257     public boolean isInitialized() {
258         return mInitialized;
259     }
260 
261     private void updateSysuiFlags() {
262         int a11yFlags = mNavBarHelper.getA11yButtonState();
263         boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
264         boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
265 
266         mSysUiState.setFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE, clickable)
267                 .setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable)
268                 .setFlag(SYSUI_STATE_IME_SHOWING,
269                         (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0)
270                 .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING,
271                         (mNavigationIconHints & NAVIGATION_HINT_IME_SHOWN) != 0)
272                 .setFlag(SYSUI_STATE_OVERVIEW_DISABLED,
273                         (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0)
274                 .setFlag(SYSUI_STATE_HOME_DISABLED,
275                         (mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0)
276                 .setFlag(SYSUI_STATE_BACK_DISABLED,
277                         (mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
278                 .setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isWindowVisible())
279                 .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
280                         allowSystemGestureIgnoringBarVisibility())
281                 .setFlag(SYSUI_STATE_SCREEN_PINNING,
282                         ActivityManagerWrapper.getInstance().isScreenPinningActive())
283                 .commitUpdate(mDisplayId);
284     }
285 
286     private void updateAssistantAvailability(boolean assistantAvailable) {
287         if (mOverviewProxyService.getProxy() == null) {
288             return;
289         }
290 
291         try {
292             mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable);
293         } catch (RemoteException e) {
294             Log.e(TAG, "onAssistantAvailable() failed, available: " + assistantAvailable, e);
295         }
296     }
297 
298     @Override
299     public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
300             boolean showImeSwitcher) {
301         boolean imeShown = mNavBarHelper.isImeShown(vis);
302         if (!imeShown) {
303             // Count imperceptible changes as visible so we transition taskbar out quickly.
304             imeShown = (vis & InputMethodService.IME_VISIBLE_IMPERCEPTIBLE) != 0;
305         }
306         showImeSwitcher = imeShown && showImeSwitcher;
307         int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition,
308                 imeShown, showImeSwitcher);
309         if (hints != mNavigationIconHints) {
310             mNavigationIconHints = hints;
311             updateSysuiFlags();
312         }
313     }
314 
315     @Override
316     public void setWindowState(int displayId, int window, int state) {
317         if (displayId == mDisplayId
318                 && window == StatusBarManager.WINDOW_NAVIGATION_BAR
319                 && mTaskBarWindowState != state) {
320             mTaskBarWindowState = state;
321             updateSysuiFlags();
322         }
323     }
324 
325     @Override
326     public void onRotationProposal(int rotation, boolean isValid) {
327         mOverviewProxyService.onRotationProposal(rotation, isValid);
328     }
329 
330     @Override
331     public void disable(int displayId, int state1, int state2, boolean animate) {
332         mDisabledFlags = state1;
333         updateSysuiFlags();
334         mOverviewProxyService.disable(displayId, state1, state2, animate);
335     }
336 
337     @Override
338     public void onSystemBarAttributesChanged(int displayId, int appearance,
339             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
340             InsetsVisibilities requestedVisibilities, String packageName) {
341         mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
342         if (mLightBarController != null && displayId == mDisplayId) {
343             mLightBarController.onNavigationBarAppearanceChanged(appearance, false/*nbModeChanged*/,
344                     BarTransitions.MODE_TRANSPARENT /*navigationBarMode*/, navbarColorManagedByIme);
345         }
346         if (mBehavior != behavior) {
347             mBehavior = behavior;
348             updateSysuiFlags();
349         }
350     }
351 
352     @Override
353     public void showTransient(int displayId, int[] types, boolean isGestureOnSystemBar) {
354         if (displayId != mDisplayId) {
355             return;
356         }
357         if (!containsType(types, ITYPE_NAVIGATION_BAR)) {
358             return;
359         }
360         mTaskbarTransientShowing = true;
361     }
362 
363     @Override
364     public void abortTransient(int displayId, int[] types) {
365         if (displayId != mDisplayId) {
366             return;
367         }
368         if (!containsType(types, ITYPE_NAVIGATION_BAR)) {
369             return;
370         }
371         mTaskbarTransientShowing = false;
372     }
373 
374     @Override
375     public void onTaskbarAutohideSuspend(boolean suspend) {
376         mTaskbarTransientShowing = suspend;
377         if (suspend) {
378             mAutoHideController.suspendAutoHide();
379         } else {
380             mAutoHideController.resumeSuspendedAutoHide();
381         }
382     }
383 
384     @Override
385     public void onNavigationModeChanged(int mode) {
386         mNavigationMode = mode;
387         mEdgeBackGestureHandler.onNavigationModeChanged(mode);
388     }
389 
390     private boolean isWindowVisible() {
391         return mTaskBarWindowState == WINDOW_STATE_SHOWING;
392     }
393 
394     private boolean allowSystemGestureIgnoringBarVisibility() {
395         return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
396     }
397 
398     @Override
399     public void onConfigurationChanged(Configuration configuration) {
400         mEdgeBackGestureHandler.onConfigurationChanged(configuration);
401     }
402 
403     @Override
404     public void onLowMemory() {}
405 
406     @Override
407     public void showPinningEnterExitToast(boolean entering) {
408         updateSysuiFlags();
409         if (mScreenPinningNotify == null) {
410             return;
411         }
412         if (entering) {
413             mScreenPinningNotify.showPinningStartToast();
414         } else {
415             mScreenPinningNotify.showPinningExitToast();
416         }
417     }
418 
419     @Override
420     public void showPinningEscapeToast() {
421         updateSysuiFlags();
422         if (mScreenPinningNotify == null) {
423             return;
424         }
425         mScreenPinningNotify.showEscapeToast(QuickStepContract.isGesturalMode(mNavigationMode),
426                 !QuickStepContract.isGesturalMode(mNavigationMode));
427     }
428 
429     @Override
430     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
431         pw.println("TaskbarDelegate (displayId=" + mDisplayId + "):");
432         pw.println("  mNavigationIconHints=" + mNavigationIconHints);
433         pw.println("  mNavigationMode=" + mNavigationMode);
434         pw.println("  mDisabledFlags=" + mDisabledFlags);
435         pw.println("  mTaskBarWindowState=" + mTaskBarWindowState);
436         pw.println("  mBehavior=" + mBehavior);
437         pw.println("  mTaskbarTransientShowing=" + mTaskbarTransientShowing);
438         mEdgeBackGestureHandler.dump(pw);
439     }
440 }
441