• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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