SystemBarHelper.java revision 916380f7eb2360a35d7c9fc2ec6e05ba76fa07f3
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 * This will only take effect in versions Lollipop or above. Otherwise this is a no-op. 151 * 152 * @param view The view to be resized when the keyboard is shown. 153 */ 154 public static void setImeInsetView(final View view) { 155 if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { 156 view.setOnApplyWindowInsetsListener(new WindowInsetsListener(view.getContext())); 157 } 158 } 159 160 /** 161 * View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN only takes effect when it is added a view instead of 162 * the window. 163 */ 164 @TargetApi(VERSION_CODES.LOLLIPOP) 165 private static void addImmersiveFlagsToDecorView(final Window window, final Handler handler, 166 final int vis) { 167 // Use peekDecorView instead of getDecorView so that clients can still set window features 168 // after calling this method. 169 final View decorView = window.peekDecorView(); 170 if (decorView != null) { 171 addVisibilityFlag(decorView, vis); 172 } else { 173 // If the decor view is not installed yet, try again in the next loop. 174 handler.post(new Runnable() { 175 @Override 176 public void run() { 177 addImmersiveFlagsToDecorView(window, handler, vis); 178 } 179 }); 180 } 181 } 182 183 @TargetApi(VERSION_CODES.LOLLIPOP) 184 private static void addImmersiveFlagsToWindow(final Window window, final int vis) { 185 WindowManager.LayoutParams attrs = window.getAttributes(); 186 attrs.systemUiVisibility |= vis; 187 window.setAttributes(attrs); 188 189 // Also set the navigation bar and status bar to transparent color. Note that this doesn't 190 // work on some devices. 191 window.setNavigationBarColor(0); 192 window.setStatusBarColor(0); 193 } 194 195 /** 196 * Apply a hack to temporarily set the window to not focusable, so that the navigation bar 197 * will not show up during the transition. 198 */ 199 private static void temporarilyDisableDialogFocus(final Window window) { 200 window.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, 201 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); 202 new Handler().post(new Runnable() { 203 @Override 204 public void run() { 205 window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); 206 } 207 }); 208 } 209 210 @TargetApi(VERSION_CODES.LOLLIPOP) 211 private static class WindowInsetsListener implements View.OnApplyWindowInsetsListener { 212 213 private int mNavigationBarHeight; 214 215 public WindowInsetsListener(Context context) { 216 mNavigationBarHeight = 217 context.getResources().getDimensionPixelSize(R.dimen.suw_navbar_height); 218 } 219 220 @Override 221 public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) { 222 223 final int bottomMargin = Math.max( 224 insets.getSystemWindowInsetBottom() - mNavigationBarHeight, 0); 225 226 ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); 227 lp.setMargins(lp.leftMargin, lp.topMargin, lp.rightMargin, bottomMargin); 228 view.requestLayout(); 229 230 return insets.replaceSystemWindowInsets( 231 insets.getSystemWindowInsetLeft(), 232 insets.getSystemWindowInsetTop(), 233 insets.getSystemWindowInsetRight(), 234 0 /* bottom */ 235 ); 236 } 237 } 238} 239