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