summaryrefslogtreecommitdiffstats
path: root/library/main/src/com/android/setupwizardlib/util/SystemBarHelper.java
blob: a9fc3f5fab477d018d519eb3f7bd64069032e41b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.setupwizardlib.util;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Dialog;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;

import com.android.setupwizardlib.R;

/**
 * A helper class to manage the system navigation bar and status bar. This will add various
 * systemUiVisibility flags to the given Window or View to make them follow the Setup Wizard style.
 *
 * When the useImmersiveMode intent extra is true, a screen in Setup Wizard should hide the system
 * bars using methods from this class. For Lollipop, {@link #hideSystemBars(android.view.Window)}
 * will completely hide the system navigation bar and change the status bar to transparent, and
 * layout the screen contents (usually the illustration) behind it.
 */
public class SystemBarHelper {

    @SuppressLint("InlinedApi")
    private static final int DEFAULT_IMMERSIVE_FLAGS =
            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;

    /**
     * Needs to be equal to View.STATUS_BAR_DISABLE_BACK
     */
    private static final int STATUS_BAR_DISABLE_BACK = 0x00400000;

    /**
     * Hide the navigation bar for a dialog.
     *
     * This will only take effect in versions Lollipop or above. Otherwise this is a no-op.
     */
    public static void hideSystemBars(final Dialog dialog) {
        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
            final Window window = dialog.getWindow();
            temporarilyDisableDialogFocus(window);
            hideSystemBars(window);
        }
    }

    /**
     * Hide the navigation bar, and make the color of the status and navigation bars transparent,
     * and specify the LAYOUT_FULLSCREEN flag so that the content is laid-out behind the transparent
     * status bar. This is commonly used with Activity.getWindow() to make the navigation and status
     * bars follow the Setup Wizard style.
     *
     * This will only take effect in versions Lollipop or above. Otherwise this is a no-op.
     */
    public static void hideSystemBars(final Window window) {
        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
            addImmersiveFlagsToWindow(window);
            addImmersiveFlagsToDecorView(window, new Handler());
        }
    }

    /**
     * Convenience method to add a visibility flag in addition to the existing ones.
     */
    public static void addVisibilityFlag(final View view, final int flag) {
        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
            final int vis = view.getSystemUiVisibility();
            view.setSystemUiVisibility(vis | flag);
        }
    }

    /**
     * Convenience method to add a visibility flag in addition to the existing ones.
     */
    public static void addVisibilityFlag(final Window window, final int flag) {
        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
            WindowManager.LayoutParams attrs = window.getAttributes();
            attrs.systemUiVisibility |= flag;
            window.setAttributes(attrs);
        }
    }

    /**
     * Convenience method to remove a visibility flag from the view, leaving other flags that are
     * not specified intact.
     */
    public static void removeVisibilityFlag(final View view, final int flag) {
        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
            final int vis = view.getSystemUiVisibility();
            view.setSystemUiVisibility(vis & ~flag);
        }
    }

    /**
     * Convenience method to remove a visibility flag from the window, leaving other flags that are
     * not specified intact.
     */
    public static void removeVisibilityFlag(final Window window, final int flag) {
        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
            WindowManager.LayoutParams attrs = window.getAttributes();
            attrs.systemUiVisibility &= ~flag;
            window.setAttributes(attrs);
        }
    }

    public static void setBackButtonVisible(final Window window, final boolean visible) {
        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
            if (visible) {
                addVisibilityFlag(window, STATUS_BAR_DISABLE_BACK);
            } else {
                removeVisibilityFlag(window, STATUS_BAR_DISABLE_BACK);
            }
        }
    }

    /**
     * Set a view to be resized when the keyboard is shown. This will set the bottom margin of the
     * view to be immediately above the keyboard, and assumes that the view sits immediately above
     * the navigation bar.
     *
     * This will only take effect in versions Lollipop or above. Otherwise this is a no-op.
     *
     * @param view The view to be resized when the keyboard is shown.
     */
    public static void setImeInsetView(final View view) {
        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
            view.setOnApplyWindowInsetsListener(new WindowInsetsListener(view.getContext()));
        }
    }

    /**
     * View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN only takes effect when it is added a view instead of
     * the window.
     */
    @TargetApi(VERSION_CODES.LOLLIPOP)
    private static void addImmersiveFlagsToDecorView(final Window window, final Handler handler) {
        // Use peekDecorView instead of getDecorView so that clients can still set window features
        // after calling this method.
        final View decorView = window.peekDecorView();
        if (decorView != null) {
            addVisibilityFlag(decorView, DEFAULT_IMMERSIVE_FLAGS);
        } else {
            // If the decor view is not installed yet, try again in the next loop.
            handler.post(new Runnable() {
                @Override
                public void run() {
                    addImmersiveFlagsToDecorView(window, handler);
                }
            });
        }
    }

    @TargetApi(VERSION_CODES.LOLLIPOP)
    private static void addImmersiveFlagsToWindow(final Window window) {
        WindowManager.LayoutParams attrs = window.getAttributes();
        attrs.systemUiVisibility |= DEFAULT_IMMERSIVE_FLAGS;
        window.setAttributes(attrs);

        // Also set the navigation bar and status bar to transparent color. Note that this doesn't
        // work on some devices.
        window.setNavigationBarColor(0);
        window.setStatusBarColor(0);
    }

    /**
     * Apply a hack to temporarily set the window to not focusable, so that the navigation bar
     * will not show up during the transition.
     */
    private static void temporarilyDisableDialogFocus(final Window window) {
        window.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
        new Handler().post(new Runnable() {
            @Override
            public void run() {
                window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
            }
        });
    }

    @TargetApi(VERSION_CODES.LOLLIPOP)
    private static class WindowInsetsListener implements View.OnApplyWindowInsetsListener {

        private int mNavigationBarHeight;

        public WindowInsetsListener(Context context) {
            mNavigationBarHeight =
                    context.getResources().getDimensionPixelSize(R.dimen.suw_navbar_height);
        }

        @Override
        public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) {

            final int bottomMargin = Math.max(
                    insets.getSystemWindowInsetBottom() - mNavigationBarHeight, 0);

            ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
            lp.setMargins(lp.leftMargin, lp.topMargin, lp.rightMargin, bottomMargin);
            view.requestLayout();

            return insets.replaceSystemWindowInsets(
                    insets.getSystemWindowInsetLeft(),
                    insets.getSystemWindowInsetTop(),
                    insets.getSystemWindowInsetRight(),
                    0 /* bottom */
            );
        }
    }
}