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