diff options
author | Setup Wizard Team <android-setup-team-eng@google.com> | 2019-04-01 10:27:47 +0800 |
---|---|---|
committer | pastychang <pastychang@google.com> | 2019-04-01 14:42:38 +0800 |
commit | de9b52a147ec3aeb290d01d0eda1867aa3103acd (patch) | |
tree | 61ec9c94b17a8b3bac9064e687164d9b2d39725f /main/java/com/google/android/setupcompat | |
parent | 2c49b010bb28fcb7b9c666389308cee98db958a1 (diff) | |
download | setupcompat-de9b52a147ec3aeb290d01d0eda1867aa3103acd.tar.gz |
Import updated Android SetupCompat Library 241248644
Copied from google3/third_party/java_src/android_libs/setupcompat
Test: mm
Included changes:
- 241248644 Recycleview apply light theme.
- 241246566 Replace isObjectEquals and hashCode to ObjectUtils#equals...
- 240935991 Move SystemBarHelper from setupdesign to setupcompat.
- 240910529 Fix lint errors and add #hashCode and #equals for CustomE...
- 240908548 Force state refresh in button background before setting t...
- 240263426 Add some code to avoid lint error for PartnerCustomizatio...
- 240262587 Move the minSDK version declaration to BUILD file
- 240190830 Don't suppress the Future return value error
- 240189246 Move internal files to under internal package
PiperOrigin-RevId: 241248644
Change-Id: I8d655b5abcb4899f5975fc75f24a256677f71a36
Diffstat (limited to 'main/java/com/google/android/setupcompat')
15 files changed, 496 insertions, 221 deletions
diff --git a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java index 4a95727..0e85268 100644 --- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java +++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java @@ -21,6 +21,7 @@ import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; import android.content.res.TypedArray; +import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.PersistableBundle; @@ -31,9 +32,10 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import com.google.android.setupcompat.internal.BuildCompat; +import com.google.android.setupcompat.internal.LifecycleFragment; import com.google.android.setupcompat.internal.PersistableBundles; import com.google.android.setupcompat.internal.TemplateLayout; -import com.google.android.setupcompat.lifecycle.LifecycleFragment; import com.google.android.setupcompat.logging.CustomEvent; import com.google.android.setupcompat.logging.MetricKey; import com.google.android.setupcompat.logging.SetupMetricsLogger; @@ -43,7 +45,6 @@ import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterButton; import com.google.android.setupcompat.template.StatusBarMixin; import com.google.android.setupcompat.template.SystemNavBarMixin; -import com.google.android.setupcompat.util.BuildCompat; import com.google.android.setupcompat.util.WizardManagerHelper; /** A templatization layout with consistent style used in Setup Wizard or app itself. */ @@ -56,7 +57,7 @@ public class PartnerCustomizationLayout extends TemplateLayout { * the {@code app:sucUsePartnerResource} XML attribute. Note that when running in setup wizard, * this is always overridden to true. */ - private boolean usePartnerResourceAttr = true; + private boolean usePartnerResourceAttr; private Activity activity; @@ -85,9 +86,6 @@ public class PartnerCustomizationLayout extends TemplateLayout { } private void init(AttributeSet attrs, int defStyleAttr) { - activity = lookupActivityFromContext(getContext()); - - boolean isSetupFlow = WizardManagerHelper.isAnySetupWizard(activity.getIntent()); TypedArray a = getContext() @@ -97,32 +95,12 @@ public class PartnerCustomizationLayout extends TemplateLayout { boolean layoutFullscreen = a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucLayoutFullscreen, true); - if (!a.hasValue(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource)) { - // TODO(b/128961334): Enable Log.WTF after other client already set sucUsePartnerResource. - Log.e(TAG, "Attribute sucUsePartnerResource not found in " + activity.getComponentName()); - } - - usePartnerResourceAttr = - isSetupFlow - || a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource, true); - a.recycle(); if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && layoutFullscreen) { setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } - Log.i( - TAG, - "activity=" - + activity.getClass().getSimpleName() - + " isSetupFlow=" - + isSetupFlow - + " enablePartnerResourceLoading=" - + enablePartnerResourceLoading() - + " usePartnerResourceAttr=" - + usePartnerResourceAttr); - registerMixin( StatusBarMixin.class, new StatusBarMixin(this, activity.getWindow(), attrs, defStyleAttr)); registerMixin(SystemNavBarMixin.class, new SystemNavBarMixin(this, activity.getWindow())); @@ -133,9 +111,11 @@ public class PartnerCustomizationLayout extends TemplateLayout { // Override the FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, FLAG_TRANSLUCENT_STATUS, // FLAG_TRANSLUCENT_NAVIGATION and SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN attributes of window forces // showing status bar and navigation bar. - activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + } if (shouldApplyPartnerResource()) { updateContentBackgroundColorWithPartnerConfig(); @@ -150,6 +130,55 @@ public class PartnerCustomizationLayout extends TemplateLayout { return inflateTemplate(inflater, 0, template); } + /** + * {@inheritDoc} + * + * <p>This method sets all these flags before onTemplateInflated since it will be too late and get + * incorrect flag value on PartnerCustomizationLayout if sets them after onTemplateInflated. + */ + @Override + protected void onBeforeTemplateInflated(AttributeSet attrs, int defStyleAttr) { + + boolean isSetupFlow; + + // Sets default value to true since this timing + // before PartnerCustomization members initialization + usePartnerResourceAttr = true; + + activity = lookupActivityFromContext(getContext()); + + isSetupFlow = WizardManagerHelper.isAnySetupWizard(activity.getIntent()); + + TypedArray a = + getContext() + .obtainStyledAttributes( + attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0); + + if (!a.hasValue(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource)) { + // TODO(b/128961334): Enable Log.WTF after other client already set sucUsePartnerResource. + Log.e(TAG, "Attribute sucUsePartnerResource not found in " + activity.getComponentName()); + } + + usePartnerResourceAttr = + isSetupFlow + || a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource, true); + + a.recycle(); + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d( + TAG, + "activity=" + + activity.getClass().getSimpleName() + + " isSetupFlow=" + + isSetupFlow + + " enablePartnerResourceLoading=" + + enablePartnerResourceLoading() + + " usePartnerResourceAttr=" + + usePartnerResourceAttr); + } + } + @Override protected ViewGroup findContainer(int containerId) { if (containerId == 0) { @@ -168,7 +197,9 @@ public class PartnerCustomizationLayout extends TemplateLayout { @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (BuildCompat.isAtLeastQ() && WizardManagerHelper.isAnySetupWizard(activity.getIntent())) { + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP + && BuildCompat.isAtLeastQ() + && WizardManagerHelper.isAnySetupWizard(activity.getIntent())) { FooterBarMixin footerBarMixin = getMixin(FooterBarMixin.class); footerBarMixin.onDetachedFromWindow(); FooterButton primaryButton = footerBarMixin.getPrimaryButton(); diff --git a/main/java/com/google/android/setupcompat/util/BuildCompat.java b/main/java/com/google/android/setupcompat/internal/BuildCompat.java index b9d3a79..54c0911 100644 --- a/main/java/com/google/android/setupcompat/util/BuildCompat.java +++ b/main/java/com/google/android/setupcompat/internal/BuildCompat.java @@ -1,8 +1,9 @@ -package com.google.android.setupcompat.util; +package com.google.android.setupcompat.internal; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +/** Utility methods for detecting the build API version. */ public final class BuildCompat { private BuildCompat() {} diff --git a/main/java/com/google/android/setupcompat/util/FallbackThemeWrapper.java b/main/java/com/google/android/setupcompat/internal/FallbackThemeWrapper.java index 917b9d7..af17a62 100644 --- a/main/java/com/google/android/setupcompat/util/FallbackThemeWrapper.java +++ b/main/java/com/google/android/setupcompat/internal/FallbackThemeWrapper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.setupcompat.util; +package com.google.android.setupcompat.internal; import android.content.Context; import android.content.res.Resources.Theme; diff --git a/main/java/com/google/android/setupcompat/lifecycle/LifecycleFragment.java b/main/java/com/google/android/setupcompat/internal/LifecycleFragment.java index 7eec881..269dd6d 100644 --- a/main/java/com/google/android/setupcompat/lifecycle/LifecycleFragment.java +++ b/main/java/com/google/android/setupcompat/internal/LifecycleFragment.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.setupcompat.lifecycle; +package com.google.android.setupcompat.internal; import static java.util.concurrent.TimeUnit.NANOSECONDS; @@ -25,9 +25,6 @@ import android.content.Context; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.util.Log; -import com.google.android.setupcompat.internal.ClockProvider; -import com.google.android.setupcompat.internal.LayoutBindBackHelper; -import com.google.android.setupcompat.internal.SetupCompatServiceInvoker; import com.google.android.setupcompat.logging.MetricKey; import com.google.android.setupcompat.logging.SetupMetricsLogger; import com.google.android.setupcompat.util.WizardManagerHelper; diff --git a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java index 0ed1d9f..a1ca156 100644 --- a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java +++ b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java @@ -41,10 +41,9 @@ import java.util.concurrent.TimeoutException; */ public class SetupCompatServiceInvoker { - @SuppressWarnings("FutureReturnValueIgnored") public void logMetricEvent(@MetricType int metricType, Bundle args) { try { - loggingExecutor.submit(() -> invokeLogMetric(metricType, args)); + loggingExecutor.execute(() -> invokeLogMetric(metricType, args)); } catch (RejectedExecutionException e) { Log.e(TAG, String.format("Metric of type %d dropped since queue is full.", metricType), e); } diff --git a/main/java/com/google/android/setupcompat/internal/TemplateLayout.java b/main/java/com/google/android/setupcompat/internal/TemplateLayout.java index 222d482..c6eba37 100644 --- a/main/java/com/google/android/setupcompat/internal/TemplateLayout.java +++ b/main/java/com/google/android/setupcompat/internal/TemplateLayout.java @@ -31,7 +31,6 @@ import android.view.ViewTreeObserver; import android.widget.FrameLayout; import com.google.android.setupcompat.R; import com.google.android.setupcompat.template.Mixin; -import com.google.android.setupcompat.util.FallbackThemeWrapper; import java.util.HashMap; import java.util.Map; @@ -80,6 +79,7 @@ public class TemplateLayout extends FrameLayout { if (containerId == 0) { containerId = a.getResourceId(R.styleable.SucTemplateLayout_sucContainer, 0); } + onBeforeTemplateInflated(attrs, defStyleAttr); inflateTemplate(template, containerId); a.recycle(); @@ -208,6 +208,13 @@ public class TemplateLayout extends FrameLayout { protected void onTemplateInflated() {} /** + * This is called before the template has been inflated and added to the view hierarchy. + * Subclasses can implement this method to modify the template as necessary, such as something + * need to be done before onTemplateInflated which is called while still in the constructor. + */ + protected void onBeforeTemplateInflated(AttributeSet attrs, int defStyleAttr) {} + + /** * @return ID of the default container for this layout. This will be used to find the container * ViewGroup, which all children views of this layout will be placed in. * @deprecated Override {@link #findContainer(int)} instead. diff --git a/main/java/com/google/android/setupcompat/logging/CustomEvent.java b/main/java/com/google/android/setupcompat/logging/CustomEvent.java index 98f8d91..18d8f3c 100644 --- a/main/java/com/google/android/setupcompat/logging/CustomEvent.java +++ b/main/java/com/google/android/setupcompat/logging/CustomEvent.java @@ -18,13 +18,16 @@ package com.google.android.setupcompat.logging; import static com.google.android.setupcompat.internal.Validations.assertLengthInRange; +import android.annotation.TargetApi; +import android.os.Build.VERSION_CODES; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; import androidx.annotation.VisibleForTesting; +import com.google.android.setupcompat.internal.BuildCompat; import com.google.android.setupcompat.internal.ClockProvider; import com.google.android.setupcompat.internal.Preconditions; -import java.util.Objects; +import com.google.android.setupcompat.util.ObjectUtils; /** * This class represents a interesting event at a particular point in time. The event is identified @@ -32,16 +35,21 @@ import java.util.Objects; * providing more attributes associated with the given event. Only primitive values are supported * for now (int, long, double, float, String). */ +@TargetApi(VERSION_CODES.Q) public final class CustomEvent implements Parcelable { /** Creates a new instance of {@code CustomEvent}. Null arguments are not allowed. */ public static CustomEvent create( MetricKey metricKey, PersistableBundle bundle, PersistableBundle piiValues) { + Preconditions.checkArgument( + BuildCompat.isAtLeastQ(), "The constructor only support on sdk Q or higher"); return new CustomEvent(ClockProvider.timeInMillis(), metricKey, bundle, piiValues); } /** Creates a new instance of {@code CustomEvent}. Null arguments are not allowed. */ public static CustomEvent create(MetricKey metricKey, PersistableBundle bundle) { + Preconditions.checkArgument( + BuildCompat.isAtLeastQ(), "The constructor only support on sdk Q or higher."); return create(metricKey, bundle, PersistableBundle.EMPTY); } @@ -52,8 +60,8 @@ public final class CustomEvent implements Parcelable { return new CustomEvent( in.readLong(), in.readParcelable(MetricKey.class.getClassLoader()), - in.readPersistableBundle(), - in.readPersistableBundle()); + in.readPersistableBundle(MetricKey.class.getClassLoader()), + in.readPersistableBundle(MetricKey.class.getClassLoader())); } @Override @@ -108,14 +116,14 @@ public final class CustomEvent implements Parcelable { } CustomEvent that = (CustomEvent) o; return timestampMillis == that.timestampMillis - && Objects.equals(metricKey, that.metricKey) - && Objects.equals(persistableBundle, that.persistableBundle) - && Objects.equals(piiValues, that.piiValues); + && ObjectUtils.equals(metricKey, that.metricKey) + && ObjectUtils.equals(persistableBundle, that.persistableBundle) + && ObjectUtils.equals(piiValues, that.piiValues); } @Override public int hashCode() { - return Objects.hash(timestampMillis, metricKey, persistableBundle, piiValues); + return ObjectUtils.hashCode(timestampMillis, metricKey, persistableBundle, piiValues); } private CustomEvent( diff --git a/main/java/com/google/android/setupcompat/logging/MetricKey.java b/main/java/com/google/android/setupcompat/logging/MetricKey.java index b369b9f..8d3692c 100644 --- a/main/java/com/google/android/setupcompat/logging/MetricKey.java +++ b/main/java/com/google/android/setupcompat/logging/MetricKey.java @@ -22,7 +22,7 @@ import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; import com.google.android.setupcompat.internal.Preconditions; -import java.util.Arrays; +import com.google.android.setupcompat.util.ObjectUtils; import java.util.regex.Pattern; /** @@ -99,13 +99,13 @@ public final class MetricKey implements Parcelable { return false; } MetricKey metricKey = (MetricKey) o; - return isObjectEquals(name, metricKey.name) && isObjectEquals(screenName, metricKey.screenName); + return ObjectUtils.equals(name, metricKey.name) + && ObjectUtils.equals(screenName, metricKey.screenName); } @Override public int hashCode() { - Object[] o = {name, screenName}; - return Arrays.hashCode(o); + return ObjectUtils.hashCode(name, screenName); } private MetricKey(String name, String screenName) { @@ -113,10 +113,6 @@ public final class MetricKey implements Parcelable { this.screenName = screenName; } - private boolean isObjectEquals(Object a, Object b) { - return (a == b) || (a != null && a.equals(b)); - } - private final String name; private final String screenName; diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java index 4a52414..005471d 100644 --- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java @@ -612,6 +612,7 @@ public class FooterBarMixin implements Mixin { private static PartnerConfig getDrawablePartnerConfig(@ButtonType int buttonType) { PartnerConfig result; + // LINT.IfChange switch (buttonType) { case ButtonType.ADD_ANOTHER: result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_ADD_ANOTHER; @@ -642,6 +643,9 @@ public class FooterBarMixin implements Mixin { result = null; break; } + // LINT.ThenChange( + // //depot/google3/third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/template/FooterButton.java, + // //depot/google3/third_party/java_src/android_libs/setupcompat/main/res/values/attrs.xml) return result; } diff --git a/main/java/com/google/android/setupcompat/template/FooterButton.java b/main/java/com/google/android/setupcompat/template/FooterButton.java index 457a116..35ea02c 100644 --- a/main/java/com/google/android/setupcompat/template/FooterButton.java +++ b/main/java/com/google/android/setupcompat/template/FooterButton.java @@ -212,6 +212,7 @@ public final class FooterButton implements OnClickListener { this.theme = theme; } + // LINT.IfChange /** Maximum valid value of ButtonType */ private static final int MAX_BUTTON_TYPE = 8; @@ -265,6 +266,7 @@ public final class FooterButton implements OnClickListener { } private String getButtonTypeName() { + // LINT.IfChange switch (buttonType) { case ButtonType.ADD_ANOTHER: return "ADD_ANOTHER"; @@ -286,6 +288,9 @@ public final class FooterButton implements OnClickListener { default: return "OTHER"; } + // LINT.ThenChange( + // //depot/google3/third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/template/FooterBarMixin.java, + // //depot/google3/third_party/java_src/android_libs/setupcompat/main/res/values/attrs.xml) } /** diff --git a/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java b/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java index c273b43..a3ce567 100644 --- a/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java @@ -36,7 +36,7 @@ import com.google.android.setupcompat.R; import com.google.android.setupcompat.internal.TemplateLayout; import com.google.android.setupcompat.partnerconfig.PartnerConfig; import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; -import com.google.android.setupcompat.util.SystemBarBaseHelper; +import com.google.android.setupcompat.util.SystemBarHelper; /** * A {@link Mixin} for setting and getting background color and window compatible with light theme @@ -163,9 +163,8 @@ public class SystemNavBarMixin implements Mixin { */ public void hideSystemBars(final Window window) { if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - SystemBarBaseHelper.addVisibilityFlag(window, SystemBarBaseHelper.DEFAULT_IMMERSIVE_FLAGS); - SystemBarBaseHelper.addImmersiveFlagsToDecorView( - window, SystemBarBaseHelper.DEFAULT_IMMERSIVE_FLAGS); + SystemBarHelper.addVisibilityFlag(window, SystemBarHelper.DEFAULT_IMMERSIVE_FLAGS); + SystemBarHelper.addImmersiveFlagsToDecorView(window, SystemBarHelper.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. @@ -183,9 +182,9 @@ public class SystemNavBarMixin implements Mixin { */ public void showSystemBars(final Window window, final Context context) { if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - SystemBarBaseHelper.removeVisibilityFlag(window, SystemBarBaseHelper.DEFAULT_IMMERSIVE_FLAGS); - SystemBarBaseHelper.removeImmersiveFlagsFromDecorView( - window, SystemBarBaseHelper.DEFAULT_IMMERSIVE_FLAGS); + SystemBarHelper.removeVisibilityFlag(window, SystemBarHelper.DEFAULT_IMMERSIVE_FLAGS); + SystemBarHelper.removeImmersiveFlagsFromDecorView( + window, SystemBarHelper.DEFAULT_IMMERSIVE_FLAGS); if (context != null) { if (applyPartnerResources) { diff --git a/main/java/com/google/android/setupcompat/util/ObjectUtils.java b/main/java/com/google/android/setupcompat/util/ObjectUtils.java new file mode 100644 index 0000000..cd5299d --- /dev/null +++ b/main/java/com/google/android/setupcompat/util/ObjectUtils.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2019 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.google.android.setupcompat.util; + +import java.util.Arrays; + +/** The util for {@link java.util.Objects} method to suitable in all sdk version. */ +public final class ObjectUtils { + + private ObjectUtils() {} + + /** Copied from {@link java.util.Objects#hash(Object...)} */ + public static int hashCode(Object... args) { + return Arrays.hashCode(args); + } + + /** Copied from {@link java.util.Objects#equals(Object, Object)} */ + public static boolean equals(Object a, Object b) { + return (a == b) || (a != null && a.equals(b)); + } +} diff --git a/main/java/com/google/android/setupcompat/util/SystemBarBaseHelper.java b/main/java/com/google/android/setupcompat/util/SystemBarBaseHelper.java deleted file mode 100644 index a1c5f65..0000000 --- a/main/java/com/google/android/setupcompat/util/SystemBarBaseHelper.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2019 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.google.android.setupcompat.util; - -import android.annotation.SuppressLint; -import android.os.Handler; -import android.util.Log; -import android.view.View; -import android.view.Window; -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. - * - * <p>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 SystemBarBaseHelper { - - private static final String TAG = "SystemBarBaseHelper"; - - @SuppressLint("InlinedApi") - public 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") - public static final int DIALOG_IMMERSIVE_FLAGS = - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - - /** - * 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; - - /** Convenience method to add a visibility flag in addition to the existing ones. */ - public static void addVisibilityFlag(final View view, final int flag) { - 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) { - 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) { - 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) { - WindowManager.LayoutParams attrs = window.getAttributes(); - attrs.systemUiVisibility &= ~flag; - window.setAttributes(attrs); - } - - /** - * 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. - */ - public static void addImmersiveFlagsToDecorView(final Window window, final int vis) { - getDecorView( - window, - new OnDecorViewInstalledListener() { - @Override - public void onDecorViewInstalled(View decorView) { - addVisibilityFlag(decorView, vis); - } - }); - } - - public 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); - } -} diff --git a/main/java/com/google/android/setupcompat/util/SystemBarHelper.java b/main/java/com/google/android/setupcompat/util/SystemBarHelper.java new file mode 100644 index 0000000..60d5283 --- /dev/null +++ b/main/java/com/google/android/setupcompat/util/SystemBarHelper.java @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2019 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.google.android.setupcompat.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. + * + * <p>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 final class SystemBarHelper { + + private static final String TAG = "SystemBarHelper"; + + /** Needs to be equal to View.STATUS_BAR_DISABLE_BACK */ + private static final int STATUS_BAR_DISABLE_BACK = 0x00400000; + + @SuppressLint("InlinedApi") + public 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") + public static final int DIALOG_IMMERSIVE_FLAGS = + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + + /** + * 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; + + /** Convenience method to add a visibility flag in addition to the existing ones. */ + public static void addVisibilityFlag(final View view, final int flag) { + 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) { + 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) { + 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) { + WindowManager.LayoutParams attrs = window.getAttributes(); + attrs.systemUiVisibility &= ~flag; + window.setAttributes(attrs); + } + + /** + * 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. + */ + public static void addImmersiveFlagsToDecorView(final Window window, final int vis) { + getDecorView( + window, + new OnDecorViewInstalledListener() { + @Override + public void onDecorViewInstalled(View decorView) { + addVisibilityFlag(decorView, vis); + } + }); + } + + public static void removeImmersiveFlagsFromDecorView(final Window window, final int vis) { + getDecorView( + window, + new OnDecorViewInstalledListener() { + @Override + public void onDecorViewInstalled(View decorView) { + removeVisibilityFlag(decorView, vis); + } + }); + } + + /** + * Hide the navigation bar for a dialog. + * + * <p>This will only take effect in versions Lollipop or above. Otherwise this is a no-op. + * + * @deprecated If the layout is instance of TemplateLayout, please use + * SystemNavBarMixin.hideSystemBars. + */ + @Deprecated + public static void hideSystemBars(final Dialog dialog) { + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + final Window window = dialog.getWindow(); + temporarilyDisableDialogFocus(window); + SystemBarHelper.addVisibilityFlag(window, SystemBarHelper.DIALOG_IMMERSIVE_FLAGS); + SystemBarHelper.addImmersiveFlagsToDecorView(window, SystemBarHelper.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. + * + * <p>This will only take effect in versions Lollipop or above. Otherwise this is a no-op. + * + * @deprecated If the layout instance of TemplateLayout, please use + * SystemNavBarMixin.hideSystemBars. + */ + @Deprecated + public static void hideSystemBars(final Window window) { + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + SystemBarHelper.addVisibilityFlag(window, SystemBarHelper.DEFAULT_IMMERSIVE_FLAGS); + SystemBarHelper.addImmersiveFlagsToDecorView(window, SystemBarHelper.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. + * + * @deprecated If the layout is instance of TemplateLayout, please use + * SystemNavBarMixin.showSystemBars. + */ + @Deprecated + public static void showSystemBars(final Window window, final Context context) { + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + SystemBarHelper.removeVisibilityFlag(window, SystemBarHelper.DEFAULT_IMMERSIVE_FLAGS); + SystemBarHelper.removeImmersiveFlagsFromDecorView( + window, SystemBarHelper.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(); + } + } + } + + /** + * 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. + * + * <p>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) { + SystemBarHelper.removeVisibilityFlag(window, STATUS_BAR_DISABLE_BACK); + SystemBarHelper.removeImmersiveFlagsFromDecorView(window, STATUS_BAR_DISABLE_BACK); + } else { + SystemBarHelper.addVisibilityFlag(window, STATUS_BAR_DISABLE_BACK); + SystemBarHelper.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. + * + * <p>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. + * + * <p>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()); + } + } + + /** + * 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(); + } + + 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); + } +} diff --git a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java index 082bcca..d071dbf 100644 --- a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java +++ b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java @@ -23,6 +23,7 @@ import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.provider.Settings; import androidx.annotation.VisibleForTesting; +import com.google.android.setupcompat.internal.BuildCompat; import java.util.Arrays; /** |