Replace autoadvance screens with waiting screen in PS setup

This change replaces the auto advancing screens shown during private
space setup flow with a single loading screen.

Recording: b/332652637#comment3

Bug: 332652637
Test: Manual
Change-Id: I470e5c6ece16cfefd0734d3daadf1d8efe963f63
This commit is contained in:
josephpv
2024-04-04 17:26:45 +00:00
parent 727ec97362
commit af800e55f4
13 changed files with 173 additions and 287 deletions

View File

@@ -1,46 +0,0 @@
<!--
~ Copyright (C) 2023 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.
-->
<com.google.android.setupdesign.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/private_space_autoadvance_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:icon="@drawable/ic_private_space_icon">
<LinearLayout style="@style/SudContentFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/lottie_animation"
style="@style/SudContentIllustration"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:lottie_rawRes="@null"/>
<TextView
android:id="@+id/createMessage"
style="@style/PrivateSpaceSetupTextFontStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="@string/private_space_setting_up_text"
android:layout_marginBottom="24dp"/>
</LinearLayout>
</com.google.android.setupdesign.GlifLayout>

View File

@@ -25,6 +25,6 @@
android:icon="@drawable/ic_delete_accent"
app:sudUsePartnerHeavyTheme="true"
app:sudIllustrationType="default"
app:sudDescriptionText = "@string/private_space_confirm_deletion_summary"
app:sudDescriptionText = "@string/private_space_few_moments_text"
app:sucHeaderText="@string/private_space_confirm_deletion_header">
</com.google.android.setupdesign.GlifLoadingLayout>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2024 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.
-->
<com.google.android.setupdesign.GlifLoadingLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/private_space_create_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:icon="@drawable/ic_private_space_icon"
app:sudUsePartnerHeavyTheme="true"
app:sudIllustrationType="default"
app:sudDescriptionText = "@string/private_space_few_moments_text"
app:sucHeaderText="@string/private_space_setting_up_text">
</com.google.android.setupdesign.GlifLoadingLayout>

View File

@@ -23,14 +23,14 @@
android:name="com.android.settings.privatespace.PrivateSpaceEducation"
android:label="fragment_ps_education">
<action
android:id="@+id/action_education_to_auto_advance"
app:destination="@id/ps_auto_advance_fragment"/>
android:id="@+id/action_education_to_create"
app:destination="@id/ps_create_fragment"/>
</fragment>
<fragment android:id="@+id/ps_auto_advance_fragment"
android:name="com.android.settings.privatespace.AutoAdvanceSetupFragment"
android:label="fragment_ps_auto_advance">
<fragment android:id="@+id/ps_create_fragment"
android:name="com.android.settings.privatespace.PrivateSpaceCreationFragment"
android:label="fragment_ps_create">
<action
android:id="@+id/action_advance_profile_error"
android:id="@+id/action_create_profile_error"
app:destination="@id/ps_profile_error_fragment"/>
<action
android:id="@+id/action_set_lock_fragment"
@@ -44,7 +44,7 @@
android:label="fragment_ps_error">
<action
android:id="@+id/action_retry_profile_creation"
app:destination="@id/ps_auto_advance_fragment"/>
app:destination="@id/ps_create_fragment"/>
</fragment>
<fragment android:id="@+id/ps_pre_finish_delay_fragment"
android:name="com.android.settings.privatespace.SetupPreFinishDelayFragment"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1295,8 +1295,6 @@
<string name="private_space_delete_button_label">Delete</string>
<!-- Title for the private space delete confirmation page. [CHAR LIMIT=40] -->
<string name="private_space_confirm_deletion_header">Deleting private space\u2026</string>
<!-- Description for private space delete confirmation page that mentions it will take a few moments. [CHAR LIMIT=40] -->
<string name="private_space_confirm_deletion_summary">This will take a few moments</string>
<!-- Toast to show when the private space was deleted. [CHAR LIMIT=NONE] -->
<string name="private_space_deleted">Private space deleted</string>
<!-- Toast to show when the private space could not be deleted. [CHAR LIMIT=NONE] -->
@@ -1332,14 +1330,10 @@
<!-- Private space footer link content description [CHAR LIMIT=40] -->
<string name="private_space_learn_more_text">Learn more about private space</string>
<string name="private_space_learn_more_url" translatable="false">https://support.google.com/android?p=private_space</string>
<!-- Text shown at the bottom in private space auto advancing screens. [CHAR LIMIT=60] -->
<!-- Description for private space loading page that mentions it will take a few moments. [CHAR LIMIT=40] -->
<string name="private_space_few_moments_text">This will take a few moments</string>
<!-- Title shown in private space creation screen. [CHAR LIMIT=60] -->
<string name="private_space_setting_up_text">Setting up private space\u2026</string>
<!-- Title for private space setup in auto advancing screen informing private space notifications are hidden when locked. [CHAR LIMIT=NONE] -->
<string name="private_space_notifications_hidden_title">Notifications from private space apps are hidden when it\u2019s locked</string>
<!-- Title for private space setup in auto advancing screen informing to explore private space settings for hide and auto lock. [CHAR LIMIT=NONE] -->
<string name="private_space_explore_settings_title">Explore private space settings to hide private space and set up automatic locking</string>
<!-- Title for private space setup in auto advancing screen informing some system apps are already installed in private space. [CHAR LIMIT=NONE] -->
<string name="private_space_apps_installed_title">Required apps are already installed in your private space</string>
<!-- Title for private space creation error screen. [CHAR LIMIT=60] -->
<string name="private_space_error_screen_title">Couldn\u2019t set up a private space</string>
<!-- Label for button to retry creating private space again on creation error. [CHAR LIMIT=30] -->

