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.animation.ActivityTransitionAnimator; 51 import com.android.systemui.animation.DelegateTransitionAnimatorController; 52 import com.android.systemui.dagger.SysUISingleton; 53 import com.android.systemui.dagger.qualifiers.Main; 54 import com.android.systemui.fragments.FragmentHostManager; 55 import com.android.systemui.fragments.FragmentService; 56 import com.android.systemui.res.R; 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 Trace.beginSection("StatusBarWindowController#refreshStatusBarHeight"); 129 try { 130 int heightFromConfig = SystemBarUtils.getStatusBarHeight(mContext); 131 132 if (mBarHeight != heightFromConfig) { 133 mBarHeight = heightFromConfig; 134 apply(mCurrentState); 135 } 136 137 if (DEBUG) Log.v(TAG, "defineSlots"); 138 } finally { 139 Trace.endSection(); 140 } 141 } 142 143 /** 144 * Adds the status bar view to the window manager. 145 */ attach()146 public void attach() { 147 // Now that the status bar window encompasses the sliding panel and its 148 // translucent backdrop, the entire thing is made TRANSLUCENT and is 149 // hardware-accelerated. 150 Trace.beginSection("StatusBarWindowController.getBarLayoutParams"); 151 mLp = getBarLayoutParams(mContext.getDisplay().getRotation()); 152 Trace.endSection(); 153 154 mWindowManager.addView(mStatusBarWindowView, mLp); 155 mLpChanged.copyFrom(mLp); 156 157 mContentInsetsProvider.addCallback(this::calculateStatusBarLocationsForAllRotations); 158 calculateStatusBarLocationsForAllRotations(); 159 mIsAttached = true; 160 apply(mCurrentState); 161 } 162 163 /** Adds the given view to the status bar window view. */ addViewToWindow(View view, ViewGroup.LayoutParams layoutParams)164 public void addViewToWindow(View view, ViewGroup.LayoutParams layoutParams) { 165 mStatusBarWindowView.addView(view, layoutParams); 166 } 167 168 /** Returns the status bar window's background view. */ getBackgroundView()169 public View getBackgroundView() { 170 return mStatusBarWindowView.findViewById(R.id.status_bar_container); 171 } 172 173 /** Returns a fragment host manager for the status bar window view. */ getFragmentHostManager()174 public FragmentHostManager getFragmentHostManager() { 175 return mFragmentService.getFragmentHostManager(mStatusBarWindowView); 176 } 177 178 /** 179 * Provides an updated animation controller if we're animating a view in the status bar. 180 * 181 * This is needed because we have to make sure that the status bar window matches the full 182 * screen during the animation and that we are expanding the view below the other status bar 183 * text. 184 * 185 * @param rootView the root view of the animation 186 * @param animationController the default animation controller to use 187 * @return If the animation is on a view in the status bar, returns an Optional containing an 188 * updated animation controller that handles status-bar-related animation details. Returns an 189 * empty optional if the animation is *not* on a view in the status bar. 190 */ wrapAnimationControllerIfInStatusBar( View rootView, ActivityTransitionAnimator.Controller animationController)191 public Optional<ActivityTransitionAnimator.Controller> wrapAnimationControllerIfInStatusBar( 192 View rootView, ActivityTransitionAnimator.Controller animationController) { 193 if (rootView != mStatusBarWindowView) { 194 return Optional.empty(); 195 } 196 197 animationController.setTransitionContainer(mLaunchAnimationContainer); 198 return Optional.of(new DelegateTransitionAnimatorController(animationController) { 199 @Override 200 public void onTransitionAnimationStart(boolean isExpandingFullyAbove) { 201 getDelegate().onTransitionAnimationStart(isExpandingFullyAbove); 202 setLaunchAnimationRunning(true); 203 } 204 205 @Override 206 public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) { 207 getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove); 208 setLaunchAnimationRunning(false); 209 } 210 }); 211 } 212 213 private WindowManager.LayoutParams getBarLayoutParams(int rotation) { 214 WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation); 215 lp.paramsForRotation = new WindowManager.LayoutParams[4]; 216 for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) { 217 lp.paramsForRotation[rot] = getBarLayoutParamsForRotation(rot); 218 } 219 return lp; 220 } 221 222 private WindowManager.LayoutParams getBarLayoutParamsForRotation(int rotation) { 223 int height = SystemBarUtils.getStatusBarHeightForRotation(mContext, rotation); 224 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 225 WindowManager.LayoutParams.MATCH_PARENT, 226 height, 227 WindowManager.LayoutParams.TYPE_STATUS_BAR, 228 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 229 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 230 | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, 231 PixelFormat.TRANSLUCENT); 232 lp.privateFlags |= PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC; 233 lp.token = new Binder(); 234 lp.gravity = Gravity.TOP; 235 lp.setFitInsetsTypes(0 /* types */); 236 lp.setTitle("StatusBar"); 237 lp.packageName = mContext.getPackageName(); 238 lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 239 final InsetsFrameProvider gestureInsetsProvider = 240 new InsetsFrameProvider(mInsetsSourceOwner, 0, mandatorySystemGestures()); 241 final int safeTouchRegionHeight = mContext.getResources().getDimensionPixelSize( 242 com.android.internal.R.dimen.display_cutout_touchable_region_size); 243 if (safeTouchRegionHeight > 0) { 244 gestureInsetsProvider.setMinimalInsetsSizeInDisplayCutoutSafe( 245 Insets.of(0, safeTouchRegionHeight, 0, 0)); 246 } 247 lp.providedInsets = new InsetsFrameProvider[] { 248 new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()) 249 .setInsetsSize(getInsets(height)), 250 new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()) 251 .setInsetsSize(getInsets(height)), 252 gestureInsetsProvider 253 }; 254 return lp; 255 256 } 257 258 private void calculateStatusBarLocationsForAllRotations() { 259 Rect[] bounds = new Rect[4]; 260 final DisplayCutout displayCutout = mContext.getDisplay().getCutout(); 261 bounds[0] = mContentInsetsProvider 262 .getBoundingRectForPrivacyChipForRotation(ROTATION_NONE, displayCutout); 263 bounds[1] = mContentInsetsProvider 264 .getBoundingRectForPrivacyChipForRotation(ROTATION_LANDSCAPE, displayCutout); 265 bounds[2] = mContentInsetsProvider 266 .getBoundingRectForPrivacyChipForRotation(ROTATION_UPSIDE_DOWN, displayCutout); 267 bounds[3] = mContentInsetsProvider 268 .getBoundingRectForPrivacyChipForRotation(ROTATION_SEASCAPE, displayCutout); 269 270 try { 271 mIWindowManager.updateStaticPrivacyIndicatorBounds(mContext.getDisplayId(), bounds); 272 } catch (RemoteException e) { 273 //Swallow 274 } 275 } 276 277 /** Set force status bar visible. */ 278 public void setForceStatusBarVisible(boolean forceStatusBarVisible) { 279 mCurrentState.mForceStatusBarVisible = forceStatusBarVisible; 280 apply(mCurrentState); 281 } 282 283 /** 284 * Sets whether an ongoing process requires the status bar to be forced visible. 285 * 286 * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing 287 * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to 288 * false but this method is set to true, then the status bar **will** be visible. 289 * 290 * TODO(b/195839150): We should likely merge this method and 291 * {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead. 292 */ 293 public void setOngoingProcessRequiresStatusBarVisible(boolean visible) { 294 mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible; 295 apply(mCurrentState); 296 } 297 298 /** 299 * Set whether a launch animation is currently running. If true, this will ensure that the 300 * window matches its parent height so that the animation is not clipped by the normal status 301 * bar height. 302 */ 303 private void setLaunchAnimationRunning(boolean isLaunchAnimationRunning) { 304 if (isLaunchAnimationRunning == mCurrentState.mIsLaunchAnimationRunning) { 305 return; 306 } 307 308 mCurrentState.mIsLaunchAnimationRunning = isLaunchAnimationRunning; 309 apply(mCurrentState); 310 } 311 312 private void applyHeight(State state) { 313 mLpChanged.height = 314 state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : mBarHeight; 315 for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) { 316 int height = SystemBarUtils.getStatusBarHeightForRotation(mContext, rot); 317 mLpChanged.paramsForRotation[rot].height = 318 state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : height; 319 // The status bar height could change at runtime if one display has a cutout while 320 // another doesn't (like some foldables). It could also change when using debug cutouts. 321 // So, we need to re-fetch the height and re-apply it to the insets each time to avoid 322 // bugs like b/290300359. 323 InsetsFrameProvider[] providers = mLpChanged.paramsForRotation[rot].providedInsets; 324 if (providers != null) { 325 for (InsetsFrameProvider provider : providers) { 326 provider.setInsetsSize(getInsets(height)); 327 } 328 } 329 } 330 } 331 332 /** 333 * Get the insets that should be applied to the status bar window given the current status bar 334 * height. 335 * 336 * The status bar window height can sometimes be full-screen (see {@link #applyHeight(State)}. 337 * However, the status bar *insets* should *not* be full-screen, because this would prevent apps 338 * from drawing any content and can cause animations to be cancelled (see b/283958440). Instead, 339 * the status bar insets should always be equal to the space occupied by the actual status bar 340 * content -- setting the insets correctly will prevent window manager from unnecessarily 341 * re-drawing this window and other windows. This method provides the correct insets. 342 */ 343 private Insets getInsets(int height) { 344 return Insets.of(0, height, 0, 0); 345 } 346 347 private void apply(State state) { 348 if (!mIsAttached) { 349 return; 350 } 351 applyForceStatusBarVisibleFlag(state); 352 applyHeight(state); 353 if (mLp != null && mLp.copyFrom(mLpChanged) != 0) { 354 mWindowManager.updateViewLayout(mStatusBarWindowView, mLp); 355 } 356 } 357 358 private static class State { 359 boolean mForceStatusBarVisible; 360 boolean mIsLaunchAnimationRunning; 361 boolean mOngoingProcessRequiresStatusBarVisible; 362 } 363 364 private void applyForceStatusBarVisibleFlag(State state) { 365 if (state.mForceStatusBarVisible 366 || state.mIsLaunchAnimationRunning 367 // Don't force-show the status bar if the user has already dismissed it. 368 || state.mOngoingProcessRequiresStatusBarVisible) { 369 mLpChanged.forciblyShownTypes |= WindowInsets.Type.statusBars(); 370 } else { 371 mLpChanged.forciblyShownTypes &= ~WindowInsets.Type.statusBars(); 372 } 373 } 374 } 375