SystemBarHelper.java revision 35ef4eb340353962f23cab81a33b2ddbdfce1da7
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 // Add the SOFT_INPUT_IS_FORWARD_NAVIGATION_FLAG. This is normally done by the system when 206 // FLAG_NOT_FOCUSABLE is not set. Setting this flag allows IME to be shown automatically 207 // if the dialog has editable text fields. 208 window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION); 209 new Handler().post(new Runnable() { 210 @Override 211 public void run() { 212 window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); 213 } 214 }); 215 } 216 217 @TargetApi(VERSION_CODES.LOLLIPOP) 218 private static class WindowInsetsListener implements View.OnApplyWindowInsetsListener { 219 220 private int mNavigationBarHeight; 221 222 public WindowInsetsListener(Context context) { 223 mNavigationBarHeight = 224 context.getResources().getDimensionPixelSize(R.dimen.suw_navbar_height); 225 } 226 227 @Override 228 public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) { 229 int bottomInset = insets.getSystemWindowInsetBottom(); 230 231 final int bottomMargin = Math.max( 232 insets.getSystemWindowInsetBottom() - mNavigationBarHeight, 0); 233 234 final ViewGroup.MarginLayoutParams lp = 235 (ViewGroup.MarginLayoutParams) view.getLayoutParams(); 236 // Check that we have enough space to apply the bottom margins before applying it. 237 // Otherwise the framework may think that the view is empty and exclude it from layout. 238 if (bottomMargin < lp.bottomMargin + view.getHeight()) { 239 lp.setMargins(lp.leftMargin, lp.topMargin, lp.rightMargin, bottomMargin); 240 view.setLayoutParams(lp); 241 bottomInset = 0; 242 } 243 244 245 return insets.replaceSystemWindowInsets( 246 insets.getSystemWindowInsetLeft(), 247 insets.getSystemWindowInsetTop(), 248 insets.getSystemWindowInsetRight(), 249 bottomInset 250 ); 251 } 252 } 253} 254