1 /*
2  * Copyright (C) 2018 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.server.wm;
18 
19 import static com.android.server.wm.BarControllerProto.STATE;
20 import static com.android.server.wm.BarControllerProto.TRANSIENT_STATE;
21 
22 import android.annotation.NonNull;
23 import android.app.StatusBarManager;
24 import android.graphics.Rect;
25 import android.os.Handler;
26 import android.os.Message;
27 import android.os.SystemClock;
28 import android.util.Slog;
29 import android.util.proto.ProtoOutputStream;
30 import android.view.View;
31 import android.view.ViewRootImpl;
32 import android.view.WindowManager;
33 
34 import com.android.server.LocalServices;
35 import com.android.server.UiThread;
36 import com.android.server.statusbar.StatusBarManagerInternal;
37 
38 import java.io.PrintWriter;
39 
40 /**
41  * Controls state/behavior specific to a system bar window.
42  */
43 public class BarController {
44     private static final boolean DEBUG = false;
45 
46     private static final int TRANSIENT_BAR_NONE = 0;
47     private static final int TRANSIENT_BAR_SHOW_REQUESTED = 1;
48     private static final int TRANSIENT_BAR_SHOWING = 2;
49     private static final int TRANSIENT_BAR_HIDING = 3;
50 
51     private static final int TRANSLUCENT_ANIMATION_DELAY_MS = 1000;
52 
53     private static final int MSG_NAV_BAR_VISIBILITY_CHANGED = 1;
54 
55     protected final String mTag;
56     protected final int mDisplayId;
57     private final int mTransientFlag;
58     private final int mUnhideFlag;
59     private final int mTranslucentFlag;
60     private final int mTransparentFlag;
61     private final int mStatusBarManagerId;
62     private final int mTranslucentWmFlag;
63     private final int mWindowType;
64     protected final Handler mHandler;
65     private final Object mServiceAquireLock = new Object();
66     private StatusBarManagerInternal mStatusBarInternal;
67 
68     protected WindowState mWin;
69     private @StatusBarManager.WindowVisibleState int mState =
70             StatusBarManager.WINDOW_STATE_SHOWING;
71     private int mTransientBarState;
72     private boolean mPendingShow;
73     private long mLastTranslucent;
74     private boolean mShowTransparent;
75     private boolean mSetUnHideFlagWhenNextTransparent;
76     private boolean mNoAnimationOnNextShow;
77     private final Rect mContentFrame = new Rect();
78 
79     private OnBarVisibilityChangedListener mVisibilityChangeListener;
80 
BarController(String tag, int displayId, int transientFlag, int unhideFlag, int translucentFlag, int statusBarManagerId, int windowType, int translucentWmFlag, int transparentFlag)81     BarController(String tag, int displayId, int transientFlag, int unhideFlag, int translucentFlag,
82             int statusBarManagerId, int windowType, int translucentWmFlag, int transparentFlag) {
83         mTag = "BarController." + tag;
84         mDisplayId = displayId;
85         mTransientFlag = transientFlag;
86         mUnhideFlag = unhideFlag;
87         mTranslucentFlag = translucentFlag;
88         mStatusBarManagerId = statusBarManagerId;
89         mWindowType = windowType;
90         mTranslucentWmFlag = translucentWmFlag;
91         mTransparentFlag = transparentFlag;
92         mHandler = new BarHandler();
93     }
94 
setWindow(WindowState win)95     void setWindow(WindowState win) {
96         if (ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL) {
97             // BarController gets replaced with InsetsPolicy in the full insets mode.
98             return;
99         }
100         mWin = win;
101     }
102 
103     /**
104      * Sets the frame within which the bar will display its content.
105      *
106      * This is used to determine if letterboxes interfere with the display of such content.
107      */
setContentFrame(Rect frame)108     void setContentFrame(Rect frame) {
109         mContentFrame.set(frame);
110     }
111 
setShowTransparent(boolean transparent)112     void setShowTransparent(boolean transparent) {
113         if (transparent != mShowTransparent) {
114             mShowTransparent = transparent;
115             mSetUnHideFlagWhenNextTransparent = transparent;
116             mNoAnimationOnNextShow = true;
117         }
118     }
119 
showTransient()120     void showTransient() {
121         if (mWin != null) {
122             setTransientBarState(TRANSIENT_BAR_SHOW_REQUESTED);
123         }
124     }
125 
isTransientShowing()126     boolean isTransientShowing() {
127         return mTransientBarState == TRANSIENT_BAR_SHOWING;
128     }
129 
isTransientShowRequested()130     boolean isTransientShowRequested() {
131         return mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED;
132     }
133 
wasRecentlyTranslucent()134     boolean wasRecentlyTranslucent() {
135         return (SystemClock.uptimeMillis() - mLastTranslucent) < TRANSLUCENT_ANIMATION_DELAY_MS;
136     }
137 
adjustSystemUiVisibilityLw(int oldVis, int vis)138     void adjustSystemUiVisibilityLw(int oldVis, int vis) {
139         if (mWin != null && mTransientBarState == TRANSIENT_BAR_SHOWING
140                 && (vis & mTransientFlag) == 0) {
141             // sysui requests hide
142             setTransientBarState(TRANSIENT_BAR_HIDING);
143             setBarShowingLw(false);
144         } else if (mWin != null && (oldVis & mUnhideFlag) != 0 && (vis & mUnhideFlag) == 0) {
145             // sysui ready to unhide
146             setBarShowingLw(true);
147         }
148     }
149 
applyTranslucentFlagLw(WindowState win, int vis, int oldVis)150     int applyTranslucentFlagLw(WindowState win, int vis, int oldVis) {
151         if (mWin != null) {
152             if (win != null) {
153                 int fl = PolicyControl.getWindowFlags(win, null);
154                 if ((fl & mTranslucentWmFlag) != 0) {
155                     vis |= mTranslucentFlag;
156                 } else {
157                     vis &= ~mTranslucentFlag;
158                 }
159                 if ((fl & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
160                         && isTransparentAllowed(win)) {
161                     vis |= mTransparentFlag;
162                 } else {
163                     vis &= ~mTransparentFlag;
164                 }
165             } else {
166                 vis = (vis & ~mTranslucentFlag) | (oldVis & mTranslucentFlag);
167                 vis = (vis & ~mTransparentFlag) | (oldVis & mTransparentFlag);
168             }
169         }
170         return vis;
171     }
172 
getContentFrame(@onNull WindowState win)173     private Rect getContentFrame(@NonNull WindowState win) {
174         final Rect rotatedContentFrame = win.mToken.getFixedRotationBarContentFrame(mWindowType);
175         return rotatedContentFrame != null ? rotatedContentFrame : mContentFrame;
176     }
177 
isLightAppearanceAllowed(WindowState win)178     boolean isLightAppearanceAllowed(WindowState win) {
179         if (win == null) {
180             return true;
181         }
182         return !win.isLetterboxedOverlappingWith(getContentFrame(win));
183     }
184 
isTransparentAllowed(WindowState win)185     boolean isTransparentAllowed(WindowState win) {
186         if (win == null) {
187             return true;
188         }
189         return win.letterboxNotIntersectsOrFullyContains(getContentFrame(win));
190     }
191 
setBarShowingLw(final boolean show)192     boolean setBarShowingLw(final boolean show) {
193         if (mWin == null) return false;
194         if (show && mTransientBarState == TRANSIENT_BAR_HIDING) {
195             mPendingShow = true;
196             return false;
197         }
198         final boolean wasVis = mWin.isVisibleLw();
199         final boolean wasAnim = mWin.isAnimatingLw();
200         final boolean skipAnim = skipAnimation();
201         final boolean change = show ? mWin.showLw(!mNoAnimationOnNextShow && !skipAnim)
202                 : mWin.hideLw(!mNoAnimationOnNextShow && !skipAnim);
203         mNoAnimationOnNextShow = false;
204         final int state = computeStateLw(wasVis, wasAnim, mWin, change);
205         final boolean stateChanged = updateStateLw(state);
206 
207         if (change && (mVisibilityChangeListener != null)) {
208             mHandler.obtainMessage(MSG_NAV_BAR_VISIBILITY_CHANGED, show ? 1 : 0, 0).sendToTarget();
209         }
210 
211         return change || stateChanged;
212     }
213 
setOnBarVisibilityChangedListener(OnBarVisibilityChangedListener listener, boolean invokeWithState)214     void setOnBarVisibilityChangedListener(OnBarVisibilityChangedListener listener,
215             boolean invokeWithState) {
216         mVisibilityChangeListener = listener;
217         if (invokeWithState) {
218             // Optionally report the initial window state for initialization purposes
219             mHandler.obtainMessage(MSG_NAV_BAR_VISIBILITY_CHANGED,
220                     (mState == StatusBarManager.WINDOW_STATE_SHOWING) ? 1 : 0, 0).sendToTarget();
221         }
222     }
223 
skipAnimation()224     protected boolean skipAnimation() {
225         return !mWin.isDrawnLw();
226     }
227 
computeStateLw( boolean wasVis, boolean wasAnim, WindowState win, boolean change)228     private @StatusBarManager.WindowVisibleState int computeStateLw(
229             boolean wasVis, boolean wasAnim, WindowState win, boolean change) {
230         if (win.isDrawnLw()) {
231             final boolean vis = win.isVisibleLw();
232             final boolean anim = win.isAnimatingLw();
233             if (mState == StatusBarManager.WINDOW_STATE_HIDING && !change && !vis) {
234                 return StatusBarManager.WINDOW_STATE_HIDDEN;
235             } else if (mState == StatusBarManager.WINDOW_STATE_HIDDEN && vis) {
236                 return StatusBarManager.WINDOW_STATE_SHOWING;
237             } else if (change) {
238                 if (wasVis && vis && !wasAnim && anim) {
239                     return StatusBarManager.WINDOW_STATE_HIDING;
240                 } else {
241                     return StatusBarManager.WINDOW_STATE_SHOWING;
242                 }
243             }
244         }
245         return mState;
246     }
247 
updateStateLw(@tatusBarManager.WindowVisibleState final int state)248     private boolean updateStateLw(@StatusBarManager.WindowVisibleState final int state) {
249         if (mWin != null && state != mState) {
250             mState = state;
251             if (DEBUG) Slog.d(mTag, "mState: " + StatusBarManager.windowStateToString(state));
252             mHandler.post(new Runnable() {
253                 @Override
254                 public void run() {
255                     StatusBarManagerInternal statusbar = getStatusBarInternal();
256                     if (statusbar != null) {
257                         statusbar.setWindowState(mDisplayId, mStatusBarManagerId, state);
258                     }
259                 }
260             });
261             return true;
262         }
263         return false;
264     }
265 
checkHiddenLw()266     boolean checkHiddenLw() {
267         if (mWin != null && mWin.isDrawnLw()) {
268             if (!mWin.isVisibleLw() && !mWin.isAnimatingLw()) {
269                 updateStateLw(StatusBarManager.WINDOW_STATE_HIDDEN);
270             }
271             if (mTransientBarState == TRANSIENT_BAR_HIDING && !mWin.isVisibleLw()) {
272                 // Finished animating out, clean up and reset style
273                 setTransientBarState(TRANSIENT_BAR_NONE);
274                 if (mPendingShow) {
275                     setBarShowingLw(true);
276                     mPendingShow = false;
277                 }
278                 return true;
279             }
280         }
281         return false;
282     }
283 
checkShowTransientBarLw()284     boolean checkShowTransientBarLw() {
285         if (mTransientBarState == TRANSIENT_BAR_SHOWING) {
286             if (DEBUG) Slog.d(mTag, "Not showing transient bar, already shown");
287             return false;
288         } else if (mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED) {
289             if (DEBUG) Slog.d(mTag, "Not showing transient bar, already requested");
290             return false;
291         } else if (mWin == null) {
292             if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar doesn't exist");
293             return false;
294         } else if (mWin.isDisplayedLw()) {
295             if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar already visible");
296             return false;
297         } else {
298             return true;
299         }
300     }
301 
updateVisibilityLw(boolean transientAllowed, int oldVis, int vis)302     int updateVisibilityLw(boolean transientAllowed, int oldVis, int vis) {
303         if (mWin == null) return vis;
304         if (isTransientShowing() || isTransientShowRequested()) { // transient bar requested
305             if (transientAllowed) {
306                 vis |= mTransientFlag;
307                 if ((oldVis & mTransientFlag) == 0) {
308                     vis |= mUnhideFlag;  // tell sysui we're ready to unhide
309                 }
310                 setTransientBarState(TRANSIENT_BAR_SHOWING);  // request accepted
311             } else {
312                 setTransientBarState(TRANSIENT_BAR_NONE);  // request denied
313             }
314         }
315         if (mShowTransparent) {
316             vis |= mTransparentFlag;
317             if (mSetUnHideFlagWhenNextTransparent) {
318                 vis |= mUnhideFlag;
319                 mSetUnHideFlagWhenNextTransparent = false;
320             }
321         }
322         if (mTransientBarState != TRANSIENT_BAR_NONE) {
323             vis |= mTransientFlag;  // ignore clear requests until transition completes
324             vis &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE;  // never show transient bars in low profile
325         }
326         if ((vis & mTranslucentFlag) != 0 || (oldVis & mTranslucentFlag) != 0
327                 || ((vis | oldVis) & mTransparentFlag) != 0) {
328             mLastTranslucent = SystemClock.uptimeMillis();
329         }
330         return vis;
331     }
332 
setTransientBarState(int state)333     private void setTransientBarState(int state) {
334         if (mWin != null && state != mTransientBarState) {
335             if (mTransientBarState == TRANSIENT_BAR_SHOWING || state == TRANSIENT_BAR_SHOWING) {
336                 mLastTranslucent = SystemClock.uptimeMillis();
337             }
338             mTransientBarState = state;
339             if (DEBUG) Slog.d(mTag, "mTransientBarState: " + transientBarStateToString(state));
340         }
341     }
342 
getStatusBarInternal()343     protected StatusBarManagerInternal getStatusBarInternal() {
344         synchronized (mServiceAquireLock) {
345             if (mStatusBarInternal == null) {
346                 mStatusBarInternal = LocalServices.getService(StatusBarManagerInternal.class);
347             }
348             return mStatusBarInternal;
349         }
350     }
351 
transientBarStateToString(int state)352     private static String transientBarStateToString(int state) {
353         if (state == TRANSIENT_BAR_HIDING) return "TRANSIENT_BAR_HIDING";
354         if (state == TRANSIENT_BAR_SHOWING) return "TRANSIENT_BAR_SHOWING";
355         if (state == TRANSIENT_BAR_SHOW_REQUESTED) return "TRANSIENT_BAR_SHOW_REQUESTED";
356         if (state == TRANSIENT_BAR_NONE) return "TRANSIENT_BAR_NONE";
357         throw new IllegalArgumentException("Unknown state " + state);
358     }
359 
dumpDebug(ProtoOutputStream proto, long fieldId)360     void dumpDebug(ProtoOutputStream proto, long fieldId) {
361         final long token = proto.start(fieldId);
362         proto.write(STATE, mState);
363         proto.write(TRANSIENT_STATE, mTransientBarState);
364         proto.end(token);
365     }
366 
dump(PrintWriter pw, String prefix)367     void dump(PrintWriter pw, String prefix) {
368         if (mWin != null) {
369             pw.print(prefix); pw.println(mTag);
370             pw.print(prefix); pw.print("  "); pw.print("mState"); pw.print('=');
371             pw.println(StatusBarManager.windowStateToString(mState));
372             pw.print(prefix); pw.print("  "); pw.print("mTransientBar"); pw.print('=');
373             pw.println(transientBarStateToString(mTransientBarState));
374             pw.print(prefix); pw.print("  mContentFrame="); pw.println(mContentFrame);
375         }
376     }
377 
378     private class BarHandler extends Handler {
BarHandler()379         BarHandler() {
380             super(UiThread.getHandler().getLooper());
381         }
382 
383         @Override
handleMessage(Message msg)384         public void handleMessage(Message msg) {
385             switch (msg.what) {
386                 case MSG_NAV_BAR_VISIBILITY_CHANGED:
387                     final boolean visible = msg.arg1 != 0;
388                     if (mVisibilityChangeListener != null) {
389                         mVisibilityChangeListener.onBarVisibilityChanged(visible);
390                     }
391                     break;
392             }
393         }
394     }
395 
396     interface OnBarVisibilityChangedListener {
onBarVisibilityChanged(boolean visible)397         void onBarVisibilityChanged(boolean visible);
398     }
399 }
400