1 /*
2  * Copyright (C) 2018 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.statusbar;
18 
19 import static android.view.Display.DEFAULT_DISPLAY;
20 
21 import android.content.Context;
22 import android.hardware.display.DisplayManager;
23 import android.os.Handler;
24 import android.os.RemoteException;
25 import android.util.Log;
26 import android.util.SparseArray;
27 import android.view.Display;
28 import android.view.IWindowManager;
29 import android.view.View;
30 import android.view.WindowManagerGlobal;
31 
32 import androidx.annotation.Nullable;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.internal.statusbar.RegisterStatusBarResult;
36 import com.android.systemui.Dependency;
37 import com.android.systemui.assist.AssistHandleViewController;
38 import com.android.systemui.dagger.qualifiers.Main;
39 import com.android.systemui.fragments.FragmentHostManager;
40 import com.android.systemui.plugins.DarkIconDispatcher;
41 import com.android.systemui.statusbar.CommandQueue.Callbacks;
42 import com.android.systemui.statusbar.phone.AutoHideController;
43 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
44 import com.android.systemui.statusbar.phone.LightBarController;
45 import com.android.systemui.statusbar.phone.NavigationBarFragment;
46 import com.android.systemui.statusbar.phone.NavigationBarView;
47 import com.android.systemui.statusbar.phone.NavigationModeController;
48 import com.android.systemui.statusbar.policy.BatteryController;
49 
50 import javax.inject.Inject;
51 import javax.inject.Singleton;
52 
53 
54 /** A controller to handle navigation bars. */
55 @Singleton
56 public class NavigationBarController implements Callbacks {
57 
58     private static final String TAG = NavigationBarController.class.getSimpleName();
59 
60     private final Context mContext;
61     private final Handler mHandler;
62     private final DisplayManager mDisplayManager;
63 
64     /** A displayId - nav bar maps. */
65     @VisibleForTesting
66     SparseArray<NavigationBarFragment> mNavigationBars = new SparseArray<>();
67 
68     @Inject
NavigationBarController(Context context, @Main Handler handler, CommandQueue commandQueue)69     public NavigationBarController(Context context, @Main Handler handler,
70             CommandQueue commandQueue) {
71         mContext = context;
72         mHandler = handler;
73         mDisplayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
74         commandQueue.addCallback(this);
75     }
76 
77     @Override
onDisplayRemoved(int displayId)78     public void onDisplayRemoved(int displayId) {
79         removeNavigationBar(displayId);
80     }
81 
82     @Override
onDisplayReady(int displayId)83     public void onDisplayReady(int displayId) {
84         Display display = mDisplayManager.getDisplay(displayId);
85         createNavigationBar(display, null);
86     }
87 
88     // TODO(b/117478341): I use {@code includeDefaultDisplay} to make this method compatible to
89     // CarStatusBar because they have their own nav bar. Think about a better way for it.
90     /**
91      * Creates navigation bars when car/status bar initializes.
92      *
93      * @param includeDefaultDisplay {@code true} to create navigation bar on default display.
94      */
createNavigationBars(final boolean includeDefaultDisplay, RegisterStatusBarResult result)95     public void createNavigationBars(final boolean includeDefaultDisplay,
96             RegisterStatusBarResult result) {
97         Display[] displays = mDisplayManager.getDisplays();
98         for (Display display : displays) {
99             if (includeDefaultDisplay || display.getDisplayId() != DEFAULT_DISPLAY) {
100                 createNavigationBar(display, result);
101             }
102         }
103     }
104 
105     /**
106      * Adds a navigation bar on default display or an external display if the display supports
107      * system decorations.
108      *
109      * @param display the display to add navigation bar on.
110      */
111     @VisibleForTesting
createNavigationBar(Display display, RegisterStatusBarResult result)112     void createNavigationBar(Display display, RegisterStatusBarResult result) {
113         if (display == null) {
114             return;
115         }
116 
117         final int displayId = display.getDisplayId();
118         final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;
119         final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
120 
121         try {
122             if (!wms.hasNavigationBar(displayId)) {
123                 return;
124             }
125         } catch (RemoteException e) {
126             // Cannot get wms, just return with warning message.
127             Log.w(TAG, "Cannot get WindowManager.");
128             return;
129         }
130         final Context context = isOnDefaultDisplay
131                 ? mContext
132                 : mContext.createDisplayContext(display);
133         NavigationBarFragment.create(context, (tag, fragment) -> {
134             NavigationBarFragment navBar = (NavigationBarFragment) fragment;
135 
136             // Unfortunately, we still need it because status bar needs LightBarController
137             // before notifications creation. We cannot directly use getLightBarController()
138             // from NavigationBarFragment directly.
139             LightBarController lightBarController = isOnDefaultDisplay
140                     ? Dependency.get(LightBarController.class)
141                     : new LightBarController(context,
142                             Dependency.get(DarkIconDispatcher.class),
143                             Dependency.get(BatteryController.class),
144                             Dependency.get(NavigationModeController.class));
145             navBar.setLightBarController(lightBarController);
146 
147             // TODO(b/118592525): to support multi-display, we start to add something which is
148             //                    per-display, while others may be global. I think it's time to add
149             //                    a new class maybe named DisplayDependency to solve per-display
150             //                    Dependency problem.
151             AutoHideController autoHideController = isOnDefaultDisplay
152                     ? Dependency.get(AutoHideController.class)
153                     : new AutoHideController(context, mHandler,
154                             Dependency.get(IWindowManager.class));
155             navBar.setAutoHideController(autoHideController);
156             navBar.restoreAppearanceAndTransientState();
157             mNavigationBars.append(displayId, navBar);
158 
159             if (result != null) {
160                 navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
161                         result.mImeWindowVis, result.mImeBackDisposition,
162                         result.mShowImeSwitcher);
163             }
164         });
165     }
166 
removeNavigationBar(int displayId)167     private void removeNavigationBar(int displayId) {
168         NavigationBarFragment navBar = mNavigationBars.get(displayId);
169         if (navBar != null) {
170             navBar.setAutoHideController(/* autoHideController */ null);
171             View navigationWindow = navBar.getView().getRootView();
172             WindowManagerGlobal.getInstance()
173                     .removeView(navigationWindow, true /* immediate */);
174             // Also remove FragmentHostState here in case that onViewDetachedFromWindow has not yet
175             // invoked after display removal.
176             FragmentHostManager.removeAndDestroy(navigationWindow);
177             mNavigationBars.remove(displayId);
178         }
179     }
180 
181     /** @see NavigationBarFragment#checkNavBarModes() */
checkNavBarModes(int displayId)182     public void checkNavBarModes(int displayId) {
183         NavigationBarFragment navBar = mNavigationBars.get(displayId);
184         if (navBar != null) {
185             navBar.checkNavBarModes();
186         }
187     }
188 
189     /** @see NavigationBarFragment#finishBarAnimations() */
finishBarAnimations(int displayId)190     public void finishBarAnimations(int displayId) {
191         NavigationBarFragment navBar = mNavigationBars.get(displayId);
192         if (navBar != null) {
193             navBar.finishBarAnimations();
194         }
195     }
196 
197     /** @see NavigationBarFragment#touchAutoDim() */
touchAutoDim(int displayId)198     public void touchAutoDim(int displayId) {
199         NavigationBarFragment navBar = mNavigationBars.get(displayId);
200         if (navBar != null) {
201             navBar.touchAutoDim();
202         }
203     }
204 
205     /** @see NavigationBarFragment#transitionTo(int, boolean) */
transitionTo(int displayId, @TransitionMode int barMode, boolean animate)206     public void transitionTo(int displayId, @TransitionMode int barMode, boolean animate) {
207         NavigationBarFragment navBar = mNavigationBars.get(displayId);
208         if (navBar != null) {
209             navBar.transitionTo(barMode, animate);
210         }
211     }
212 
213     /** @see NavigationBarFragment#disableAnimationsDuringHide(long) */
disableAnimationsDuringHide(int displayId, long delay)214     public void disableAnimationsDuringHide(int displayId, long delay) {
215         NavigationBarFragment navBar = mNavigationBars.get(displayId);
216         if (navBar != null) {
217             navBar.disableAnimationsDuringHide(delay);
218         }
219     }
220 
221     /** @return {@link NavigationBarView} on the default display. */
getDefaultNavigationBarView()222     public @Nullable NavigationBarView getDefaultNavigationBarView() {
223         return getNavigationBarView(DEFAULT_DISPLAY);
224     }
225 
226     /**
227      * @param displayId the ID of display which Navigation bar is on
228      * @return {@link NavigationBarView} on the display with {@code displayId}.
229      *         {@code null} if no navigation bar on that display.
230      */
getNavigationBarView(int displayId)231     public @Nullable NavigationBarView getNavigationBarView(int displayId) {
232         NavigationBarFragment navBar = mNavigationBars.get(displayId);
233         return (navBar == null) ? null : (NavigationBarView) navBar.getView();
234     }
235 
236     /** @return {@link NavigationBarFragment} on the default display. */
237     @Nullable
getDefaultNavigationBarFragment()238     public NavigationBarFragment getDefaultNavigationBarFragment() {
239         return mNavigationBars.get(DEFAULT_DISPLAY);
240     }
241 
242     /** @return {@link AssistHandleViewController} (only on the default display). */
243     @Nullable
getAssistHandlerViewController()244     public AssistHandleViewController getAssistHandlerViewController() {
245         NavigationBarFragment navBar = getDefaultNavigationBarFragment();
246         return navBar == null ? null : navBar.getAssistHandlerViewController();
247     }
248 }
249