View File

@@ -1,218 +0,0 @@
/*
* Copyright (C) 2023 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.settings.privatespace;
import static android.text.Layout.BREAK_STRATEGY_SIMPLE;
import static android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.Nullable;
import androidx.navigation.fragment.NavHostFragment;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
import com.airbnb.lottie.LottieAnimationView;
import com.google.android.setupdesign.GlifLayout;
import com.google.common.collect.ImmutableList;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/** Fragment to show screens that auto advance during private space setup flow */
public class AutoAdvanceSetupFragment extends InstrumentedFragment {
private static final String TAG = "AutoAdvanceFragment";
private static final String TITLE_INDEX = "title_index";
private static final int DELAY_BETWEEN_SCREENS = 5000; // 5 seconds in millis
private static final int ANIMATION_DURATION_MILLIS = 500;
private static final int HEADER_TEXT_MAX_LINES = 4;
private GlifLayout mRootView;
private static final Handler sHandler = new Handler(Looper.getMainLooper());
private int mScreenTitleIndex;
private static final List<Pair<Integer, Integer>> HEADER_ILLUSTRATION_PAIRS =
ImmutableList.of(
new Pair(R.string.private_space_notifications_hidden_title,
R.raw.private_space_notifications_illustration),
new Pair(R.string.private_space_apps_installed_title,
R.raw.private_space_unlock_to_share_illustration),
new Pair(R.string.private_space_explore_settings_title,
R.raw.private_space_placeholder_illustration));
private Runnable mUpdateScreenResources =
new Runnable() {
@Override
public void run() {
if (getActivity() != null) {
if (++mScreenTitleIndex < HEADER_ILLUSTRATION_PAIRS.size()) {
startFadeOutAnimation();
sHandler.postDelayed(mUpdateScreenResources, DELAY_BETWEEN_SCREENS);
} else if (PrivateSpaceMaintainer.getInstance(getActivity())
.doesPrivateSpaceExist()) {
mMetricsFeatureProvider.action(
getContext(),
SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_CREATED,
true);
if (isConnectedToInternet()) {
NavHostFragment.findNavController(AutoAdvanceSetupFragment.this)
.navigate(R.id.action_account_intro_fragment);
} else {
NavHostFragment.findNavController(AutoAdvanceSetupFragment.this)
.navigate(R.id.action_set_lock_fragment);
}
} else {
mMetricsFeatureProvider.action(
getContext(),
SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_CREATED,
false);
showPrivateSpaceErrorScreen();
}
}
}
};
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
if (android.os.Flags.allowPrivateProfile()
&& android.multiuser.Flags.enablePrivateSpaceFeatures()) {
super.onCreate(savedInstanceState);
}
}
@Override
public View onCreateView(
LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (savedInstanceState == null) {
if (PrivateSpaceMaintainer.getInstance(getActivity()).createPrivateSpace()) {
Log.i(TAG, "Private Space created");
}
} else {
mScreenTitleIndex = savedInstanceState.getInt(TITLE_INDEX);
if (mScreenTitleIndex >= HEADER_ILLUSTRATION_PAIRS.size()) {
return super.onCreateView(inflater, container, savedInstanceState);
}
}
mRootView =
(GlifLayout)
inflater.inflate(R.layout.private_space_advancing_screen, container, false);
mRootView.getHeaderTextView().setMaxLines(HEADER_TEXT_MAX_LINES);
mRootView.getHeaderTextView().setBreakStrategy(BREAK_STRATEGY_SIMPLE);
mRootView.getHeaderTextView().setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
updateHeaderAndIllustration();
OnBackPressedCallback callback =
new OnBackPressedCallback(true /* enabled by default */) {
@Override
public void handleOnBackPressed() {
// Handle the back button event. We intentionally don't want to allow back
// button to work in this screen during the setup flow.
}
};
requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
return mRootView;
}
@Override
public void onSaveInstanceState(@NotNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(TITLE_INDEX, mScreenTitleIndex);
}
@Override
public void onDestroy() {
sHandler.removeCallbacks(mUpdateScreenResources);
super.onDestroy();
}
@Override
public void onResume() {
sHandler.postDelayed(mUpdateScreenResources, DELAY_BETWEEN_SCREENS);
super.onResume();
}
@Override
public int getMetricsCategory() {
return SettingsEnums.PRIVATE_SPACE_SETUP_SPACE_CREATION;
}
private void showPrivateSpaceErrorScreen() {
NavHostFragment.findNavController(AutoAdvanceSetupFragment.this)
.navigate(R.id.action_advance_profile_error);
}
private void updateHeaderAndIllustration() {
mRootView.setHeaderText(HEADER_ILLUSTRATION_PAIRS.get(mScreenTitleIndex).first);
LottieAnimationView animationView = mRootView.findViewById(R.id.lottie_animation);
animationView.setAnimation(HEADER_ILLUSTRATION_PAIRS.get(mScreenTitleIndex).second);
animationView.playAnimation();
startFadeInAnimation();
}
private void startFadeInAnimation() {
ValueAnimator textView = ObjectAnimator.ofFloat(
mRootView.getHeaderTextView(), View.ALPHA, 0f, 1f);
ValueAnimator lottieView = ObjectAnimator.ofFloat(
mRootView.findViewById(R.id.lottie_animation), View.ALPHA, 0, 1f);
AnimatorSet fadeIn = new AnimatorSet();
fadeIn.playTogether(textView, lottieView);
fadeIn.setDuration(ANIMATION_DURATION_MILLIS).start();
}
private void startFadeOutAnimation() {
AnimatorSet fadeOut = new AnimatorSet();
ValueAnimator textView = ObjectAnimator.ofFloat(
mRootView.getHeaderTextView(), View.ALPHA, 1f, 0f);
ValueAnimator lottieView = ObjectAnimator.ofFloat(
mRootView.findViewById(R.id.lottie_animation), View.ALPHA, 1f, 0f);
fadeOut.playTogether(textView, lottieView);
fadeOut.setDuration(ANIMATION_DURATION_MILLIS).start();
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
updateHeaderAndIllustration();
}
});
}
/** Returns true if device has an active internet connection, false otherwise. */
private boolean isConnectedToInternet() {
ConnectivityManager cm =
(ConnectivityManager)
getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
}
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright (C) 2024 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.settings.privatespace;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.navigation.fragment.NavHostFragment;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
import com.google.android.setupdesign.GlifLayout;
/** Fragment to a show loading screen and create private profile during private space setup flow */
public class PrivateSpaceCreationFragment extends InstrumentedFragment {
private static final String TAG = "PrivateSpaceCreateFrag";
private static final int PRIVATE_SPACE_CREATE_POST_DELAY_MS = 1000;
private static final Handler sHandler = new Handler(Looper.getMainLooper());
private Runnable mRunnable =
() -> {
createPrivateSpace();
};
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
if (android.os.Flags.allowPrivateProfile()
&& android.multiuser.Flags.enablePrivateSpaceFeatures()) {
super.onCreate(savedInstanceState);
}
}
@NonNull
@Override
public View onCreateView(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
GlifLayout rootView =
(GlifLayout)
inflater.inflate(R.layout.private_space_create_screen, container, false);
OnBackPressedCallback callback =
new OnBackPressedCallback(true /* enabled by default */) {
@Override
public void handleOnBackPressed() {
// Handle the back button event. We intentionally don't want to allow back
// button to work in this screen during the setup flow.
}
};
requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
return rootView;
}
@Override
public void onResume() {
super.onResume();
// Ensures screen visibility to user by introducing a 1-second delay before creating private
// space.
sHandler.postDelayed(mRunnable, PRIVATE_SPACE_CREATE_POST_DELAY_MS);
}
@Override
public void onDestroy() {
sHandler.removeCallbacks(mRunnable);
super.onDestroy();
}
private void createPrivateSpace() {
if (PrivateSpaceMaintainer.getInstance(getActivity()).createPrivateSpace()) {
Log.i(TAG, "Private Space created");
mMetricsFeatureProvider.action(
getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_CREATED, true);
if (isConnectedToInternet()) {
NavHostFragment.findNavController(PrivateSpaceCreationFragment.this)
.navigate(R.id.action_account_intro_fragment);
} else {
NavHostFragment.findNavController(PrivateSpaceCreationFragment.this)
.navigate(R.id.action_set_lock_fragment);
}
} else {
mMetricsFeatureProvider.action(
getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_CREATED, false);
showPrivateSpaceErrorScreen();
}
}
@Override
public int getMetricsCategory() {
return SettingsEnums.PRIVATE_SPACE_SETUP_SPACE_CREATION;
}
private void showPrivateSpaceErrorScreen() {
NavHostFragment.findNavController(PrivateSpaceCreationFragment.this)
.navigate(R.id.action_create_profile_error);
}
/** Returns true if device has an active internet connection, false otherwise. */
private boolean isConnectedToInternet() {
ConnectivityManager cm =
(ConnectivityManager) getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
}
}

View File

@@ -91,7 +91,7 @@ public class PrivateSpaceEducation extends InstrumentedFragment {
getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_START);
Log.i(TAG, "Starting private space setup");
NavHostFragment.findNavController(PrivateSpaceEducation.this)
.navigate(R.id.action_education_to_auto_advance);
.navigate(R.id.action_education_to_create);
};
}

View File

@@ -77,7 +77,7 @@ public class PrivateSpaceMaintainer {
*
* <p> This method should be used by the Private Space Setup Flow ONLY.
*/
@VisibleForTesting
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public final synchronized boolean createPrivateSpace() {
if (!Flags.allowPrivateProfile()
|| !android.multiuser.Flags.enablePrivateSpaceFeatures()) {