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