aboutsummaryrefslogtreecommitdiff
path: root/setupwizardlib/src/main/java/com/android/setupwizardlib/util
diff options
context:
space:
mode:
authorTrumeet <liangyuteng12345@gmail.com>2017-08-03 15:12:06 +0800
committerTrumeet <liangyuteng12345@gmail.com>2017-08-03 15:12:08 +0800
commit019522cc93d947b5075ca90547b9e13f08787739 (patch)
tree18096ab2a5f678f5d2b4f3a810966c2bf9defe67 /setupwizardlib/src/main/java/com/android/setupwizardlib/util
downloadSetupWizardLibCompat-019522cc93d947b5075ca90547b9e13f08787739.tar
SetupWizardLibCompat-019522cc93d947b5075ca90547b9e13f08787739.tar.gz
SetupWizardLibCompat-019522cc93d947b5075ca90547b9e13f08787739.tar.bz2
SetupWizardLibCompat-019522cc93d947b5075ca90547b9e13f08787739.zip
First Commit0.1
Diffstat (limited to 'setupwizardlib/src/main/java/com/android/setupwizardlib/util')
-rw-r--r--setupwizardlib/src/main/java/com/android/setupwizardlib/util/AbstractRequireScrollHelper.java78
-rw-r--r--setupwizardlib/src/main/java/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java77
-rw-r--r--setupwizardlib/src/main/java/com/android/setupwizardlib/util/ListViewRequireScrollHelper.java81
-rw-r--r--setupwizardlib/src/main/java/com/android/setupwizardlib/util/Partner.java162
-rw-r--r--setupwizardlib/src/main/java/com/android/setupwizardlib/util/RequireScrollHelper.java64
-rw-r--r--setupwizardlib/src/main/java/com/android/setupwizardlib/util/ResultCodes.java28
-rw-r--r--setupwizardlib/src/main/java/com/android/setupwizardlib/util/SystemBarHelper.java348
-rw-r--r--setupwizardlib/src/main/java/com/android/setupwizardlib/util/WizardManagerHelper.java216
8 files changed, 1054 insertions, 0 deletions
diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/AbstractRequireScrollHelper.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/AbstractRequireScrollHelper.java
new file mode 100644
index 0000000..2697371
--- /dev/null
+++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/AbstractRequireScrollHelper.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 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.view.View;
+
+import com.android.setupwizardlib.view.NavigationBar;
+
+/**
+ * Add this helper to require the scroll view to be scrolled to the bottom, making sure that the
+ * user sees all content on the screen. This will change the navigation bar to show the more button
+ * instead of the next button when there is more content to be seen. When the more button is
+ * clicked, the scroll view will be scrolled one page down.
+ */
+public abstract class AbstractRequireScrollHelper implements View.OnClickListener {
+
+ private final NavigationBar mNavigationBar;
+
+ private boolean mScrollNeeded;
+ // Whether the user have seen the more button yet.
+ private boolean mScrollNotified = false;
+
+ protected AbstractRequireScrollHelper(NavigationBar navigationBar) {
+ mNavigationBar = navigationBar;
+ }
+
+ protected void requireScroll() {
+ mNavigationBar.getMoreButton().setOnClickListener(this);
+ }
+
+ protected void notifyScrolledToBottom() {
+ if (mScrollNeeded) {
+ mNavigationBar.post(new Runnable() {
+ @Override
+ public void run() {
+ mNavigationBar.getNextButton().setVisibility(View.VISIBLE);
+ mNavigationBar.getMoreButton().setVisibility(View.GONE);
+ }
+ });
+ mScrollNeeded = false;
+ mScrollNotified = true;
+ }
+ }
+
+ protected void notifyRequiresScroll() {
+ if (!mScrollNeeded && !mScrollNotified) {
+ mNavigationBar.post(new Runnable() {
+ @Override
+ public void run() {
+ mNavigationBar.getNextButton().setVisibility(View.GONE);
+ mNavigationBar.getMoreButton().setVisibility(View.VISIBLE);
+ }
+ });
+ mScrollNeeded = true;
+ }
+ }
+
+ @Override
+ public void onClick(View view) {
+ pageScrollDown();
+ }
+
+ protected abstract void pageScrollDown();
+}
diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java
new file mode 100644
index 0000000..bf4c0c2
--- /dev/null
+++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/DrawableLayoutDirectionHelper.java
@@ -0,0 +1,77 @@
+/*
+ * 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.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
+import android.os.Build;
+import android.view.View;
+
+/**
+ * Provides convenience methods to handle drawable layout directions in different SDK versions.
+ */
+public class DrawableLayoutDirectionHelper {
+
+ /**
+ * Creates an {@link android.graphics.drawable.InsetDrawable} according to the layout direction
+ * of {@code view}.
+ */
+ public static InsetDrawable createRelativeInsetDrawable(Drawable drawable,
+ int insetStart, int insetTop, int insetEnd, int insetBottom, View view) {
+ boolean isRtl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1
+ && view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ return createRelativeInsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom,
+ isRtl);
+ }
+
+ /**
+ * Creates an {@link android.graphics.drawable.InsetDrawable} according to the layout direction
+ * of {@code context}.
+ */
+ public static InsetDrawable createRelativeInsetDrawable(Drawable drawable,
+ int insetStart, int insetTop, int insetEnd, int insetBottom, Context context) {
+ boolean isRtl = false;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ final int layoutDirection =
+ context.getResources().getConfiguration().getLayoutDirection();
+ isRtl = layoutDirection == View.LAYOUT_DIRECTION_RTL;
+ }
+ return createRelativeInsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom,
+ isRtl);
+ }
+
+ /**
+ * Creates an {@link android.graphics.drawable.InsetDrawable} according to
+ * {@code layoutDirection}.
+ */
+ public static InsetDrawable createRelativeInsetDrawable(Drawable drawable,
+ int insetStart, int insetTop, int insetEnd, int insetBottom, int layoutDirection) {
+ //noinspection AndroidLintInlinedApi
+ return createRelativeInsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom,
+ layoutDirection == View.LAYOUT_DIRECTION_RTL);
+ }
+
+ private static InsetDrawable createRelativeInsetDrawable(Drawable drawable,
+ int insetStart, int insetTop, int insetEnd, int insetBottom, boolean isRtl) {
+ if (isRtl) {
+ return new InsetDrawable(drawable, insetEnd, insetTop, insetStart, insetBottom);
+ } else {
+ return new InsetDrawable(drawable, insetStart, insetTop, insetEnd, insetBottom);
+ }
+ }
+}
diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/ListViewRequireScrollHelper.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/ListViewRequireScrollHelper.java
new file mode 100644
index 0000000..7877569
--- /dev/null
+++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/ListViewRequireScrollHelper.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 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.os.Build;
+import android.widget.AbsListView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import com.android.setupwizardlib.view.NavigationBar;
+
+/**
+ * Add this helper to require the list view to be scrolled to the bottom, making sure that the
+ * user sees all content on the screen. This will change the navigation bar to show the more button
+ * instead of the next button when there is more content to be seen. When the more button is
+ * clicked, the list view will be scrolled one page down.
+ */
+public class ListViewRequireScrollHelper extends AbstractRequireScrollHelper
+ implements AbsListView.OnScrollListener {
+
+ public static void requireScroll(NavigationBar navigationBar, ListView listView) {
+ new ListViewRequireScrollHelper(navigationBar, listView).requireScroll();
+ }
+
+ private final ListView mListView;
+
+ private ListViewRequireScrollHelper(NavigationBar navigationBar, ListView listView) {
+ super(navigationBar);
+ mListView = listView;
+ }
+
+ @Override
+ protected void requireScroll() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
+ // APIs to scroll a list only exists on Froyo or above.
+ super.requireScroll();
+ mListView.setOnScrollListener(this);
+
+ final ListAdapter adapter = mListView.getAdapter();
+ if (mListView.getLastVisiblePosition() < adapter.getCount()) {
+ notifyRequiresScroll();
+ }
+ }
+ }
+
+ @Override
+ protected void pageScrollDown() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
+ final int height = mListView.getHeight();
+ mListView.smoothScrollBy(height, 500);
+ }
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ if (firstVisibleItem + visibleItemCount >= totalItemCount) {
+ notifyScrolledToBottom();
+ } else {
+ notifyRequiresScroll();
+ }
+ }
+}
diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/Partner.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/Partner.java
new file mode 100644
index 0000000..fd187d9
--- /dev/null
+++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/Partner.java
@@ -0,0 +1,162 @@
+/*
+ * 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.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import com.android.setupwizardlib.annotations.VisibleForTesting;
+
+/**
+ * Utilities to discover and interact with partner customizations. An overlay package is one that
+ * registers the broadcast receiver for {@code com.android.setupwizard.action.PARTNER_CUSTOMIZATION}
+ * in its manifest. There can only be one customization APK on a device, and it must be bundled with
+ * the system.
+ *
+ * <p>Derived from {@code com.android.launcher3/Partner.java}
+ */
+public class Partner {
+ private static final String TAG = "(SUW) Partner";
+
+ /** Marker action used to discover partner */
+ private static final String
+ ACTION_PARTNER_CUSTOMIZATION = "com.android.setupwizard.action.PARTNER_CUSTOMIZATION";
+
+ private static boolean sSearched = false;
+ private static Partner sPartner;
+
+ /**
+ * Convenience to get a drawable from partner overlay, or if not available, the drawable from
+ * the original context.
+ *
+ * @see #getResourceEntry(android.content.Context, int)
+ */
+ public static Drawable getDrawable(Context context, int id) {
+ final ResourceEntry entry = getResourceEntry(context, id);
+ return entry.resources.getDrawable(entry.id);
+ }
+
+ /**
+ * Convenience to get a string from partner overlay, or if not available, the string from the
+ * original context.
+ *
+ * @see #getResourceEntry(android.content.Context, int)
+ */
+ public static String getString(Context context, int id) {
+ final ResourceEntry entry = getResourceEntry(context, id);
+ return entry.resources.getString(entry.id);
+ }
+
+ /**
+ * Find an entry of resource in the overlay package provided by partners. It will first look for
+ * the resource in the overlay package, and if not available, will return the one in the
+ * original context.
+ *
+ * @return a ResourceEntry in the partner overlay's resources, if one is defined. Otherwise the
+ * resources from the original context is returned. Clients can then get the resource by
+ * {@code entry.resources.getString(entry.id)}, or other methods available in
+ * {@link android.content.res.Resources}.
+ */
+ public static ResourceEntry getResourceEntry(Context context, int id) {
+ final Partner partner = Partner.get(context);
+ if (partner != null) {
+ final Resources ourResources = context.getResources();
+ final String name = ourResources.getResourceEntryName(id);
+ final String type = ourResources.getResourceTypeName(id);
+ final int partnerId = partner.getIdentifier(name, type);
+ if (partnerId != 0) {
+ return new ResourceEntry(partner.mResources, partnerId, true);
+ }
+ }
+ return new ResourceEntry(context.getResources(), id, false);
+ }
+
+ public static class ResourceEntry {
+ public Resources resources;
+ public int id;
+ public boolean isOverlay;
+
+ ResourceEntry(Resources resources, int id, boolean isOverlay) {
+ this.resources = resources;
+ this.id = id;
+ this.isOverlay = isOverlay;
+ }
+ }
+
+ /**
+ * Find and return partner details, or {@code null} if none exists. A partner package is marked
+ * by a broadcast receiver declared in the manifest that handles the
+ * {@code com.android.setupwizard.action.PARTNER_CUSTOMIZATION} intent action. The overlay
+ * package must also be a system package.
+ */
+ public static synchronized Partner get(Context context) {
+ if (!sSearched) {
+ PackageManager pm = context.getPackageManager();
+ final Intent intent = new Intent(ACTION_PARTNER_CUSTOMIZATION);
+ for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
+ if (info.activityInfo == null) {
+ continue;
+ }
+ final ApplicationInfo appInfo = info.activityInfo.applicationInfo;
+ if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ try {
+ final Resources res = pm.getResourcesForApplication(appInfo);
+ sPartner = new Partner(appInfo.packageName, res);
+ break;
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Failed to find resources for " + appInfo.packageName);
+ }
+ }
+ }
+ sSearched = true;
+ }
+ return sPartner;
+ }
+
+ @VisibleForTesting
+ public static synchronized void resetForTesting() {
+ sSearched = false;
+ sPartner = null;
+ }
+
+ private final String mPackageName;
+ private final Resources mResources;
+
+ private Partner(String packageName, Resources res) {
+ mPackageName = packageName;
+ mResources = res;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public Resources getResources() {
+ return mResources;
+ }
+
+ public int getIdentifier(String name, String defType) {
+ return mResources.getIdentifier(name, defType, mPackageName);
+ }
+}
diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/RequireScrollHelper.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/RequireScrollHelper.java
new file mode 100644
index 0000000..cce336f
--- /dev/null
+++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/RequireScrollHelper.java
@@ -0,0 +1,64 @@
+/*
+ * 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.widget.ScrollView;
+
+import com.android.setupwizardlib.view.BottomScrollView;
+import com.android.setupwizardlib.view.NavigationBar;
+
+/**
+ * Add this helper to require the scroll view to be scrolled to the bottom, making sure that the
+ * user sees all content on the screen. This will change the navigation bar to show the more button
+ * instead of the next button when there is more content to be seen. When the more button is
+ * clicked, the scroll view will be scrolled one page down.
+ */
+public class RequireScrollHelper extends AbstractRequireScrollHelper
+ implements BottomScrollView.BottomScrollListener {
+
+ public static void requireScroll(NavigationBar navigationBar, BottomScrollView scrollView) {
+ new RequireScrollHelper(navigationBar, scrollView).requireScroll();
+ }
+
+ private final BottomScrollView mScrollView;
+
+ private RequireScrollHelper(NavigationBar navigationBar, BottomScrollView scrollView) {
+ super(navigationBar);
+ mScrollView = scrollView;
+ }
+
+ @Override
+ protected void requireScroll() {
+ super.requireScroll();
+ mScrollView.setBottomScrollListener(this);
+ }
+
+ @Override
+ protected void pageScrollDown() {
+ mScrollView.pageScroll(ScrollView.FOCUS_DOWN);
+ }
+
+ @Override
+ public void onScrolledToBottom() {
+ notifyScrolledToBottom();
+ }
+
+ @Override
+ public void onRequiresScroll() {
+ notifyRequiresScroll();
+ }
+}
diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/ResultCodes.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/ResultCodes.java
new file mode 100644
index 0000000..a429e73
--- /dev/null
+++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/ResultCodes.java
@@ -0,0 +1,28 @@
+/*
+ * 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 static android.app.Activity.RESULT_FIRST_USER;
+
+public final class ResultCodes {
+
+ public static final int RESULT_SKIP = RESULT_FIRST_USER;
+ public static final int RESULT_RETRY = RESULT_FIRST_USER + 1;
+ public static final int RESULT_ACTIVITY_NOT_FOUND = RESULT_FIRST_USER + 2;
+
+ public static final int RESULT_FIRST_SETUP_USER = RESULT_FIRST_USER + 100;
+}
diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/SystemBarHelper.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/SystemBarHelper.java
new file mode 100644
index 0000000..44bcefc
--- /dev/null
+++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/SystemBarHelper.java
@@ -0,0 +1,348 @@
+/*
+ * 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 android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import com.android.setupwizardlib.R;
+
+/**
+ * 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.
+ *
+ * <p>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.
+ *
+ * <p>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);
+ }
+ }
+
+ 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);
+ } else {
+ addVisibilityFlag(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());
+ }
+ }
+
+ /**
+ * 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.LOLLIPOP)
+ 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.LOLLIPOP)
+ 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 mHandler = new Handler();
+ private Window mWindow;
+ private int mRetries;
+ private OnDecorViewInstalledListener mCallback;
+
+ private Runnable mCheckDecorViewRunnable = 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 = mWindow.peekDecorView();
+ if (decorView != null) {
+ mCallback.onDecorViewInstalled(decorView);
+ } else {
+ mRetries--;
+ if (mRetries >= 0) {
+ // If the decor view is not installed yet, try again in the next loop.
+ mHandler.post(mCheckDecorViewRunnable);
+ } else {
+ Log.w(TAG, "Cannot get decor view of window: " + mWindow);
+ }
+ }
+ }
+ };
+
+ public void getDecorView(Window window, OnDecorViewInstalledListener callback,
+ int retries) {
+ mWindow = window;
+ mRetries = retries;
+ mCallback = callback;
+ mCheckDecorViewRunnable.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 mBottomOffset;
+ private boolean mHasCalculatedBottomOffset = false;
+
+ @Override
+ public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) {
+ if (!mHasCalculatedBottomOffset) {
+ mBottomOffset = getBottomDistance(view);
+ mHasCalculatedBottomOffset = true;
+ }
+
+ int bottomInset = insets.getSystemWindowInsetBottom();
+
+ final int bottomMargin = Math.max(
+ insets.getSystemWindowInsetBottom() - mBottomOffset, 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();
+ }
+}
diff --git a/setupwizardlib/src/main/java/com/android/setupwizardlib/util/WizardManagerHelper.java b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/WizardManagerHelper.java
new file mode 100644
index 0000000..10172ce
--- /dev/null
+++ b/setupwizardlib/src/main/java/com/android/setupwizardlib/util/WizardManagerHelper.java
@@ -0,0 +1,216 @@
+/*
+ * 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.content.Context;
+import android.content.Intent;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.provider.Settings;
+
+public class WizardManagerHelper {
+
+ private static final String ACTION_NEXT = "com.android.wizard.NEXT";
+
+ /*
+ * EXTRA_SCRIPT_URI and EXTRA_ACTION_ID will be removed once all outstanding references have
+ * transitioned to using EXTRA_WIZARD_BUNDLE.
+ */
+ @Deprecated
+ private static final String EXTRA_SCRIPT_URI = "scriptUri";
+ @Deprecated
+ private static final String EXTRA_ACTION_ID = "actionId";
+
+ private static final String EXTRA_WIZARD_BUNDLE = "wizardBundle";
+ private static final String EXTRA_RESULT_CODE = "com.android.setupwizard.ResultCode";
+ private static final String EXTRA_IS_FIRST_RUN = "firstRun";
+
+ public static final String EXTRA_THEME = "theme";
+ public static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode";
+
+ public static final String SETTINGS_GLOBAL_DEVICE_PROVISIONED = "device_provisioned";
+ public static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
+
+ public static final String THEME_HOLO = "holo";
+ public static final String THEME_HOLO_LIGHT = "holo_light";
+ public static final String THEME_MATERIAL = "material";
+ public static final String THEME_MATERIAL_LIGHT = "material_light";
+
+ /**
+ * @deprecated This constant is not used and will not be passed by any released version of setup
+ * wizard.
+ */
+ @Deprecated
+ public static final String THEME_MATERIAL_BLUE = "material_blue";
+
+ /**
+ * @deprecated This constant is not used and will not be passed by any released version of setup
+ * wizard.
+ */
+ @Deprecated
+ public static final String THEME_MATERIAL_BLUE_LIGHT = "material_blue_light";
+
+ /**
+ * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the dark variant of the
+ * theme used in setup wizard for NYC.
+ */
+ public static final String THEME_GLIF = "glif";
+
+ /**
+ * Passed in a setup wizard intent as {@link #EXTRA_THEME}. This is the default theme used in
+ * setup wizard for NYC.
+ */
+ public static final String THEME_GLIF_LIGHT = "glif_light";
+
+ /**
+ * Get an intent that will invoke the next step of setup wizard.
+ *
+ * @param originalIntent The original intent that was used to start the step, usually via
+ * {@link android.app.Activity#getIntent()}.
+ * @param resultCode The result code of the step. See {@link ResultCodes}.
+ * @return A new intent that can be used with
+ * {@link android.app.Activity#startActivityForResult(Intent, int)} to start the next
+ * step of the setup flow.
+ */
+ public static Intent getNextIntent(Intent originalIntent, int resultCode) {
+ return getNextIntent(originalIntent, resultCode, null);
+ }
+
+ /**
+ * Get an intent that will invoke the next step of setup wizard.
+ *
+ * @param originalIntent The original intent that was used to start the step, usually via
+ * {@link android.app.Activity#getIntent()}.
+ * @param resultCode The result code of the step. See {@link ResultCodes}.
+ * @param data An intent containing extra result data.
+ * @return A new intent that can be used with
+ * {@link android.app.Activity#startActivityForResult(Intent, int)} to start the next
+ * step of the setup flow.
+ */
+ public static Intent getNextIntent(Intent originalIntent, int resultCode, Intent data) {
+ Intent intent = new Intent(ACTION_NEXT);
+ copyWizardManagerExtras(originalIntent, intent);
+ intent.putExtra(EXTRA_RESULT_CODE, resultCode);
+ if (data != null && data.getExtras() != null) {
+ intent.putExtras(data.getExtras());
+ }
+ intent.putExtra(EXTRA_THEME, originalIntent.getStringExtra(EXTRA_THEME));
+
+ return intent;
+ }
+
+ /**
+ * Copy the internal extras used by setup wizard from one intent to another. For low-level use
+ * only, such as when using {@link Intent#FLAG_ACTIVITY_FORWARD_RESULT} to relay to another
+ * intent.
+ *
+ * @param srcIntent Intent to get the wizard manager extras from.
+ * @param dstIntent Intent to copy the wizard manager extras to.
+ */
+ public static void copyWizardManagerExtras(Intent srcIntent, Intent dstIntent) {
+ dstIntent.putExtra(EXTRA_WIZARD_BUNDLE, srcIntent.getBundleExtra(EXTRA_WIZARD_BUNDLE));
+ dstIntent.putExtra(EXTRA_IS_FIRST_RUN,
+ srcIntent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false));
+ dstIntent.putExtra(EXTRA_SCRIPT_URI, srcIntent.getStringExtra(EXTRA_SCRIPT_URI));
+ dstIntent.putExtra(EXTRA_ACTION_ID, srcIntent.getStringExtra(EXTRA_ACTION_ID));
+ }
+
+ /**
+ * Check whether an intent is intended to be used within the setup wizard flow.
+ *
+ * @param intent The intent to be checked, usually from
+ * {@link android.app.Activity#getIntent()}.
+ * @return true if the intent passed in was intended to be used with setup wizard.
+ */
+ public static boolean isSetupWizardIntent(Intent intent) {
+ return intent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false);
+ }
+
+ /**
+ * Checks whether the current user has completed Setup Wizard. This is true if the current user
+ * has gone through Setup Wizard. The current user may or may not be the device owner and the
+ * device owner may have already completed setup wizard.
+ *
+ * @param context The context to retrieve the settings.
+ * @return true if the current user has completed Setup Wizard.
+ * @see #isDeviceProvisioned(android.content.Context)
+ */
+ public static boolean isUserSetupComplete(Context context) {
+ if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+ } else {
+ // For versions below JB MR1, there are no user profiles. Just return the global device
+ // provisioned state.
+ return Settings.Secure.getInt(context.getContentResolver(),
+ SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) == 1;
+ }
+ }
+
+ /**
+ * Checks whether the device is provisioned. This means that the device has gone through Setup
+ * Wizard at least once. Note that the user can still be in Setup Wizard even if this is true,
+ * for a secondary user profile triggered through Settings > Add account.
+ *
+ * @param context The context to retrieve the settings.
+ * @return true if the device is provisioned.
+ * @see #isUserSetupComplete(android.content.Context)
+ */
+ public static boolean isDeviceProvisioned(Context context) {
+ if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
+ return Settings.Global.getInt(context.getContentResolver(),
+ SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) == 1;
+ } else {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ SETTINGS_GLOBAL_DEVICE_PROVISIONED, 0) == 1;
+ }
+ }
+
+ /**
+ * Checks the intent whether the extra indicates that the light theme should be used or not. If
+ * the theme is not specified in the intent, or the theme specified is unknown, the value def
+ * will be returned.
+ *
+ * @param intent The intent used to start the activity, which the theme extra will be read from.
+ * @param def The default value if the theme is not specified.
+ * @return True if the activity started by the given intent should use light theme.
+ */
+ public static boolean isLightTheme(Intent intent, boolean def) {
+ final String theme = intent.getStringExtra(EXTRA_THEME);
+ return isLightTheme(theme, def);
+ }
+
+ /**
+ * Checks whether {@code theme} represents a light or dark theme. If the theme specified is
+ * unknown, the value def will be returned.
+ *
+ * @param theme The theme as specified from an intent sent from setup wizard.
+ * @param def The default value if the theme is not known.
+ * @return True if {@code theme} represents a light theme.
+ */
+ public static boolean isLightTheme(String theme, boolean def) {
+ if (THEME_HOLO_LIGHT.equals(theme) || THEME_MATERIAL_LIGHT.equals(theme)
+ || THEME_MATERIAL_BLUE_LIGHT.equals(theme) || THEME_GLIF_LIGHT.equals(theme)) {
+ return true;
+ } else if (THEME_HOLO.equals(theme) || THEME_MATERIAL.equals(theme)
+ || THEME_MATERIAL_BLUE.equals(theme) || THEME_GLIF.equals(theme)) {
+ return false;
+ } else {
+ return def;
+ }
+ }
+}