• 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.view.Display.DEFAULT_DISPLAY;
20 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
21 
22 import android.content.Context;
23 import android.content.pm.ActivityInfo;
24 import android.content.res.Configuration;
25 import android.content.res.Resources;
26 import android.hardware.display.DisplayManager;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.RemoteException;
30 import android.os.SystemProperties;
31 import android.util.DisplayMetrics;
32 import android.util.Log;
33 import android.util.SparseArray;
34 import android.view.Display;
35 import android.view.IWindowManager;
36 import android.view.View;
37 import android.view.WindowManager;
38 import android.view.WindowManagerGlobal;
39 import android.view.accessibility.AccessibilityManager;
40 
41 import androidx.annotation.NonNull;
42 import androidx.annotation.Nullable;
43 
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.internal.logging.MetricsLogger;
46 import com.android.internal.logging.UiEventLogger;
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.accessibility.AccessibilityButtonModeObserver;
51 import com.android.systemui.accessibility.SystemActions;
52 import com.android.systemui.assist.AssistManager;
53 import com.android.systemui.broadcast.BroadcastDispatcher;
54 import com.android.systemui.dagger.SysUISingleton;
55 import com.android.systemui.dagger.qualifiers.Main;
56 import com.android.systemui.model.SysUiState;
57 import com.android.systemui.plugins.statusbar.StatusBarStateController;
58 import com.android.systemui.recents.OverviewProxyService;
59 import com.android.systemui.recents.Recents;
60 import com.android.systemui.settings.UserTracker;
61 import com.android.systemui.statusbar.CommandQueue;
62 import com.android.systemui.statusbar.CommandQueue.Callbacks;
63 import com.android.systemui.statusbar.NotificationRemoteInputManager;
64 import com.android.systemui.statusbar.NotificationShadeDepthController;
65 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
66 import com.android.systemui.statusbar.phone.ShadeController;
67 import com.android.systemui.statusbar.phone.StatusBar;
68 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
69 import com.android.systemui.statusbar.policy.ConfigurationController;
70 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
71 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
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 
78 import javax.inject.Inject;
79 
80 import dagger.Lazy;
81 
82 
83 /** A controller to handle navigation bars. */
84 @SysUISingleton
85 public class NavigationBarController implements Callbacks,
86         ConfigurationController.ConfigurationListener,
87         NavigationModeController.ModeChangedListener, Dumpable {
88 
89     private static final float TABLET_MIN_DPS = 600;
90 
91     private static final String TAG = NavigationBarController.class.getSimpleName();
92 
93     private final Context mContext;
94     private final WindowManager mWindowManager;
95     private final Lazy<AssistManager> mAssistManagerLazy;
96     private final AccessibilityManager mAccessibilityManager;
97     private final AccessibilityManagerWrapper mAccessibilityManagerWrapper;
98     private final DeviceProvisionedController mDeviceProvisionedController;
99     private final MetricsLogger mMetricsLogger;
100     private final OverviewProxyService mOverviewProxyService;
101     private final NavigationModeController mNavigationModeController;
102     private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
103     private final StatusBarStateController mStatusBarStateController;
104     private final SysUiState mSysUiFlagsContainer;
105     private final BroadcastDispatcher mBroadcastDispatcher;
106     private final CommandQueue mCommandQueue;
107     private final Optional<Pip> mPipOptional;
108     private final Optional<LegacySplitScreen> mSplitScreenOptional;
109     private final Optional<Recents> mRecentsOptional;
110     private final Lazy<StatusBar> mStatusBarLazy;
111     private final ShadeController mShadeController;
112     private final NotificationRemoteInputManager mNotificationRemoteInputManager;
113     private final SystemActions mSystemActions;
114     private final UiEventLogger mUiEventLogger;
115     private final Handler mHandler;
116     private final DisplayManager mDisplayManager;
117     private final NavigationBarOverlayController mNavBarOverlayController;
118     private final TaskbarDelegate mTaskbarDelegate;
119     private final NotificationShadeDepthController mNotificationShadeDepthController;
120     private int mNavMode;
121     private boolean mIsTablet;
122     private final UserTracker mUserTracker;
123 
124     /** A displayId - nav bar maps. */
125     @VisibleForTesting
126     SparseArray<NavigationBar> mNavigationBars = new SparseArray<>();
127 
128     // Tracks config changes that will actually recreate the nav bar
129     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
130             ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_SCREEN_LAYOUT
131                     | ActivityInfo.CONFIG_UI_MODE);
132 
133     @Inject
NavigationBarController(Context context, WindowManager windowManager, Lazy<AssistManager> assistManagerLazy, AccessibilityManager accessibilityManager, AccessibilityManagerWrapper accessibilityManagerWrapper, DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger, OverviewProxyService overviewProxyService, NavigationModeController navigationModeController, AccessibilityButtonModeObserver accessibilityButtonModeObserver, StatusBarStateController statusBarStateController, SysUiState sysUiFlagsContainer, BroadcastDispatcher broadcastDispatcher, CommandQueue commandQueue, Optional<Pip> pipOptional, Optional<LegacySplitScreen> splitScreenOptional, Optional<Recents> recentsOptional, Lazy<StatusBar> statusBarLazy, ShadeController shadeController, NotificationRemoteInputManager notificationRemoteInputManager, NotificationShadeDepthController notificationShadeDepthController, SystemActions systemActions, @Main Handler mainHandler, UiEventLogger uiEventLogger, NavigationBarOverlayController navBarOverlayController, ConfigurationController configurationController, UserTracker userTracker)134     public NavigationBarController(Context context,
135             WindowManager windowManager,
136             Lazy<AssistManager> assistManagerLazy,
137             AccessibilityManager accessibilityManager,
138             AccessibilityManagerWrapper accessibilityManagerWrapper,
139             DeviceProvisionedController deviceProvisionedController,
140             MetricsLogger metricsLogger,
141             OverviewProxyService overviewProxyService,
142             NavigationModeController navigationModeController,
143             AccessibilityButtonModeObserver accessibilityButtonModeObserver,
144             StatusBarStateController statusBarStateController,
145             SysUiState sysUiFlagsContainer,
146             BroadcastDispatcher broadcastDispatcher,
147             CommandQueue commandQueue,
148             Optional<Pip> pipOptional,
149             Optional<LegacySplitScreen> splitScreenOptional,
150             Optional<Recents> recentsOptional,
151             Lazy<StatusBar> statusBarLazy,
152             ShadeController shadeController,
153             NotificationRemoteInputManager notificationRemoteInputManager,
154             NotificationShadeDepthController notificationShadeDepthController,
155             SystemActions systemActions,
156             @Main Handler mainHandler,
157             UiEventLogger uiEventLogger,
158             NavigationBarOverlayController navBarOverlayController,
159             ConfigurationController configurationController,
160             UserTracker userTracker) {
161         mContext = context;
162         mWindowManager = windowManager;
163         mAssistManagerLazy = assistManagerLazy;
164         mAccessibilityManager = accessibilityManager;
165         mAccessibilityManagerWrapper = accessibilityManagerWrapper;
166         mDeviceProvisionedController = deviceProvisionedController;
167         mMetricsLogger = metricsLogger;
168         mOverviewProxyService = overviewProxyService;
169         mNavigationModeController = navigationModeController;
170         mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
171         mStatusBarStateController = statusBarStateController;
172         mSysUiFlagsContainer = sysUiFlagsContainer;
173         mBroadcastDispatcher = broadcastDispatcher;
174         mCommandQueue = commandQueue;
175         mPipOptional = pipOptional;
176         mSplitScreenOptional = splitScreenOptional;
177         mRecentsOptional = recentsOptional;
178         mStatusBarLazy = statusBarLazy;
179         mShadeController = shadeController;
180         mNotificationRemoteInputManager = notificationRemoteInputManager;
181         mNotificationShadeDepthController = notificationShadeDepthController;
182         mSystemActions = systemActions;
183         mUiEventLogger = uiEventLogger;
184         mHandler = mainHandler;
185         mDisplayManager = mContext.getSystemService(DisplayManager.class);
186         commandQueue.addCallback(this);
187         configurationController.addCallback(this);
188         mConfigChanges.applyNewConfig(mContext.getResources());
189         mNavBarOverlayController = navBarOverlayController;
190         mNavMode = mNavigationModeController.addListener(this);
191         mNavigationModeController.addListener(this);
192         mTaskbarDelegate = new TaskbarDelegate(mOverviewProxyService);
193         mIsTablet = isTablet(mContext.getResources().getConfiguration());
194         mUserTracker = userTracker;
195     }
196 
197     @Override
onConfigChanged(Configuration newConfig)198     public void onConfigChanged(Configuration newConfig) {
199         boolean isOldConfigTablet = mIsTablet;
200         mIsTablet = isTablet(newConfig);
201         boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
202         // If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
203         if (largeScreenChanged && updateNavbarForTaskbar()) {
204             return;
205         }
206 
207         if (mConfigChanges.applyNewConfig(mContext.getResources())) {
208             for (int i = 0; i < mNavigationBars.size(); i++) {
209                 recreateNavigationBar(mNavigationBars.keyAt(i));
210             }
211         } else {
212             for (int i = 0; i < mNavigationBars.size(); i++) {
213                 mNavigationBars.valueAt(i).onConfigurationChanged(newConfig);
214             }
215         }
216     }
217 
218     @Override
onNavigationModeChanged(int mode)219     public void onNavigationModeChanged(int mode) {
220         if (mNavMode == mode) {
221             return;
222         }
223         final int oldMode = mNavMode;
224         mNavMode = mode;
225         mHandler.post(() -> {
226             // create/destroy nav bar based on nav mode only in unfolded state
227             if (oldMode != mNavMode) {
228                 updateNavbarForTaskbar();
229             }
230             for (int i = 0; i < mNavigationBars.size(); i++) {
231                 NavigationBar navBar = mNavigationBars.valueAt(i);
232                 if (navBar == null) {
233                     continue;
234                 }
235                 navBar.getView().updateStates();
236             }
237         });
238     }
239 
240     /**
241      * @return {@code true} if navbar was added/removed, false otherwise
242      */
updateNavbarForTaskbar()243     public boolean updateNavbarForTaskbar() {
244         if (!isThreeButtonTaskbarFlagEnabled()) {
245             return false;
246         }
247 
248         if (mIsTablet && mNavMode == NAV_BAR_MODE_3BUTTON) {
249             // Remove navigation bar when taskbar is showing, currently only for 3 button mode
250             removeNavigationBar(mContext.getDisplayId());
251             mCommandQueue.addCallback(mTaskbarDelegate);
252         } else if (mNavigationBars.get(mContext.getDisplayId()) == null) {
253             // Add navigation bar after taskbar goes away
254             createNavigationBar(mContext.getDisplay(), null, null);
255             mCommandQueue.removeCallback(mTaskbarDelegate);
256         }
257 
258         return true;
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         mIsTablet = isTablet(mContext.getResources().getConfiguration());
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 NavigationBarView navigationBarView = getNavigationBarView(displayId);
276         if (navigationBarView != null) {
277             navigationBarView.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         if (updateNavbarForTaskbar()) {
306             return;
307         }
308 
309         Display[] displays = mDisplayManager.getDisplays();
310         for (Display display : displays) {
311             if (includeDefaultDisplay || display.getDisplayId() != DEFAULT_DISPLAY) {
312                 createNavigationBar(display, null /* savedState */, result);
313             }
314         }
315     }
316 
317     /**
318      * Adds a navigation bar on default display or an external display if the display supports
319      * system decorations.
320      *
321      * @param display the display to add navigation bar on.
322      */
323     @VisibleForTesting
createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result)324     void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
325         if (display == null) {
326             return;
327         }
328 
329         if (isThreeButtonTaskbarEnabled()) {
330             return;
331         }
332 
333         final int displayId = display.getDisplayId();
334         final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;
335         final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
336 
337         try {
338             if (!wms.hasNavigationBar(displayId)) {
339                 return;
340             }
341         } catch (RemoteException e) {
342             // Cannot get wms, just return with warning message.
343             Log.w(TAG, "Cannot get WindowManager.");
344             return;
345         }
346         final Context context = isOnDefaultDisplay
347                 ? mContext
348                 : mContext.createDisplayContext(display);
349         NavigationBar navBar = new NavigationBar(context,
350                 mWindowManager,
351                 mAssistManagerLazy,
352                 mAccessibilityManager,
353                 mAccessibilityManagerWrapper,
354                 mDeviceProvisionedController,
355                 mMetricsLogger,
356                 mOverviewProxyService,
357                 mNavigationModeController,
358                 mAccessibilityButtonModeObserver,
359                 mStatusBarStateController,
360                 mSysUiFlagsContainer,
361                 mBroadcastDispatcher,
362                 mCommandQueue,
363                 mPipOptional,
364                 mSplitScreenOptional,
365                 mRecentsOptional,
366                 mStatusBarLazy,
367                 mShadeController,
368                 mNotificationRemoteInputManager,
369                 mNotificationShadeDepthController,
370                 mSystemActions,
371                 mHandler,
372                 mNavBarOverlayController,
373                 mUiEventLogger,
374                 mUserTracker);
375         mNavigationBars.put(displayId, navBar);
376 
377         View navigationBarView = navBar.createView(savedState);
378         navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
379             @Override
380             public void onViewAttachedToWindow(View v) {
381                 if (result != null) {
382                     navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
383                             result.mImeWindowVis, result.mImeBackDisposition,
384                             result.mShowImeSwitcher);
385                 }
386             }
387 
388             @Override
389             public void onViewDetachedFromWindow(View v) {
390                 v.removeOnAttachStateChangeListener(this);
391             }
392         });
393     }
394 
removeNavigationBar(int displayId)395     void removeNavigationBar(int displayId) {
396         NavigationBar navBar = mNavigationBars.get(displayId);
397         if (navBar != null) {
398             navBar.setAutoHideController(/* autoHideController */ null);
399             navBar.destroyView();
400             mNavigationBars.remove(displayId);
401         }
402     }
403 
404     /** @see NavigationBar#checkNavBarModes() */
checkNavBarModes(int displayId)405     public void checkNavBarModes(int displayId) {
406         NavigationBar navBar = mNavigationBars.get(displayId);
407         if (navBar != null) {
408             navBar.checkNavBarModes();
409         }
410     }
411 
412     /** @see NavigationBar#finishBarAnimations() */
finishBarAnimations(int displayId)413     public void finishBarAnimations(int displayId) {
414         NavigationBar navBar = mNavigationBars.get(displayId);
415         if (navBar != null) {
416             navBar.finishBarAnimations();
417         }
418     }
419 
420     /** @see NavigationBar#touchAutoDim() */
touchAutoDim(int displayId)421     public void touchAutoDim(int displayId) {
422         NavigationBar navBar = mNavigationBars.get(displayId);
423         if (navBar != null) {
424             navBar.touchAutoDim();
425         }
426     }
427 
428     /** @see NavigationBar#transitionTo(int, boolean) */
transitionTo(int displayId, @TransitionMode int barMode, boolean animate)429     public void transitionTo(int displayId, @TransitionMode int barMode, boolean animate) {
430         NavigationBar navBar = mNavigationBars.get(displayId);
431         if (navBar != null) {
432             navBar.transitionTo(barMode, animate);
433         }
434     }
435 
436     /** @see NavigationBar#disableAnimationsDuringHide(long) */
disableAnimationsDuringHide(int displayId, long delay)437     public void disableAnimationsDuringHide(int displayId, long delay) {
438         NavigationBar navBar = mNavigationBars.get(displayId);
439         if (navBar != null) {
440             navBar.disableAnimationsDuringHide(delay);
441         }
442     }
443 
444     /** @return {@link NavigationBarView} on the default display. */
getDefaultNavigationBarView()445     public @Nullable NavigationBarView getDefaultNavigationBarView() {
446         return getNavigationBarView(DEFAULT_DISPLAY);
447     }
448 
449     /**
450      * @param displayId the ID of display which Navigation bar is on
451      * @return {@link NavigationBarView} on the display with {@code displayId}.
452      *         {@code null} if no navigation bar on that display.
453      */
getNavigationBarView(int displayId)454     public @Nullable NavigationBarView getNavigationBarView(int displayId) {
455         NavigationBar navBar = mNavigationBars.get(displayId);
456         return (navBar == null) ? null : navBar.getView();
457     }
458 
459     /** @return {@link NavigationBar} on the default display. */
460     @Nullable
getDefaultNavigationBar()461     public NavigationBar getDefaultNavigationBar() {
462         return mNavigationBars.get(DEFAULT_DISPLAY);
463     }
464 
isThreeButtonTaskbarEnabled()465     private boolean isThreeButtonTaskbarEnabled() {
466         return mIsTablet && mNavMode == NAV_BAR_MODE_3BUTTON &&
467                 isThreeButtonTaskbarFlagEnabled();
468     }
469 
isThreeButtonTaskbarFlagEnabled()470     private boolean isThreeButtonTaskbarFlagEnabled() {
471         return SystemProperties.getBoolean("persist.debug.taskbar_three_button", false);
472     }
473 
isTablet(Configuration newConfig)474     private boolean isTablet(Configuration newConfig) {
475         float density = Resources.getSystem().getDisplayMetrics().density;
476         int size = Math.min((int) (density * newConfig.screenWidthDp),
477                 (int) (density* newConfig.screenHeightDp));
478         DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
479         float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
480         return (size / densityRatio) >= TABLET_MIN_DPS;
481     }
482 
483     @Override
dump(@onNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args)484     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
485         for (int i = 0; i < mNavigationBars.size(); i++) {
486             if (i > 0) {
487                 pw.println();
488             }
489             mNavigationBars.valueAt(i).dump(pw);
490         }
491     }
492 }
493