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