SystemBarHelper.java revision 8fc85be65673f6ec8b3202d8b3fc644a3adeeded
1/*
2 * Copyright (C) 2015 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
17package com.android.setupwizardlib.util;
18
19import android.annotation.SuppressLint;
20import android.annotation.TargetApi;
21import android.app.Dialog;
22import android.content.Context;
23import android.os.Build.VERSION;
24import android.os.Build.VERSION_CODES;
25import android.os.Handler;
26import android.view.View;
27import android.view.ViewGroup;
28import android.view.Window;
29import android.view.WindowInsets;
30import android.view.WindowManager;
31
32import com.android.setupwizardlib.R;
33
34/**
35 * A helper class to manage the system navigation bar and status bar. This will add various
36 * systemUiVisibility flags to the given Window or View to make them follow the Setup Wizard style.
37 *
38 * When the useImmersiveMode intent extra is true, a screen in Setup Wizard should hide the system
39 * bars using methods from this class. For Lollipop, {@link #hideSystemBars(android.view.Window)}
40 * will completely hide the system navigation bar and change the status bar to transparent, and
41 * layout the screen contents (usually the illustration) behind it.
42 */
43public class SystemBarHelper {
44
45    @SuppressLint("InlinedApi")
46    private static final int DEFAULT_IMMERSIVE_FLAGS =
47            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
48            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
49            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
50            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
51
52    @SuppressLint("InlinedApi")
53    private static final int DIALOG_IMMERSIVE_FLAGS =
54            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
55            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
56
57    /**
58     * Needs to be equal to View.STATUS_BAR_DISABLE_BACK
59     */
60    private static final int STATUS_BAR_DISABLE_BACK = 0x00400000;
61
62    /**
63     * Hide the navigation bar for a dialog.
64     *
65     * This will only take effect in versions Lollipop or above. Otherwise this is a no-op.
66     */
67    public static void hideSystemBars(final Dialog dialog) {
68        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
69            final Window window = dialog.getWindow();
70            temporarilyDisableDialogFocus(window);
71            addImmersiveFlagsToWindow(window, DIALOG_IMMERSIVE_FLAGS);
72            addImmersiveFlagsToDecorView(window, new Handler(), DIALOG_IMMERSIVE_FLAGS);
73        }
74    }
75
76    /**
77     * Hide the navigation bar, and make the color of the status and navigation bars transparent,
78     * and specify the LAYOUT_FULLSCREEN flag so that the content is laid-out behind the transparent
79     * status bar. This is commonly used with Activity.getWindow() to make the navigation and status
80     * bars follow the Setup Wizard style.
81     *
82     * This will only take effect in versions Lollipop or above. Otherwise this is a no-op.
83     */
84    public static void hideSystemBars(final Window window) {
85        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
86            addImmersiveFlagsToWindow(window, DEFAULT_IMMERSIVE_FLAGS);
87            addImmersiveFlagsToDecorView(window, new Handler(), DEFAULT_IMMERSIVE_FLAGS);
88        }
89    }
90
91    /**
92     * Convenience method to add a visibility flag in addition to the existing ones.
93     */
94    public static void addVisibilityFlag(final View view, final int flag) {
95        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
96            final int vis = view.getSystemUiVisibility();
97            view.setSystemUiVisibility(vis | flag);
98        }
99    }
100
101    /**
102     * Convenience method to add a visibility flag in addition to the existing ones.
103     */
104    public static void addVisibilityFlag(final Window window, final int flag) {
105        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
106            WindowManager.LayoutParams attrs = window.getAttributes();
107            attrs.systemUiVisibility |= flag;
108            window.setAttributes(attrs);
109        }
110    }
111
112    /**
113     * Convenience method to remove a visibility flag from the view, leaving other flags that are
114     * not specified intact.
115     */
116    public static void removeVisibilityFlag(final View view, final int flag) {
117        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
118            final int vis = view.getSystemUiVisibility();
119            view.setSystemUiVisibility(vis & ~flag);
120        }
121    }
122
123    /**
124     * Convenience method to remove a visibility flag from the window, leaving other flags that are
125     * not specified intact.
126     */
127    public static void removeVisibilityFlag(final Window window, final int flag) {
128        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
129            WindowManager.LayoutParams attrs = window.getAttributes();
130            attrs.systemUiVisibility &= ~flag;
131            window.setAttributes(attrs);
132        }
133    }
134
135    public static void setBackButtonVisible(final Window window, final boolean visible) {
136        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
137            if (visible) {
138                removeVisibilityFlag(window, STATUS_BAR_DISABLE_BACK);
139            } else {
140                addVisibilityFlag(window, STATUS_BAR_DISABLE_BACK);
141            }
142        }
143    }
144
145    /**
146     * Set a view to be resized when the keyboard is shown. This will set the bottom margin of the
147     * view to be immediately above the keyboard, and assumes that the view sits immediately above
148     * the navigation bar.
149     *
150     * Note that you must set windowSoftInputMode to adjustResize for this class to work. Otherwise
151     * window insets are not dispatched and this method will have no effect.
152     *
153     * This will only take effect in versions Lollipop or above. Otherwise this is a no-op.
154     *
155     * @param view The view to be resized when the keyboard is shown.
156     */
157    public static void setImeInsetView(final View view) {
158        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
159            view.setOnApplyWindowInsetsListener(new WindowInsetsListener(view.getContext()));
160        }
161    }
162
163    /**
164     * View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN only takes effect when it is added a view instead of
165     * the window.
166     */
167    @TargetApi(VERSION_CODES.LOLLIPOP)
168    private static void addImmersiveFlagsToDecorView(final Window window, final Handler handler,
169            final int vis) {
170        // Use peekDecorView instead of getDecorView so that clients can still set window features
171        // after calling this method.
172        final View decorView = window.peekDecorView();
173        if (decorView != null) {
174            addVisibilityFlag(decorView, vis);
175        } else {
176            // If the decor view is not installed yet, try again in the next loop.
177            handler.post(new Runnable() {
178                @Override
179                public void run() {
180                    addImmersiveFlagsToDecorView(window, handler, vis);
181                }
182            });
183        }
184    }
185
186    @TargetApi(VERSION_CODES.LOLLIPOP)
187    private static void addImmersiveFlagsToWindow(final Window window, final int vis) {
188        WindowManager.LayoutParams attrs = window.getAttributes();
189        attrs.systemUiVisibility |= vis;
190        window.setAttributes(attrs);
191
192        // Also set the navigation bar and status bar to transparent color. Note that this doesn't
193        // work on some devices.
194        window.setNavigationBarColor(0);
195        window.setStatusBarColor(0);
196    }
197
198    /**
199     * Apply a hack to temporarily set the window to not focusable, so that the navigation bar
200     * will not show up during the transition.
201     */
202    private static void temporarilyDisableDialogFocus(final Window window) {
203        window.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
204                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
205        new Handler().post(new Runnable() {
206            @Override
207            public void run() {
208                window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
209            }
210        });
211    }
212
213    @TargetApi(VERSION_CODES.LOLLIPOP)
214    private static class WindowInsetsListener implements View.OnApplyWindowInsetsListener {
215
216        private int mNavigationBarHeight;
217
218        public WindowInsetsListener(Context context) {
219            mNavigationBarHeight =
220                    context.getResources().getDimensionPixelSize(R.dimen.suw_navbar_height);
221        }
222
223        @Override
224        public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) {
225            int bottomInset = insets.getSystemWindowInsetBottom();
226
227            final int bottomMargin = Math.max(
228                    insets.getSystemWindowInsetBottom() - mNavigationBarHeight, 0);
229
230            final ViewGroup.MarginLayoutParams lp =
231                    (ViewGroup.MarginLayoutParams) view.getLayoutParams();
232            // Check that we have enough space to apply the bottom margins before applying it.
233            // Otherwise the framework may think that the view is empty and exclude it from layout.
234            if (bottomMargin < lp.bottomMargin + view.getHeight()) {
235                lp.setMargins(lp.leftMargin, lp.topMargin, lp.rightMargin, bottomMargin);
236                view.setLayoutParams(lp);
237                bottomInset = 0;
238            }
239
240
241            return insets.replaceSystemWindowInsets(
242                    insets.getSystemWindowInsetLeft(),
243                    insets.getSystemWindowInsetTop(),
244                    insets.getSystemWindowInsetRight(),
245                    bottomInset
246            );
247        }
248    }
249}
250