All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.setupwizardlib.util.SystemBarHelper Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * 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.content.res.TypedArray;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
import androidx.annotation.RequiresPermission;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;

/**
 * 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 { private static final String TAG = "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; @SuppressLint("InlinedApi") private static final int DIALOG_IMMERSIVE_FLAGS = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; /** Needs to be equal to View.STATUS_BAR_DISABLE_BACK */ private static final int STATUS_BAR_DISABLE_BACK = 0x00400000; /** * The maximum number of retries when peeking the decor view. When polling for the decor view, * waiting it to be installed, set a maximum number of retries. */ private static final int PEEK_DECOR_VIEW_RETRIES = 3; /** * 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); addVisibilityFlag(window, DIALOG_IMMERSIVE_FLAGS); addImmersiveFlagsToDecorView(window, DIALOG_IMMERSIVE_FLAGS); // Also set the navigation bar and status bar to transparent color. Note that this // doesn't work if android.R.boolean.config_enableTranslucentDecor is false. window.setNavigationBarColor(0); window.setStatusBarColor(0); } } /** * Hide the navigation bar, make the color of the status and navigation bars transparent, and * specify {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} flag so that the content is laid-out * behind the transparent status bar. This is commonly used with {@link * android.app.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) { addVisibilityFlag(window, DEFAULT_IMMERSIVE_FLAGS); addImmersiveFlagsToDecorView(window, DEFAULT_IMMERSIVE_FLAGS); // Also set the navigation bar and status bar to transparent color. Note that this // doesn't work if android.R.boolean.config_enableTranslucentDecor is false. window.setNavigationBarColor(0); window.setStatusBarColor(0); } } /** * Revert the actions of hideSystemBars. Note that this will remove the system UI visibility flags * regardless of whether it is originally present. You should also manually reset the navigation * bar and status bar colors, as this method doesn't know what value to revert it to. */ public static void showSystemBars(final Dialog dialog, final Context context) { showSystemBars(dialog.getWindow(), context); } /** * Revert the actions of hideSystemBars. Note that this will remove the system UI visibility flags * regardless of whether it is originally present. You should also manually reset the navigation * bar and status bar colors, as this method doesn't know what value to revert it to. */ public static void showSystemBars(final Window window, final Context context) { if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { removeVisibilityFlag(window, DEFAULT_IMMERSIVE_FLAGS); removeImmersiveFlagsFromDecorView(window, DEFAULT_IMMERSIVE_FLAGS); if (context != null) { //noinspection AndroidLintInlinedApi final TypedArray typedArray = context.obtainStyledAttributes( new int[] {android.R.attr.statusBarColor, android.R.attr.navigationBarColor}); final int statusBarColor = typedArray.getColor(0, 0); final int navigationBarColor = typedArray.getColor(1, 0); window.setStatusBarColor(statusBarColor); window.setNavigationBarColor(navigationBarColor); typedArray.recycle(); } } } /** 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); } } /** * Sets whether the back button on the software navigation bar is visible. This only works if you * have the STATUS_BAR permission. Otherwise framework will filter out this flag and this method * call will not have any effect. * *

IMPORTANT: Do not assume that users have no way to go back when the back button is hidden. * Many devices have physical back buttons, and accessibility services like TalkBack may have * gestures mapped to back. Please use onBackPressed, onKeyDown, or other similar ways to make * sure back button events are still handled (or ignored) properly. */ @RequiresPermission("android.permission.STATUS_BAR") public static void setBackButtonVisible(final Window window, final boolean visible) { if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { if (visible) { removeVisibilityFlag(window, STATUS_BAR_DISABLE_BACK); removeImmersiveFlagsFromDecorView(window, STATUS_BAR_DISABLE_BACK); } else { addVisibilityFlag(window, STATUS_BAR_DISABLE_BACK); addImmersiveFlagsToDecorView(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. * *

Note that you must set {@link android.R.attr#windowSoftInputMode} to {@code adjustResize} * for this class to work. Otherwise window insets are not dispatched and this method will have no * effect. * *

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()); } } /** * Add the specified immersive flags to the decor view of the window, because {@link * View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} only takes effect when it is added to a view instead of * the window. */ @TargetApi(VERSION_CODES.HONEYCOMB) private static void addImmersiveFlagsToDecorView(final Window window, final int vis) { getDecorView( window, new OnDecorViewInstalledListener() { @Override public void onDecorViewInstalled(View decorView) { addVisibilityFlag(decorView, vis); } }); } @TargetApi(VERSION_CODES.HONEYCOMB) private static void removeImmersiveFlagsFromDecorView(final Window window, final int vis) { getDecorView( window, new OnDecorViewInstalledListener() { @Override public void onDecorViewInstalled(View decorView) { removeVisibilityFlag(decorView, vis); } }); } private static void getDecorView(Window window, OnDecorViewInstalledListener callback) { new DecorViewFinder().getDecorView(window, callback, PEEK_DECOR_VIEW_RETRIES); } private static class DecorViewFinder { private final Handler handler = new Handler(); private Window window; private int retries; private OnDecorViewInstalledListener callback; private final Runnable checkDecorViewRunnable = new Runnable() { @Override public void run() { // 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) { callback.onDecorViewInstalled(decorView); } else { retries--; if (retries >= 0) { // If the decor view is not installed yet, try again in the next loop. handler.post(checkDecorViewRunnable); } else { Log.w(TAG, "Cannot get decor view of window: " + window); } } } }; public void getDecorView(Window window, OnDecorViewInstalledListener callback, int retries) { this.window = window; this.retries = retries; this.callback = callback; checkDecorViewRunnable.run(); } } private interface OnDecorViewInstalledListener { void onDecorViewInstalled(View decorView); } /** * 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); // Add the SOFT_INPUT_IS_FORWARD_NAVIGATION_FLAG. This is normally done by the system when // FLAG_NOT_FOCUSABLE is not set. Setting this flag allows IME to be shown automatically // if the dialog has editable text fields. window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION); 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 bottomOffset; private boolean hasCalculatedBottomOffset = false; @Override public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) { if (!hasCalculatedBottomOffset) { bottomOffset = getBottomDistance(view); hasCalculatedBottomOffset = true; } int bottomInset = insets.getSystemWindowInsetBottom(); final int bottomMargin = Math.max(insets.getSystemWindowInsetBottom() - bottomOffset, 0); final ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); // Check that we have enough space to apply the bottom margins before applying it. // Otherwise the framework may think that the view is empty and exclude it from layout. if (bottomMargin < lp.bottomMargin + view.getHeight()) { lp.setMargins(lp.leftMargin, lp.topMargin, lp.rightMargin, bottomMargin); view.setLayoutParams(lp); bottomInset = 0; } return insets.replaceSystemWindowInsets( insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), bottomInset); } } private static int getBottomDistance(View view) { int[] coords = new int[2]; view.getLocationInWindow(coords); return view.getRootView().getHeight() - coords[1] - view.getHeight(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy