• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
20 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
21 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
22 
23 import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
24 import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
25 
26 import android.content.Context;
27 import android.content.pm.ActivityInfo;
28 import android.content.res.Configuration;
29 import android.hardware.display.DisplayManager;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.RemoteException;
33 import android.os.Trace;
34 import android.os.UserHandle;
35 import android.provider.Settings;
36 import android.util.Log;
37 import android.util.SparseArray;
38 import android.view.Display;
39 import android.view.IWindowManager;
40 import android.view.View;
41 import android.view.WindowManagerGlobal;
42 
43 import androidx.annotation.NonNull;
44 import androidx.annotation.Nullable;
45 
46 import com.android.internal.annotations.VisibleForTesting;
47 import com.android.internal.statusbar.RegisterStatusBarResult;
48 import com.android.settingslib.applications.InterestingConfigChanges;
49 import com.android.systemui.Dumpable;
50 import com.android.systemui.dagger.SysUISingleton;
51 import com.android.systemui.dagger.qualifiers.Main;
52 import com.android.systemui.dump.DumpManager;
53 import com.android.systemui.flags.FeatureFlags;
54 import com.android.systemui.flags.Flags;
55 import com.android.systemui.model.SysUiState;
56 import com.android.systemui.recents.OverviewProxyService;
57 import com.android.systemui.settings.DisplayTracker;
58 import com.android.systemui.shared.system.QuickStepContract;
59 import com.android.systemui.shared.system.TaskStackChangeListeners;
60 import com.android.systemui.statusbar.CommandQueue;
61 import com.android.systemui.statusbar.CommandQueue.Callbacks;
62 import com.android.systemui.statusbar.phone.AutoHideController;
63 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
64 import com.android.systemui.statusbar.phone.LightBarController;
65 import com.android.systemui.statusbar.policy.ConfigurationController;
66 import com.android.systemui.util.settings.SecureSettings;
67 import com.android.wm.shell.back.BackAnimation;
68 import com.android.wm.shell.pip.Pip;
69 
70 import java.io.PrintWriter;
71 import java.util.Optional;
72 
73 import javax.inject.Inject;
74 
75 /** A controller to handle navigation bars. */
76 @SysUISingleton
77 public class NavigationBarController implements
78         Callbacks,
79         ConfigurationController.ConfigurationListener,
80         NavigationModeController.ModeChangedListener,
81         Dumpable {
82 
83     private static final String TAG = NavigationBarController.class.getSimpleName();
84 
85     private final Context mContext;
86     private final Handler mHandler;
87     private final NavigationBarComponent.Factory mNavigationBarComponentFactory;
88     private FeatureFlags mFeatureFlags;
89     private final SecureSettings mSecureSettings;
90     private final DisplayTracker mDisplayTracker;
91     private final DisplayManager mDisplayManager;
92     private final TaskbarDelegate mTaskbarDelegate;
93     private final NavBarHelper mNavBarHelper;
94     private int mNavMode;
95     @VisibleForTesting boolean mIsLargeScreen;
96 
97     /** A displayId - nav bar maps. */
98     @VisibleForTesting
99     SparseArray<NavigationBar> mNavigationBars = new SparseArray<>();
100 
101     // Tracks config changes that will actually recreate the nav bar
102     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
103             ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_SCREEN_LAYOUT
104                     | ActivityInfo.CONFIG_UI_MODE);
105 
106     @Inject
NavigationBarController(Context context, OverviewProxyService overviewProxyService, NavigationModeController navigationModeController, SysUiState sysUiFlagsContainer, CommandQueue commandQueue, @Main Handler mainHandler, ConfigurationController configurationController, NavBarHelper navBarHelper, TaskbarDelegate taskbarDelegate, NavigationBarComponent.Factory navigationBarComponentFactory, DumpManager dumpManager, AutoHideController autoHideController, LightBarController lightBarController, TaskStackChangeListeners taskStackChangeListeners, Optional<Pip> pipOptional, Optional<BackAnimation> backAnimation, FeatureFlags featureFlags, SecureSettings secureSettings, DisplayTracker displayTracker)107     public NavigationBarController(Context context,
108             OverviewProxyService overviewProxyService,
109             NavigationModeController navigationModeController,
110             SysUiState sysUiFlagsContainer,
111             CommandQueue commandQueue,
112             @Main Handler mainHandler,
113             ConfigurationController configurationController,
114             NavBarHelper navBarHelper,
115             TaskbarDelegate taskbarDelegate,
116             NavigationBarComponent.Factory navigationBarComponentFactory,
117             DumpManager dumpManager,
118             AutoHideController autoHideController,
119             LightBarController lightBarController,
120             TaskStackChangeListeners taskStackChangeListeners,
121             Optional<Pip> pipOptional,
122             Optional<BackAnimation> backAnimation,
123             FeatureFlags featureFlags,
124             SecureSettings secureSettings,
125             DisplayTracker displayTracker) {
126         mContext = context;
127         mHandler = mainHandler;
128         mNavigationBarComponentFactory = navigationBarComponentFactory;
129         mFeatureFlags = featureFlags;
130         mSecureSettings = secureSettings;
131         mDisplayTracker = displayTracker;
132         mDisplayManager = mContext.getSystemService(DisplayManager.class);
133         commandQueue.addCallback(this);
134         configurationController.addCallback(this);
135         mConfigChanges.applyNewConfig(mContext.getResources());
136         mNavMode = navigationModeController.addListener(this);
137         mNavBarHelper = navBarHelper;
138         mTaskbarDelegate = taskbarDelegate;
139         mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
140                 navBarHelper, navigationModeController, sysUiFlagsContainer,
141                 dumpManager, autoHideController, lightBarController, pipOptional,
142                 backAnimation.orElse(null), taskStackChangeListeners);
143         mIsLargeScreen = isLargeScreen(mContext);
144         dumpManager.registerDumpable(this);
145     }
146 
147     @Override
onConfigChanged(Configuration newConfig)148     public void onConfigChanged(Configuration newConfig) {
149         boolean isOldConfigLargeScreen = mIsLargeScreen;
150         mIsLargeScreen = isLargeScreen(mContext);
151         boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources());
152         boolean largeScreenChanged = mIsLargeScreen != isOldConfigLargeScreen;
153         // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
154         Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
155                 + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized()
156                 + " willApplyConfigToNavbars=" + willApplyConfig
157                 + " navBarCount=" + mNavigationBars.size());
158         if (mTaskbarDelegate.isInitialized()) {
159             mTaskbarDelegate.onConfigurationChanged(newConfig);
160         }
161         // If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
162         if (largeScreenChanged && updateNavbarForTaskbar()) {
163             return;
164         }
165 
166         if (willApplyConfig) {
167             for (int i = 0; i < mNavigationBars.size(); i++) {
168                 recreateNavigationBar(mNavigationBars.keyAt(i));
169             }
170         } else {
171             for (int i = 0; i < mNavigationBars.size(); i++) {
172                 mNavigationBars.valueAt(i).onConfigurationChanged(newConfig);
173             }
174         }
175     }
176 
177     @Override
onNavigationModeChanged(int mode)178     public void onNavigationModeChanged(int mode) {
179         if (mNavMode == mode) {
180             return;
181         }
182         final int oldMode = mNavMode;
183         mNavMode = mode;
184         updateAccessibilityButtonModeIfNeeded();
185 
186         mHandler.post(() -> {
187             // create/destroy nav bar based on nav mode only in unfolded state
188             if (oldMode != mNavMode) {
189                 updateNavbarForTaskbar();
190             }
191             for (int i = 0; i < mNavigationBars.size(); i++) {
192                 NavigationBar navBar = mNavigationBars.valueAt(i);
193                 if (navBar == null) {
194                     continue;
195                 }
196                 navBar.getView().updateStates();
197             }
198         });
199     }
200 
updateAccessibilityButtonModeIfNeeded()201     private void updateAccessibilityButtonModeIfNeeded() {
202         final int mode = mSecureSettings.getIntForUser(
203                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
204                 ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
205 
206         // ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU is compatible under gestural or non-gestural
207         // mode, so we don't need to update it.
208         if (mode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
209             return;
210         }
211 
212         // ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR is incompatible under gestural mode. Need to
213         // force update to ACCESSIBILITY_BUTTON_MODE_GESTURE.
214         if (QuickStepContract.isGesturalMode(mNavMode)
215                 && mode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR) {
216             mSecureSettings.putIntForUser(
217                     Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_GESTURE,
218                     UserHandle.USER_CURRENT);
219             // ACCESSIBILITY_BUTTON_MODE_GESTURE is incompatible under non gestural mode. Need to
220             // force update to ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR.
221         } else if (!QuickStepContract.isGesturalMode(mNavMode)
222                 && mode == ACCESSIBILITY_BUTTON_MODE_GESTURE) {
223             mSecureSettings.putIntForUser(
224                     Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
225                     ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
226         }
227     }
228 
229     /** @see #initializeTaskbarIfNecessary() */
updateNavbarForTaskbar()230     private boolean updateNavbarForTaskbar() {
231         boolean taskbarShown = initializeTaskbarIfNecessary();
232         if (!taskbarShown && mNavigationBars.get(mContext.getDisplayId()) == null) {
233             createNavigationBar(mContext.getDisplay(), null, null);
234         }
235         return taskbarShown;
236     }
237 
238     /** @return {@code true} if taskbar is enabled, false otherwise */
initializeTaskbarIfNecessary()239     private boolean initializeTaskbarIfNecessary() {
240         // Enable for large screens or (phone AND flag is set); assuming phone = !mIsLargeScreen
241         boolean taskbarEnabled = mIsLargeScreen || mFeatureFlags.isEnabled(
242                 Flags.HIDE_NAVBAR_WINDOW);
243 
244         if (taskbarEnabled) {
245             Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary");
246             final int displayId = mContext.getDisplayId();
247             // Hint to NavBarHelper if we are replacing an existing bar to skip extra work
248             mNavBarHelper.setTogglingNavbarTaskbar(mNavigationBars.contains(displayId));
249             // Remove navigation bar when taskbar is showing
250             removeNavigationBar(displayId);
251             mTaskbarDelegate.init(displayId);
252             mNavBarHelper.setTogglingNavbarTaskbar(false);
253             Trace.endSection();
254 
255         } else {
256             mTaskbarDelegate.destroy();
257         }
258         return taskbarEnabled;
259     }
260 
261     @Override
onDisplayRemoved(int displayId)262     public void onDisplayRemoved(int displayId) {
263         removeNavigationBar(displayId);
264     }
265 
266     @Override
onDisplayReady(int displayId)267     public void onDisplayReady(int displayId) {
268         Display display = mDisplayManager.getDisplay(displayId);
269         mIsLargeScreen = isLargeScreen(mContext);
270         createNavigationBar(display, null /* savedState */, null /* result */);
271     }
272 
273     @Override
setNavigationBarLumaSamplingEnabled(int displayId, boolean enable)274     public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
275         final NavigationBar navigationBar = getNavigationBar(displayId);
276         if (navigationBar != null) {
277             navigationBar.setNavigationBarLumaSamplingEnabled(enable);
278         }
279     }
280 
281     /**
282      * Recreates the navigation bar for the given display.
283      */
recreateNavigationBar(int displayId)284     private void recreateNavigationBar(int displayId) {
285         // TODO: Improve this flow so that we don't need to create a new nav bar but just
286         //       the view
287         Bundle savedState = new Bundle();
288         NavigationBar bar = mNavigationBars.get(displayId);
289         if (bar != null) {
290             bar.onSaveInstanceState(savedState);
291         }
292         removeNavigationBar(displayId);
293         createNavigationBar(mDisplayManager.getDisplay(displayId), savedState, null /* result */);
294     }
295 
296     // TODO(b/117478341): I use {@code includeDefaultDisplay} to make this method compatible to
297     // CarStatusBar because they have their own nav bar. Think about a better way for it.
298     /**
299      * Creates navigation bars when car/status bar initializes.
300      *
301      * @param includeDefaultDisplay {@code true} to create navigation bar on default display.
302      */
createNavigationBars(final boolean includeDefaultDisplay, RegisterStatusBarResult result)303     public void createNavigationBars(final boolean includeDefaultDisplay,
304             RegisterStatusBarResult result) {
305         updateAccessibilityButtonModeIfNeeded();
306 
307         // Don't need to create nav bar on the default display if we initialize TaskBar.
308         final boolean shouldCreateDefaultNavbar = includeDefaultDisplay
309                 && !initializeTaskbarIfNecessary();
310         Display[] displays = mDisplayTracker.getAllDisplays();
311         for (Display display : displays) {
312             if (shouldCreateDefaultNavbar
313                     || display.getDisplayId() != mDisplayTracker.getDefaultDisplayId()) {
314                 createNavigationBar(display, null /* savedState */, result);
315             }
316         }
317     }
318 
319     /**
320      * Adds a navigation bar on default display or an external display if the display supports
321      * system decorations.
322      *
323      * @param display the display to add navigation bar on.
324      */
325     @VisibleForTesting
createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result)326     void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
327         if (display == null) {
328             return;
329         }
330 
331         final int displayId = display.getDisplayId();
332         final boolean isOnDefaultDisplay = displayId == mDisplayTracker.getDefaultDisplayId();
333 
334         // We may show TaskBar on the default display for large screen device. Don't need to create
335         // navigation bar for this case.
336         if (isOnDefaultDisplay && initializeTaskbarIfNecessary()) {
337             return;
338         }
339 
340         final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
341 
342         try {
343             if (!wms.hasNavigationBar(displayId)) {
344                 return;
345             }
346         } catch (RemoteException e) {
347             // Cannot get wms, just return with warning message.
348             Log.w(TAG, "Cannot get WindowManager.");
349             return;
350         }
351         final Context context = isOnDefaultDisplay
352                 ? mContext
353                 : mContext.createDisplayContext(display);
354         NavigationBarComponent component = mNavigationBarComponentFactory.create(
355                 context, savedState);
356         NavigationBar navBar = component.getNavigationBar();
357         navBar.init();
358         mNavigationBars.put(displayId, navBar);
359 
360         navBar.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
361             @Override
362             public void onViewAttachedToWindow(View v) {
363                 if (result != null) {
364                     navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
365                             result.mImeWindowVis, result.mImeBackDisposition,
366                             result.mShowImeSwitcher);
367                 }
368             }
369 
370             @Override
371             public void onViewDetachedFromWindow(View v) {
372                 v.removeOnAttachStateChangeListener(this);
373             }
374         });
375     }
376 
removeNavigationBar(int displayId)377     void removeNavigationBar(int displayId) {
378         NavigationBar navBar = mNavigationBars.get(displayId);
379         if (navBar != null) {
380             navBar.destroyView();
381             mNavigationBars.remove(displayId);
382         }
383     }
384 
385     /** @see NavigationBar#checkNavBarModes() */
checkNavBarModes(int displayId)386     public void checkNavBarModes(int displayId) {
387         NavigationBar navBar = mNavigationBars.get(displayId);
388         if (navBar != null) {
389             navBar.checkNavBarModes();
390         }
391     }
392 
393     /** @see NavigationBar#finishBarAnimations() */
finishBarAnimations(int displayId)394     public void finishBarAnimations(int displayId) {
395         NavigationBar navBar = mNavigationBars.get(displayId);
396         if (navBar != null) {
397             navBar.finishBarAnimations();
398         }
399     }
400 
401     /** @see NavigationBar#touchAutoDim() */
touchAutoDim(int displayId)402     public void touchAutoDim(int displayId) {
403         NavigationBar navBar = mNavigationBars.get(displayId);
404         if (navBar != null) {
405             navBar.touchAutoDim();
406         }
407     }
408 
409     /** @see NavigationBar#transitionTo(int, boolean) */
transitionTo(int displayId, @TransitionMode int barMode, boolean animate)410     public void transitionTo(int displayId, @TransitionMode int barMode, boolean animate) {
411         NavigationBar navBar = mNavigationBars.get(displayId);
412         if (navBar != null) {
413             navBar.transitionTo(barMode, animate);
414         }
415     }
416 
417     /** @see NavigationBar#disableAnimationsDuringHide(long) */
disableAnimationsDuringHide(int displayId, long delay)418     public void disableAnimationsDuringHide(int displayId, long delay) {
419         NavigationBar navBar = mNavigationBars.get(displayId);
420         if (navBar != null) {
421             navBar.disableAnimationsDuringHide(delay);
422         }
423     }
424 
425     /** @return {@link NavigationBarView} on the default display. */
getDefaultNavigationBarView()426     public @Nullable NavigationBarView getDefaultNavigationBarView() {
427         return getNavigationBarView(mDisplayTracker.getDefaultDisplayId());
428     }
429 
430     /**
431      * @param displayId the ID of display which Navigation bar is on
432      * @return {@link NavigationBarView} on the display with {@code displayId}.
433      *         {@code null} if no navigation bar on that display.
434      */
getNavigationBarView(int displayId)435     public @Nullable NavigationBarView getNavigationBarView(int displayId) {
436         NavigationBar navBar = getNavigationBar(displayId);
437         return (navBar == null) ? null : navBar.getView();
438     }
439 
getNavigationBar(int displayId)440     private @Nullable NavigationBar getNavigationBar(int displayId) {
441         return mNavigationBars.get(displayId);
442     }
443 
showPinningEnterExitToast(int displayId, boolean entering)444     public void showPinningEnterExitToast(int displayId, boolean entering) {
445         final NavigationBarView navBarView = getNavigationBarView(displayId);
446         if (navBarView != null) {
447             navBarView.showPinningEnterExitToast(entering);
448         } else if (displayId == mDisplayTracker.getDefaultDisplayId()
449                 && mTaskbarDelegate.isInitialized()) {
450             mTaskbarDelegate.showPinningEnterExitToast(entering);
451         }
452     }
453 
showPinningEscapeToast(int displayId)454     public void showPinningEscapeToast(int displayId) {
455         final NavigationBarView navBarView = getNavigationBarView(displayId);
456         if (navBarView != null) {
457             navBarView.showPinningEscapeToast();
458         } else if (displayId == mDisplayTracker.getDefaultDisplayId()
459                 && mTaskbarDelegate.isInitialized()) {
460             mTaskbarDelegate.showPinningEscapeToast();
461         }
462     }
463 
isOverviewEnabled(int displayId)464     public boolean isOverviewEnabled(int displayId) {
465         final NavigationBarView navBarView = getNavigationBarView(displayId);
466         if (navBarView != null) {
467             return navBarView.isOverviewEnabled();
468         } else {
469             return mTaskbarDelegate.isOverviewEnabled();
470         }
471     }
472 
473     /** @return {@link NavigationBar} on the default display. */
474     @Nullable
getDefaultNavigationBar()475     public NavigationBar getDefaultNavigationBar() {
476         return mNavigationBars.get(mDisplayTracker.getDefaultDisplayId());
477     }
478 
479     @Override
dump(@onNull PrintWriter pw, @NonNull String[] args)480     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
481         pw.println("mIsLargeScreen=" + mIsLargeScreen);
482         pw.println("mNavMode=" + mNavMode);
483         for (int i = 0; i < mNavigationBars.size(); i++) {
484             if (i > 0) {
485                 pw.println();
486             }
487             mNavigationBars.valueAt(i).dump(pw);
488         }
489     }
490 }
491