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