SystemBarHelper.java revision e96ec75d23e82b352ab1393dbac8d6372ceb62d7
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    /**
53     * Needs to be equal to View.STATUS_BAR_DISABLE_BACK
54     */
55    private static final int STATUS_BAR_DISABLE_BACK = 0x00400000;
56
57    /**
58     * Hide the navigation bar for a dialog.
59     *
60     * This will only take effect in versions Lollipop or above. Otherwise this is a no-op.
61     */
62    public static void hideSystemBars(final Dialog dialog) {
63        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
64            final Window window = dialog.getWindow();
65            temporarilyDisableDialogFocus(window);
66            hideSystemBars(window);
67        }
68    }
69
70    /**
71     * Hide the navigation bar, and make the color of the status and navigation bars transparent,
72     * and specify the LAYOUT_FULLSCREEN flag so that the content is laid-out behind the transparent
73     * status bar. This is commonly used with Activity.getWindow() to make the navigation and status
74     * bars follow the Setup Wizard style.
75     *
76     * This will only take effect in versions Lollipop or above. Otherwise this is a no-op.
77     */
78    public static void hideSystemBars(final Window window) {
79        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
80            addImmersiveFlagsToWindow(window);
81            addImmersiveFlagsToDecorView(window, new Handler());
82        }
83    }
84
85    /**
86     * Convenience method to add a visibility flag in addition to the existing ones.
87     */
88    public static void addVisibilityFlag(final View view, final int flag) {
89        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
90            final int vis = view.getSystemUiVisibility();
91            view.setSystemUiVisibility(vis | flag);
92        }
93    }
94
95    /**
96     * Convenience method to add a visibility flag in addition to the existing ones.
97     */
98    public static void addVisibilityFlag(final Window window, final int flag) {
99        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
100            WindowManager.LayoutParams attrs = window.getAttributes();
101            attrs.systemUiVisibility |= flag;
102            window.setAttributes(attrs);
103        }
104    }
105
106    /**
107     * Convenience method to remove a visibility flag from the view, leaving other flags that are
108     * not specified intact.
109     */
110    public static void removeVisibilityFlag(final View view, final int flag) {
111        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
112            final int vis = view.getSystemUiVisibility();
113            view.setSystemUiVisibility(vis & ~flag);
114        }
115    }
116
117    /**
118     * Convenience method to remove a visibility flag from the window, leaving other flags that are
119     * not specified intact.
120     */
121    public static void removeVisibilityFlag(final Window window, final int flag) {
122        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
123            WindowManager.LayoutParams attrs = window.getAttributes();
124            attrs.systemUiVisibility &= ~flag;
125            window.setAttributes(attrs);
126        }
127    }
128
129    public static void setBackButtonVisible(final Window window, final boolean visible) {
130        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
131            if (visible) {
132                removeVisibilityFlag(window, STATUS_BAR_DISABLE_BACK);
133            } else {
134                addVisibilityFlag(window, STATUS_BAR_DISABLE_BACK);
135            }
136        }
137    }
138
139    /**
140     * Set a view to be resized when the keyboard is shown. This will set the bottom margin of the
141     * view to be immediately above the keyboard, and assumes that the view sits immediately above
142     * the navigation bar.
143     *
144     * This will only take effect in versions Lollipop or above. Otherwise this is a no-op.
145     *
146     * @param view The view to be resized when the keyboard is shown.
147     */
148    public static void setImeInsetView(final View view) {
149        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
150            view.setOnApplyWindowInsetsListener(new WindowInsetsListener(view.getContext()));
151        }
152    }
153
154    /**
155     * View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN only takes effect when it is added a view instead of
156     * the window.
157     */
158    @TargetApi(VERSION_CODES.LOLLIPOP)
159    private static void addImmersiveFlagsToDecorView(final Window window, final Handler handler) {
160        // Use peekDecorView instead of getDecorView so that clients can still set window features
161        // after calling this method.
162        final View decorView = window.peekDecorView();
163        if (decorView != null) {
164            addVisibilityFlag(decorView, DEFAULT_IMMERSIVE_FLAGS);
165        } else {
166            // If the decor view is not installed yet, try again in the next loop.
167            handler.post(new Runnable() {
168                @Override
169                public void run() {
170                    addImmersiveFlagsToDecorView(window, handler);
171                }
172            });
173        }
174    }
175
176    @TargetApi(VERSION_CODES.LOLLIPOP)
177    private static void addImmersiveFlagsToWindow(final Window window) {
178        WindowManager.LayoutParams attrs = window.getAttributes();
179        attrs.systemUiVisibility |= DEFAULT_IMMERSIVE_FLAGS;
180        window.setAttributes(attrs);
181
182        // Also set the navigation bar and status bar to transparent color. Note that this doesn't
183        // work on some devices.
184        window.setNavigationBarColor(0);
185        window.setStatusBarColor(0);
186    }
187
188    /**
189     * Apply a hack to temporarily set the window to not focusable, so that the navigation bar
190     * will not show up during the transition.
191     */
192    private static void temporarilyDisableDialogFocus(final Window window) {
193        window.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
194                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
195        new Handler().post(new Runnable() {
196            @Override
197            public void run() {
198                window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
199            }
200        });
201    }
202
203    @TargetApi(VERSION_CODES.LOLLIPOP)
204    private static class WindowInsetsListener implements View.OnApplyWindowInsetsListener {
205
206        private int mNavigationBarHeight;
207
208        public WindowInsetsListener(Context context) {
209            mNavigationBarHeight =
210                    context.getResources().getDimensionPixelSize(R.dimen.suw_navbar_height);
211        }
212
213        @Override
214        public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) {
215
216            final int bottomMargin = Math.max(
217                    insets.getSystemWindowInsetBottom() - mNavigationBarHeight, 0);
218
219            ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
220            lp.setMargins(lp.leftMargin, lp.topMargin, lp.rightMargin, bottomMargin);
221            view.requestLayout();
222
223            return insets.replaceSystemWindowInsets(
224                    insets.getSystemWindowInsetLeft(),
225                    insets.getSystemWindowInsetTop(),
226                    insets.getSystemWindowInsetRight(),
227                    0 /* bottom */
228            );
229        }
230    }
231}
232