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