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.view.WindowInsets.Type.mandatorySystemGestures; 20 import static android.view.WindowInsets.Type.statusBars; 21 import static android.view.WindowInsets.Type.tappableElement; 22 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 23 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC; 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.Insets; 33 import android.graphics.PixelFormat; 34 import android.graphics.Rect; 35 import android.os.Binder; 36 import android.os.RemoteException; 37 import android.os.Trace; 38 import android.util.Log; 39 import android.view.DisplayCutout; 40 import android.view.Gravity; 41 import android.view.IWindowManager; 42 import android.view.InsetsFrameProvider; 43 import android.view.Surface; 44 import android.view.View; 45 import android.view.ViewGroup; 46 import android.view.WindowInsets; 47 import android.view.WindowManager; 48 49 import com.android.internal.policy.SystemBarUtils; 50 import com.android.systemui.R; 51 import com.android.systemui.animation.ActivityLaunchAnimator; 52 import com.android.systemui.animation.DelegateLaunchAnimatorController; 53 import com.android.systemui.dagger.SysUISingleton; 54 import com.android.systemui.dagger.qualifiers.Main; 55 import com.android.systemui.fragments.FragmentHostManager; 56 import com.android.systemui.fragments.FragmentService; 57 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; 58 import com.android.systemui.unfold.UnfoldTransitionProgressProvider; 59 import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener; 60 61 import java.util.Optional; 62 63 import javax.inject.Inject; 64 65 /** 66 * Encapsulates all logic for the status bar window state management. 67 */ 68 @SysUISingleton 69 public class StatusBarWindowController { 70 private static final String TAG = "StatusBarWindowController"; 71 private static final boolean DEBUG = false; 72 73 private final Context mContext; 74 private final WindowManager mWindowManager; 75 private final IWindowManager mIWindowManager; 76 private final StatusBarContentInsetsProvider mContentInsetsProvider; 77 private int mBarHeight = -1; 78 private final State mCurrentState = new State(); 79 private boolean mIsAttached; 80 81 private final ViewGroup mStatusBarWindowView; 82 private final FragmentService mFragmentService; 83 // The container in which we should run launch animations started from the status bar and 84 // expanding into the opening window. 85 private final ViewGroup mLaunchAnimationContainer; 86 private WindowManager.LayoutParams mLp; 87 private final WindowManager.LayoutParams mLpChanged; 88 private final Binder mInsetsSourceOwner = new Binder(); 89 90 @Inject StatusBarWindowController( Context context, @StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView, WindowManager windowManager, IWindowManager iWindowManager, StatusBarContentInsetsProvider contentInsetsProvider, FragmentService fragmentService, @Main Resources resources, Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider)91 public StatusBarWindowController( 92 Context context, 93 @StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView, 94 WindowManager windowManager, 95 IWindowManager iWindowManager, 96 StatusBarContentInsetsProvider contentInsetsProvider, 97 FragmentService fragmentService, 98 @Main Resources resources, 99 Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) { 100 mContext = context; 101 mWindowManager = windowManager; 102 mIWindowManager = iWindowManager; 103 mContentInsetsProvider = contentInsetsProvider; 104 mStatusBarWindowView = statusBarWindowView; 105 mFragmentService = fragmentService; 106 mLaunchAnimationContainer = mStatusBarWindowView.findViewById( 107 R.id.status_bar_launch_animation_container); 108 mLpChanged = new WindowManager.LayoutParams(); 109 110 if (mBarHeight < 0) { 111 mBarHeight = SystemBarUtils.getStatusBarHeight(mContext); 112 } 113 unfoldTransitionProgressProvider.ifPresent( 114 unfoldProgressProvider -> unfoldProgressProvider.addCallback( 115 new JankMonitorTransitionProgressListener( 116 /* attachedViewProvider=*/ () -> mStatusBarWindowView))); 117 } 118 getStatusBarHeight()119 public int getStatusBarHeight() { 120 return mBarHeight; 121 } 122 123 /** 124 * Rereads the status bar height and reapplys the current state if the height 125 * is different. 126 */ refreshStatusBarHeight()127 public void refreshStatusBarHeight() { 128 int heightFromConfig = SystemBarUtils.getStatusBarHeight(mContext); 129 130 if (mBarHeight != heightFromConfig) { 131 mBarHeight = heightFromConfig; 132 apply(mCurrentState); 133 } 134 135 if (DEBUG) Log.v(TAG, "defineSlots"); 136 } 137 138 /** 139 * Adds the status bar view to the window manager. 140 */ attach()141 public void attach() { 142 // Now that the status bar window encompasses the sliding panel and its 143 // translucent backdrop, the entire thing is made TRANSLUCENT and is 144 // hardware-accelerated. 145 Trace.beginSection("StatusBarWindowController.getBarLayoutParams"); 146 mLp = getBarLayoutParams(mContext.getDisplay().getRotation()); 147 Trace.endSection(); 148 149 mWindowManager.addView(mStatusBarWindowView, mLp); 150 mLpChanged.copyFrom(mLp); 151 152 mContentInsetsProvider.addCallback(this::calculateStatusBarLocationsForAllRotations); 153 calculateStatusBarLocationsForAllRotations(); 154 mIsAttached = true; 155 apply(mCurrentState); 156 } 157 158 /** Adds the given view to the status bar window view. */ addViewToWindow(View view, ViewGroup.LayoutParams layoutParams)159 public void addViewToWindow(View view, ViewGroup.LayoutParams layoutParams) { 160 mStatusBarWindowView.addView(view, layoutParams); 161 } 162 163 /** Returns the status bar window's background view. */ getBackgroundView()164 public View getBackgroundView() { 165 return mStatusBarWindowView.findViewById(R.id.status_bar_container); 166 } 167 168 /** Returns a fragment host manager for the status bar window view. */ getFragmentHostManager()169 public FragmentHostManager getFragmentHostManager() { 170 return mFragmentService.getFragmentHostManager(mStatusBarWindowView); 171 } 172 173 /** 174 * Provides an updated animation controller if we're animating a view in the status bar. 175 * 176 * This is needed because we have to make sure that the status bar window matches the full 177 * screen during the animation and that we are expanding the view below the other status bar 178 * text. 179 * 180 * @param rootView the root view of the animation 181 * @param animationController the default animation controller to use 182 * @return If the animation is on a view in the status bar, returns an Optional containing an 183 * updated animation controller that handles status-bar-related animation details. Returns an 184 * empty optional if the animation is *not* on a view in the status bar. 185 */ wrapAnimationControllerIfInStatusBar( View rootView, ActivityLaunchAnimator.Controller animationController)186 public Optional<ActivityLaunchAnimator.Controller> wrapAnimationControllerIfInStatusBar( 187 View rootView, ActivityLaunchAnimator.Controller animationController) { 188 if (rootView != mStatusBarWindowView) { 189 return Optional.empty(); 190 } 191 192 animationController.setLaunchContainer(mLaunchAnimationContainer); 193 return Optional.of(new DelegateLaunchAnimatorController(animationController) { 194 @Override 195 public void onLaunchAnimationStart(boolean isExpandingFullyAbove) { 196 getDelegate().onLaunchAnimationStart(isExpandingFullyAbove); 197 setLaunchAnimationRunning(true); 198 } 199 200 @Override 201 public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) { 202 getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove); 203 setLaunchAnimationRunning(false); 204 } 205 }); 206 } 207 208 private WindowManager.LayoutParams getBarLayoutParams(int rotation) { 209 WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation); 210 lp.paramsForRotation = new WindowManager.LayoutParams[4]; 211 for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) { 212 lp.paramsForRotation[rot] = getBarLayoutParamsForRotation(rot); 213 } 214 return lp; 215 } 216 217 private WindowManager.LayoutParams getBarLayoutParamsForRotation(int rotation) { 218 int height = SystemBarUtils.getStatusBarHeightForRotation(mContext, rotation); 219 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 220 WindowManager.LayoutParams.MATCH_PARENT, 221 height, 222 WindowManager.LayoutParams.TYPE_STATUS_BAR, 223 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 224 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 225 | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, 226 PixelFormat.TRANSLUCENT); 227 lp.privateFlags |= PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC; 228 lp.token = new Binder(); 229 lp.gravity = Gravity.TOP; 230 lp.setFitInsetsTypes(0 /* types */); 231 lp.setTitle("StatusBar"); 232 lp.packageName = mContext.getPackageName(); 233 lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 234 final InsetsFrameProvider gestureInsetsProvider = 235 new InsetsFrameProvider(mInsetsSourceOwner, 0, mandatorySystemGestures()); 236 final int safeTouchRegionHeight = mContext.getResources().getDimensionPixelSize( 237 com.android.internal.R.dimen.display_cutout_touchable_region_size); 238 if (safeTouchRegionHeight > 0) { 239 gestureInsetsProvider.setMinimalInsetsSizeInDisplayCutoutSafe( 240 Insets.of(0, safeTouchRegionHeight, 0, 0)); 241 } 242 lp.providedInsets = new InsetsFrameProvider[] { 243 new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()) 244 .setInsetsSize(getInsets(height)), 245 new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()) 246 .setInsetsSize(getInsets(height)), 247 gestureInsetsProvider 248 }; 249 return lp; 250 251 } 252 253 private void calculateStatusBarLocationsForAllRotations() { 254 Rect[] bounds = new Rect[4]; 255 final DisplayCutout displayCutout = mContext.getDisplay().getCutout(); 256 bounds[0] = mContentInsetsProvider 257 .getBoundingRectForPrivacyChipForRotation(ROTATION_NONE, displayCutout); 258 bounds[1] = mContentInsetsProvider 259 .getBoundingRectForPrivacyChipForRotation(ROTATION_LANDSCAPE, displayCutout); 260 bounds[2] = mContentInsetsProvider 261 .getBoundingRectForPrivacyChipForRotation(ROTATION_UPSIDE_DOWN, displayCutout); 262 bounds[3] = mContentInsetsProvider 263 .getBoundingRectForPrivacyChipForRotation(ROTATION_SEASCAPE, displayCutout); 264 265 try { 266 mIWindowManager.updateStaticPrivacyIndicatorBounds(mContext.getDisplayId(), bounds); 267 } catch (RemoteException e) { 268 //Swallow 269 } 270 } 271 272 /** Set force status bar visible. */ 273 public void setForceStatusBarVisible(boolean forceStatusBarVisible) { 274 mCurrentState.mForceStatusBarVisible = forceStatusBarVisible; 275 apply(mCurrentState); 276 } 277 278 /** 279 * Sets whether an ongoing process requires the status bar to be forced visible. 280 * 281 * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing 282 * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to 283 * false but this method is set to true, then the status bar **will** be visible. 284 * 285 * TODO(b/195839150): We should likely merge this method and 286 * {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead. 287 */ 288 public void setOngoingProcessRequiresStatusBarVisible(boolean visible) { 289 mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible; 290 apply(mCurrentState); 291 } 292 293 /** 294 * Set whether a launch animation is currently running. If true, this will ensure that the 295 * window matches its parent height so that the animation is not clipped by the normal status 296 * bar height. 297 */ 298 private void setLaunchAnimationRunning(boolean isLaunchAnimationRunning) { 299 if (isLaunchAnimationRunning == mCurrentState.mIsLaunchAnimationRunning) { 300 return; 301 } 302 303 mCurrentState.mIsLaunchAnimationRunning = isLaunchAnimationRunning; 304 apply(mCurrentState); 305 } 306 307 private void applyHeight(State state) { 308 mLpChanged.height = 309 state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : mBarHeight; 310 for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) { 311 int height = SystemBarUtils.getStatusBarHeightForRotation(mContext, rot); 312 mLpChanged.paramsForRotation[rot].height = 313 state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : height; 314 // The status bar height could change at runtime if one display has a cutout while 315 // another doesn't (like some foldables). It could also change when using debug cutouts. 316 // So, we need to re-fetch the height and re-apply it to the insets each time to avoid 317 // bugs like b/290300359. 318 InsetsFrameProvider[] providers = mLpChanged.paramsForRotation[rot].providedInsets; 319 if (providers != null) { 320 for (InsetsFrameProvider provider : providers) { 321 provider.setInsetsSize(getInsets(height)); 322 } 323 } 324 } 325 } 326 327 /** 328 * Get the insets that should be applied to the status bar window given the current status bar 329 * height. 330 * 331 * The status bar window height can sometimes be full-screen (see {@link #applyHeight(State)}. 332 * However, the status bar *insets* should *not* be full-screen, because this would prevent apps 333 * from drawing any content and can cause animations to be cancelled (see b/283958440). Instead, 334 * the status bar insets should always be equal to the space occupied by the actual status bar 335 * content -- setting the insets correctly will prevent window manager from unnecessarily 336 * re-drawing this window and other windows. This method provides the correct insets. 337 */ 338 private Insets getInsets(int height) { 339 return Insets.of(0, height, 0, 0); 340 } 341 342 private void apply(State state) { 343 if (!mIsAttached) { 344 return; 345 } 346 applyForceStatusBarVisibleFlag(state); 347 applyHeight(state); 348 if (mLp != null && mLp.copyFrom(mLpChanged) != 0) { 349 mWindowManager.updateViewLayout(mStatusBarWindowView, mLp); 350 } 351 } 352 353 private static class State { 354 boolean mForceStatusBarVisible; 355 boolean mIsLaunchAnimationRunning; 356 boolean mOngoingProcessRequiresStatusBarVisible; 357 } 358 359 private void applyForceStatusBarVisibleFlag(State state) { 360 if (state.mForceStatusBarVisible 361 || state.mIsLaunchAnimationRunning 362 // Don't force-show the status bar if the user has already dismissed it. 363 || state.mOngoingProcessRequiresStatusBarVisible) { 364 mLpChanged.forciblyShownTypes |= WindowInsets.Type.statusBars(); 365 } else { 366 mLpChanged.forciblyShownTypes &= ~WindowInsets.Type.statusBars(); 367 } 368 } 369 } 370