1 /* 2 * Copyright (C) 2019 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.window; 18 19 import static android.app.WindowConfiguration.ROTATION_UNDEFINED; 20 import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION; 21 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 22 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC; 23 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; 24 25 import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; 26 import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; 27 import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; 28 import static com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN; 29 30 import android.content.Context; 31 import android.content.res.Resources; 32 import android.graphics.PixelFormat; 33 import android.graphics.Rect; 34 import android.os.Binder; 35 import android.os.RemoteException; 36 import android.util.Log; 37 import android.view.Gravity; 38 import android.view.IWindowManager; 39 import android.view.Surface; 40 import android.view.View; 41 import android.view.ViewGroup; 42 import android.view.WindowManager; 43 44 import com.android.internal.policy.SystemBarUtils; 45 import com.android.systemui.R; 46 import com.android.systemui.animation.ActivityLaunchAnimator; 47 import com.android.systemui.animation.DelegateLaunchAnimatorController; 48 import com.android.systemui.dagger.SysUISingleton; 49 import com.android.systemui.dagger.qualifiers.Main; 50 import com.android.systemui.fragments.FragmentHostManager; 51 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; 52 53 import java.util.Optional; 54 55 import javax.inject.Inject; 56 57 /** 58 * Encapsulates all logic for the status bar window state management. 59 */ 60 @SysUISingleton 61 public class StatusBarWindowController { 62 private static final String TAG = "StatusBarWindowController"; 63 private static final boolean DEBUG = false; 64 65 private final Context mContext; 66 private final WindowManager mWindowManager; 67 private final IWindowManager mIWindowManager; 68 private final StatusBarContentInsetsProvider mContentInsetsProvider; 69 private final Resources mResources; 70 private int mBarHeight = -1; 71 private final State mCurrentState = new State(); 72 73 private final ViewGroup mStatusBarWindowView; 74 // The container in which we should run launch animations started from the status bar and 75 // expanding into the opening window. 76 private final ViewGroup mLaunchAnimationContainer; 77 private WindowManager.LayoutParams mLp; 78 private final WindowManager.LayoutParams mLpChanged; 79 80 @Inject StatusBarWindowController( Context context, @StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView, WindowManager windowManager, IWindowManager iWindowManager, StatusBarContentInsetsProvider contentInsetsProvider, @Main Resources resources)81 public StatusBarWindowController( 82 Context context, 83 @StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView, 84 WindowManager windowManager, 85 IWindowManager iWindowManager, 86 StatusBarContentInsetsProvider contentInsetsProvider, 87 @Main Resources resources) { 88 mContext = context; 89 mWindowManager = windowManager; 90 mIWindowManager = iWindowManager; 91 mContentInsetsProvider = contentInsetsProvider; 92 mStatusBarWindowView = statusBarWindowView; 93 mLaunchAnimationContainer = mStatusBarWindowView.findViewById( 94 R.id.status_bar_launch_animation_container); 95 mLpChanged = new WindowManager.LayoutParams(); 96 mResources = resources; 97 98 if (mBarHeight < 0) { 99 mBarHeight = SystemBarUtils.getStatusBarHeight(mContext); 100 } 101 } 102 getStatusBarHeight()103 public int getStatusBarHeight() { 104 return mBarHeight; 105 } 106 107 /** 108 * Rereads the status bar height and reapplys the current state if the height 109 * is different. 110 */ refreshStatusBarHeight()111 public void refreshStatusBarHeight() { 112 int heightFromConfig = SystemBarUtils.getStatusBarHeight(mContext); 113 114 if (mBarHeight != heightFromConfig) { 115 mBarHeight = heightFromConfig; 116 apply(mCurrentState); 117 } 118 119 if (DEBUG) Log.v(TAG, "defineSlots"); 120 } 121 122 /** 123 * Adds the status bar view to the window manager. 124 */ attach()125 public void attach() { 126 // Now that the status bar window encompasses the sliding panel and its 127 // translucent backdrop, the entire thing is made TRANSLUCENT and is 128 // hardware-accelerated. 129 mLp = getBarLayoutParams(mContext.getDisplay().getRotation()); 130 131 mWindowManager.addView(mStatusBarWindowView, mLp); 132 mLpChanged.copyFrom(mLp); 133 134 mContentInsetsProvider.addCallback(this::calculateStatusBarLocationsForAllRotations); 135 calculateStatusBarLocationsForAllRotations(); 136 } 137 138 /** Adds the given view to the status bar window view. */ addViewToWindow(View view, ViewGroup.LayoutParams layoutParams)139 public void addViewToWindow(View view, ViewGroup.LayoutParams layoutParams) { 140 mStatusBarWindowView.addView(view, layoutParams); 141 } 142 143 /** Returns the status bar window's background view. */ getBackgroundView()144 public View getBackgroundView() { 145 return mStatusBarWindowView.findViewById(R.id.status_bar_container); 146 } 147 148 /** Returns a fragment host manager for the status bar window view. */ getFragmentHostManager()149 public FragmentHostManager getFragmentHostManager() { 150 return FragmentHostManager.get(mStatusBarWindowView); 151 } 152 153 /** 154 * Provides an updated animation controller if we're animating a view in the status bar. 155 * 156 * This is needed because we have to make sure that the status bar window matches the full 157 * screen during the animation and that we are expanding the view below the other status bar 158 * text. 159 * 160 * @param rootView the root view of the animation 161 * @param animationController the default animation controller to use 162 * @return If the animation is on a view in the status bar, returns an Optional containing an 163 * updated animation controller that handles status-bar-related animation details. Returns an 164 * empty optional if the animation is *not* on a view in the status bar. 165 */ wrapAnimationControllerIfInStatusBar( View rootView, ActivityLaunchAnimator.Controller animationController)166 public Optional<ActivityLaunchAnimator.Controller> wrapAnimationControllerIfInStatusBar( 167 View rootView, ActivityLaunchAnimator.Controller animationController) { 168 if (rootView != mStatusBarWindowView) { 169 return Optional.empty(); 170 } 171 172 animationController.setLaunchContainer(mLaunchAnimationContainer); 173 return Optional.of(new DelegateLaunchAnimatorController(animationController) { 174 @Override 175 public void onLaunchAnimationStart(boolean isExpandingFullyAbove) { 176 getDelegate().onLaunchAnimationStart(isExpandingFullyAbove); 177 setLaunchAnimationRunning(true); 178 } 179 180 @Override 181 public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) { 182 getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove); 183 setLaunchAnimationRunning(false); 184 } 185 }); 186 } 187 188 private WindowManager.LayoutParams getBarLayoutParams(int rotation) { 189 WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation); 190 lp.paramsForRotation = new WindowManager.LayoutParams[4]; 191 for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) { 192 lp.paramsForRotation[rot] = getBarLayoutParamsForRotation(rot); 193 } 194 return lp; 195 } 196 197 private WindowManager.LayoutParams getBarLayoutParamsForRotation(int rotation) { 198 int height = mBarHeight; 199 if (INSETS_LAYOUT_GENERALIZATION) { 200 switch (rotation) { 201 case ROTATION_UNDEFINED: 202 case Surface.ROTATION_0: 203 case Surface.ROTATION_180: 204 height = SystemBarUtils.getStatusBarHeightForRotation( 205 mContext, Surface.ROTATION_0); 206 break; 207 case Surface.ROTATION_90: 208 height = SystemBarUtils.getStatusBarHeightForRotation( 209 mContext, Surface.ROTATION_90); 210 break; 211 case Surface.ROTATION_270: 212 height = SystemBarUtils.getStatusBarHeightForRotation( 213 mContext, Surface.ROTATION_270); 214 break; 215 } 216 } 217 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 218 WindowManager.LayoutParams.MATCH_PARENT, 219 height, 220 WindowManager.LayoutParams.TYPE_STATUS_BAR, 221 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 222 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 223 | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, 224 PixelFormat.TRANSLUCENT); 225 lp.privateFlags |= PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC; 226 lp.token = new Binder(); 227 lp.gravity = Gravity.TOP; 228 lp.setFitInsetsTypes(0 /* types */); 229 lp.setTitle("StatusBar"); 230 lp.packageName = mContext.getPackageName(); 231 lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 232 return lp; 233 234 } 235 236 private void calculateStatusBarLocationsForAllRotations() { 237 Rect[] bounds = new Rect[4]; 238 bounds[0] = mContentInsetsProvider 239 .getBoundingRectForPrivacyChipForRotation(ROTATION_NONE); 240 bounds[1] = mContentInsetsProvider 241 .getBoundingRectForPrivacyChipForRotation(ROTATION_LANDSCAPE); 242 bounds[2] = mContentInsetsProvider 243 .getBoundingRectForPrivacyChipForRotation(ROTATION_UPSIDE_DOWN); 244 bounds[3] = mContentInsetsProvider 245 .getBoundingRectForPrivacyChipForRotation(ROTATION_SEASCAPE); 246 247 try { 248 mIWindowManager.updateStaticPrivacyIndicatorBounds(mContext.getDisplayId(), bounds); 249 } catch (RemoteException e) { 250 //Swallow 251 } 252 } 253 254 /** Set force status bar visible. */ 255 public void setForceStatusBarVisible(boolean forceStatusBarVisible) { 256 mCurrentState.mForceStatusBarVisible = forceStatusBarVisible; 257 apply(mCurrentState); 258 } 259 260 /** 261 * Sets whether an ongoing process requires the status bar to be forced visible. 262 * 263 * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing 264 * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to 265 * false but this method is set to true, then the status bar **will** be visible. 266 * 267 * TODO(b/195839150): We should likely merge this method and 268 * {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead. 269 */ 270 public void setOngoingProcessRequiresStatusBarVisible(boolean visible) { 271 mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible; 272 apply(mCurrentState); 273 } 274 275 /** 276 * Set whether a launch animation is currently running. If true, this will ensure that the 277 * window matches its parent height so that the animation is not clipped by the normal status 278 * bar height. 279 */ 280 private void setLaunchAnimationRunning(boolean isLaunchAnimationRunning) { 281 if (isLaunchAnimationRunning == mCurrentState.mIsLaunchAnimationRunning) { 282 return; 283 } 284 285 mCurrentState.mIsLaunchAnimationRunning = isLaunchAnimationRunning; 286 apply(mCurrentState); 287 } 288 289 private void applyHeight(State state) { 290 mLpChanged.height = 291 state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : mBarHeight; 292 } 293 294 private void apply(State state) { 295 applyForceStatusBarVisibleFlag(state); 296 applyHeight(state); 297 if (mLp != null && mLp.copyFrom(mLpChanged) != 0) { 298 mWindowManager.updateViewLayout(mStatusBarWindowView, mLp); 299 } 300 } 301 302 private static class State { 303 boolean mForceStatusBarVisible; 304 boolean mIsLaunchAnimationRunning; 305 boolean mOngoingProcessRequiresStatusBarVisible; 306 } 307 308 private void applyForceStatusBarVisibleFlag(State state) { 309 if (state.mForceStatusBarVisible 310 || state.mIsLaunchAnimationRunning 311 // Don't force-show the status bar if the user has already dismissed it. 312 || state.mOngoingProcessRequiresStatusBarVisible) { 313 mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; 314 } else { 315 mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; 316 } 317 } 318 } 319