Commit ebdf8a1e authored by Wenyu Fu's avatar Wenyu Fu Committed by Commit Bot

[CCTToSFRE] Add ToSDialogPolicyLoad Listener into Lightweight FRE

Support skipping ToS on Lightweight FRE.
Add loading view into ToS dialog in Lightweight FRE.

Bug: 1108549
Change-Id: Idf28657e896e7586c8d0070f1d95ace70961a3ef
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2510549Reviewed-by: default avatarIlya Sherman <isherman@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarSky Malice <skym@chromium.org>
Commit-Queue: Wenyu Fu <wenyufu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#826815}
parent f4a8c093
......@@ -23,23 +23,49 @@
android:scaleType="fitCenter"
android:src="@drawable/product_logo_name" />
<org.chromium.ui.widget.TextViewWithClickableSpans
android:id="@+id/lightweight_fre_tos_and_privacy"
android:layout_width="wrap_content"
<FrameLayout
android:id="@+id/lightweight_fre_body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/lightweight_fre_head_image"
android:layout_marginBottom="32dp"
android:lineSpacingMultiplier="1.64"
android:paddingEnd="24dp"
android:paddingStart="24dp"
android:textAppearance="@style/TextAppearance.TextMedium.Primary" />
android:layout_marginBottom="32dp">
<org.chromium.ui.widget.TextViewWithClickableSpans
android:id="@+id/lightweight_fre_tos_and_privacy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lineSpacingMultiplier="1.64"
android:paddingEnd="24dp"
android:paddingStart="24dp"
android:textAppearance="@style/TextAppearance.TextMedium.Primary" />
<!-- The FrameLayout here is to facilitate adding a proper content description for
the loading view. During development, it didn't seem possible to override the
LoadingView contentDescription in XML, but if there's support for this at some
point then we can remove the FrameLayout. -->
<FrameLayout
android:id="@+id/loading_view_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:contentDescription="@string/sync_loading"
android:visibility="gone">
<org.chromium.components.browser_ui.widget.LoadingView
android:id="@+id/loading_view"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_height="@dimen/fre_loading_spinner_size"
android:layout_width="@dimen/fre_loading_spinner_size"
android:visibility="gone"/>
</FrameLayout>
</FrameLayout>
<org.chromium.components.browser_ui.widget.DualControlLayout
android:id="@+id/lightweight_fre_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_below="@id/lightweight_fre_tos_and_privacy"
android:layout_below="@id/lightweight_fre_body"
android:paddingEnd="8dp"
android:paddingStart="8dp"
app:buttonAlignment="end"
......
......@@ -250,16 +250,19 @@ public abstract class FirstRunFlowSequencer {
// Promo pages are removed, so there is nothing else to show in FRE.
return false;
}
if (isCct && FirstRunStatus.isEphemeralSkipFirstRun()) {
if (FirstRunStatus.isEphemeralSkipFirstRun() && (isCct || preferLightweightFre)) {
// Domain policies may have caused CCTs to skip the FRE. While this needs to be figured
// out at runtime for each app restart, it should apply to all CCTs for the duration of
// the app's lifetime.
// TODO(https://crbug.com/1108582): Replace this with a shared pref.
return false;
}
return !preferLightweightFre
|| (!FirstRunStatus.shouldSkipWelcomePage()
&& !FirstRunStatus.getLightweightFirstRunFlowComplete());
if (preferLightweightFre
&& (FirstRunStatus.shouldSkipWelcomePage()
|| FirstRunStatus.getLightweightFirstRunFlowComplete())) {
return false;
}
return true;
}
/**
......
......@@ -6,16 +6,27 @@ package org.chromium.chrome.browser.firstrun;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.SystemClock;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.IntentUtils;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.supplier.OneshotSupplierImpl;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.policy.EnterpriseInfo;
import org.chromium.chrome.browser.policy.PolicyServiceFactory;
import org.chromium.components.browser_ui.widget.LoadingView;
import org.chromium.components.policy.PolicyService;
import org.chromium.components.signin.ChildAccountStatus;
import org.chromium.ui.base.LocalizationUtils;
import org.chromium.ui.text.NoUnderlineClickableSpan;
......@@ -25,15 +36,45 @@ import org.chromium.ui.text.SpanApplier.SpanInfo;
/**
* Lightweight FirstRunActivity. It shows ToS dialog only.
*/
public class LightweightFirstRunActivity extends FirstRunActivityBase {
public class LightweightFirstRunActivity
extends FirstRunActivityBase implements LoadingView.Observer {
// TODO(https://crbug.com/1148081) Clean this boolean when releasing this feature, and remove
// @Nullable from members below.
private static boolean sSupportSkippingTos = true;
private @Nullable FirstRunAppRestrictionInfo mFirstRunAppRestrictionInfo;
private @Nullable SkipTosDialogPolicyListener mSkipTosDialogPolicyListener;
private @Nullable OneshotSupplierImpl<PolicyService> mPolicyServiceSupplier;
private FirstRunFlowSequencer mFirstRunFlowSequencer;
private TextView mTosAndPrivacyTextView;
private Button mOkButton;
private LoadingView mLoadingView;
private View mLoadingViewContainer;
private View mLightweightFreButtons;
private boolean mViewCreated;
private boolean mNativeInitialized;
private boolean mTriggerAcceptAfterNativeInit;
private long mViewCreatedTimeMs;
public static final String EXTRA_ASSOCIATED_APP_NAME =
"org.chromium.chrome.browser.firstrun.AssociatedAppName";
public LightweightFirstRunActivity() {
if (sSupportSkippingTos) {
mFirstRunAppRestrictionInfo = FirstRunAppRestrictionInfo.takeMaybeInitialized();
mPolicyServiceSupplier = new OneshotSupplierImpl<>();
mSkipTosDialogPolicyListener = new SkipTosDialogPolicyListener(
mFirstRunAppRestrictionInfo, mPolicyServiceSupplier,
EnterpriseInfo.getInstance(), new LightWeightTosDialogMetricsNameProvider());
// We can ignore the result from #onAvailable here, as views are not created at this
// point.
mSkipTosDialogPolicyListener.onAvailable((b) -> onPolicyLoadListenerAvailable());
}
}
@Override
public void triggerLayoutInflation() {
setFinishOnTouchOutside(true);
......@@ -49,7 +90,7 @@ public class LightweightFirstRunActivity extends FirstRunActivityBase {
@ChildAccountStatus.Status
int childAccountStatus = freProperties.getInt(
SigninFirstRunFragment.CHILD_ACCOUNT_STATUS, ChildAccountStatus.NOT_CHILD);
onChildAccountKnown(ChildAccountStatus.isChild(childAccountStatus));
initializeViews(ChildAccountStatus.isChild(childAccountStatus));
}
};
mFirstRunFlowSequencer.start();
......@@ -57,7 +98,7 @@ public class LightweightFirstRunActivity extends FirstRunActivityBase {
}
/** Called once it is known whether the device has a child account. */
public void onChildAccountKnown(boolean hasChildAccount) {
private void initializeViews(boolean hasChildAccount) {
setContentView(LayoutInflater.from(LightweightFirstRunActivity.this)
.inflate(R.layout.lightweight_fre_tos, null));
......@@ -87,11 +128,12 @@ public class LightweightFirstRunActivity extends FirstRunActivityBase {
new SpanInfo("<LINK1>", "</LINK1>", clickableGoogleTermsSpan),
new SpanInfo("<LINK2>", "</LINK2>", clickableChromeAdditionalTermsSpan));
}
TextView tosAndPrivacyTextView =
(TextView) findViewById(R.id.lightweight_fre_tos_and_privacy);
tosAndPrivacyTextView.setText(tosAndPrivacyText);
tosAndPrivacyTextView.setMovementMethod(LinkMovementMethod.getInstance());
mTosAndPrivacyTextView = (TextView) findViewById(R.id.lightweight_fre_tos_and_privacy);
mTosAndPrivacyTextView.setText(tosAndPrivacyText);
mTosAndPrivacyTextView.setMovementMethod(LinkMovementMethod.getInstance());
mLightweightFreButtons = findViewById(R.id.lightweight_fre_buttons);
mOkButton = (Button) findViewById(R.id.button_primary);
int okButtonHorizontalPadding =
getResources().getDimensionPixelSize(R.dimen.fre_button_padding);
......@@ -101,6 +143,57 @@ public class LightweightFirstRunActivity extends FirstRunActivityBase {
((Button) findViewById(R.id.button_secondary))
.setOnClickListener(view -> abortFirstRunExperience());
mLoadingView = findViewById(R.id.loading_view);
mLoadingViewContainer = findViewById(R.id.loading_view_container);
mViewCreated = true;
mViewCreatedTimeMs = SystemClock.elapsedRealtime();
if (mSkipTosDialogPolicyListener != null) {
// Check if we need to setup logic for policy loading.
if (mSkipTosDialogPolicyListener.get() == null) {
mLoadingView.addObserver(this);
mLoadingView.showLoadingUI();
setTosComponentVisibility(false);
} else if (mSkipTosDialogPolicyListener.get()) {
skipTosByPolicy();
}
}
}
private void setTosComponentVisibility(boolean isVisible) {
int visibility = isVisible ? View.VISIBLE : View.INVISIBLE;
mTosAndPrivacyTextView.setVisibility(visibility);
mLightweightFreButtons.setVisibility(visibility);
}
private void onPolicyLoadListenerAvailable() {
if (mViewCreated) mLoadingView.hideLoadingUI();
}
@Override
public void onShowLoadingUIComplete() {
mLoadingViewContainer.setVisibility(View.VISIBLE);
}
@Override
public void onHideLoadingUIComplete() {
assert mSkipTosDialogPolicyListener != null && mSkipTosDialogPolicyListener.get() != null;
RecordHistogram.recordTimesHistogram("MobileFre.Lightweight.LoadingDuration",
SystemClock.elapsedRealtime() - mViewCreatedTimeMs);
if (mSkipTosDialogPolicyListener.get()) {
skipTosByPolicy();
} else {
// Else, show the ToS as the loading spinner is GONE.
boolean hasAccessibilityFocus = mLoadingViewContainer.isAccessibilityFocused();
mLoadingViewContainer.setVisibility(View.GONE);
setTosComponentVisibility(true);
if (hasAccessibilityFocus) {
mTosAndPrivacyTextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
}
}
@Override
......@@ -109,6 +202,13 @@ public class LightweightFirstRunActivity extends FirstRunActivityBase {
assert !mNativeInitialized;
mNativeInitialized = true;
boolean isPolicyServiceNeeded =
mSkipTosDialogPolicyListener != null && mSkipTosDialogPolicyListener.get() == null;
if (mPolicyServiceSupplier != null && isPolicyServiceNeeded) {
mPolicyServiceSupplier.set(PolicyServiceFactory.getGlobalPolicyService());
}
if (mTriggerAcceptAfterNativeInit) acceptTermsOfService();
}
......@@ -117,15 +217,35 @@ public class LightweightFirstRunActivity extends FirstRunActivityBase {
abortFirstRunExperience();
}
public void abortFirstRunExperience() {
@Override
public void onDestroy() {
super.onDestroy();
mLoadingView.destroy();
// As first run is complete, we no longer need FirstRunAppRestrictionInfo.
if (mFirstRunAppRestrictionInfo != null) mFirstRunAppRestrictionInfo.destroy();
if (mSkipTosDialogPolicyListener != null) mSkipTosDialogPolicyListener.destroy();
}
private void abortFirstRunExperience() {
finish();
notifyCustomTabCallbackFirstRunIfNecessary(getIntent(), false);
}
public void completeFirstRunExperience() {
FirstRunStatus.setLightweightFirstRunFlowComplete(true);
finish();
exitLightweightFirstRun();
}
private void skipTosByPolicy() {
// TODO(crbug.com/1108564): Show the different UI that has the enterprise disclosure.
FirstRunStatus.setEphemeralSkipFirstRun(true);
exitLightweightFirstRun();
}
private void exitLightweightFirstRun() {
finish();
sendFirstRunCompletePendingIntent();
}
......@@ -149,4 +269,26 @@ public class LightweightFirstRunActivity extends FirstRunActivityBase {
CustomTabActivity.showInfoPage(
this, LocalizationUtils.substituteLocalePlaceholder(getString(url)));
}
private class LightWeightTosDialogMetricsNameProvider
implements SkipTosDialogPolicyListener.HistogramNameProvider {
@Override
public String getOnDeviceOwnedDetectedTimeHistogramName() {
return mViewCreated
? "MobileFre.Lightweight.IsDeviceOwnedCheckSpeed.SlowerThanInflation"
: "MobileFre.Lightweight.IsDeviceOwnedCheckSpeed.FasterThanInflation";
}
@Override
public String getOnPolicyAvailableTimeHistogramName() {
return mViewCreated
? "MobileFre.Lightweight.EnterprisePolicyCheckSpeed.SlowerThanInflation"
: "MobileFre.Lightweight.EnterprisePolicyCheckSpeed.FasterThanInflation";
}
}
@VisibleForTesting
public static void setSupportSkippingTos(boolean isSupported) {
sSupportSkippingTos = isSupported;
}
}
......@@ -77,6 +77,10 @@ public class TosAndUmaFirstRunFragmentWithEnterpriseSupport
mLoadingSpinner.destroy();
mLoadingSpinner = null;
}
if (mSkipTosDialogPolicyListener != null) {
mSkipTosDialogPolicyListener.destroy();
mSkipTosDialogPolicyListener = null;
}
super.onDestroy();
}
......@@ -113,7 +117,9 @@ public class TosAndUmaFirstRunFragmentWithEnterpriseSupport
@Override
public void onNativeInitialized() {
super.onNativeInitialized();
mPolicyServiceProvider.set(PolicyServiceFactory.getGlobalPolicyService());
if (mSkipTosDialogPolicyListener != null && mSkipTosDialogPolicyListener.get() == null) {
mPolicyServiceProvider.set(PolicyServiceFactory.getGlobalPolicyService());
}
}
@Override
......
......@@ -4,12 +4,14 @@
package org.chromium.chrome.test.pagecontroller.tests.webapk;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.support.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
......@@ -18,11 +20,16 @@ import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.Log;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
import org.chromium.chrome.browser.firstrun.LightweightFirstRunActivity;
import org.chromium.chrome.browser.webapps.WebappRegistry;
import org.chromium.chrome.test.pagecontroller.controllers.webapk.first_run.LightWeightTOSController;
import org.chromium.chrome.test.pagecontroller.rules.ChromeUiApplicationTestRule;
......@@ -35,12 +42,17 @@ import org.chromium.components.webapk.lib.client.WebApkValidator;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
/**
* Test the Maps Go First Run Experience.
* Test the Maps Go First Run Experience. This test will be mostly focus on verifying the
* lightweight first run is activated in different scenarios.
*/
@SmallTest
@RunWith(BaseJUnit4ClassRunner.class)
public class MapsGoFirstRunTest {
private static final String TAG = "MapsGoFirstRunTest";
private static final String FLAG_POLICY_TOS_DIALOG_BEHAVIOR_STANDARD =
"policy={\"TosDialogBehavior\":1}";
private static final String FLAG_POLICY_TOS_DIALOG_BEHAVIOR_SKIP =
"policy={\"TosDialogBehavior\":2}";
private static final long MAPS_GO_FRE_TIMEOUT_MS = 9000L;
public ChromeUiAutomatorTestRule mUiAutomatorRule = new ChromeUiAutomatorTestRule();
public ChromeUiApplicationTestRule mChromeUiRule = new ChromeUiApplicationTestRule();
......@@ -48,15 +60,34 @@ public class MapsGoFirstRunTest {
@Rule
public final TestRule mChain = RuleChain.outerRule(mChromeUiRule).around(mUiAutomatorRule);
private Activity mLightweightFreActivity;
private ApplicationStatus.ActivityStateListener mActivityStateListener;
private final CallbackHelper mFreStoppedCallback = new CallbackHelper();
@Before
public void setUp() {
WebApkValidator.setDisableValidationForTesting(true);
TestThreadUtils.runOnUiThreadBlocking(WebappRegistry::refreshSharedPrefsForTesting);
mActivityStateListener = (activity, newState) -> {
if (activity instanceof LightweightFirstRunActivity) {
if (mLightweightFreActivity == null) mLightweightFreActivity = activity;
if (newState == ActivityState.STOPPED) mFreStoppedCallback.notifyCalled();
}
};
ApplicationStatus.registerStateListenerForAllActivities(mActivityStateListener);
}
@After
public void tearDown() {
LightweightFirstRunActivity.setSupportSkippingTos(true);
ApplicationStatus.unregisterActivityStateListener(mActivityStateListener);
}
@Test
@DisabledTest(message = "https://crbug.com/1142821")
public void testFirstRunIsShown() {
LightweightFirstRunActivity.setSupportSkippingTos(false);
FirstRunStatus.setLightweightFirstRunFlowComplete(false);
launchWebapk("org.chromium.test.maps_go_webapk", "org.chromium.chrome");
......@@ -69,12 +100,41 @@ public class MapsGoFirstRunTest {
@Test
public void testFirstRunIsNotShownAfterAck() {
LightweightFirstRunActivity.setSupportSkippingTos(false);
FirstRunStatus.setLightweightFirstRunFlowComplete(true);
launchWebapk("org.chromium.test.maps_go_webapk", "org.chromium.chrome");
LightWeightTOSController controller = LightWeightTOSController.getInstance();
Assert.assertFalse(
"Light weight TOS page should NOT be shown.", controller.isCurrentPageThis());
Assert.assertNull("Lightweight FRE should not launch.", mLightweightFreActivity);
}
@Test
@CommandLineFlags.Add({"force-device-ownership", FLAG_POLICY_TOS_DIALOG_BEHAVIOR_SKIP})
public void testTosSkipped() throws Exception {
LightweightFirstRunActivity.setSupportSkippingTos(true);
FirstRunStatus.setLightweightFirstRunFlowComplete(false);
launchWebapk("org.chromium.test.maps_go_webapk", "org.chromium.chrome");
Assert.assertNotNull("Lightweight FRE should launch.", mLightweightFreActivity);
LightWeightTOSController controller = LightWeightTOSController.getInstance();
Assert.assertFalse(
"Light weight TOS page should NOT be shown.", controller.isCurrentPageThis());
mFreStoppedCallback.waitForCallback("Lightweight Fre never completes.", 0);
}
@Test
@CommandLineFlags.Add({FLAG_POLICY_TOS_DIALOG_BEHAVIOR_STANDARD})
public void testTosNotSkippedByPolicy() {
LightweightFirstRunActivity.setSupportSkippingTos(true);
FirstRunStatus.setLightweightFirstRunFlowComplete(false);
launchWebapk("org.chromium.test.maps_go_webapk", "org.chromium.chrome");
Assert.assertNotNull("Lightweight FRE should launch.", mLightweightFreActivity);
LightWeightTOSController controller = LightWeightTOSController.getInstance();
Assert.assertTrue("Light weight TOS page should be shown.", controller.isCurrentPageThis());
}
/**
......
......@@ -781,6 +781,44 @@ reviews. Googlers can read more about this at go/gwsq-gerrit.
</summary>
</histogram>
<histogram
name="MobileFre.Lightweight.EnterprisePolicyCheckSpeed{MobileFreInflationSpeedComparison}"
units="ms" expires_after="2021-07-31">
<owner>skym@chromium.org</owner>
<owner>wenyufu@chromium.org</owner>
<summary>
Android: Records the time it takes from Lightweight FRE launch to the
enterprise policy check completing. This check is often skipped when its
result becomes irrelevant. {MobileFreInflationSpeedComparison}
</summary>
<token key="MobileFreInflationSpeedComparison"
variants="MobileFreInflationSpeedComparison"/>
</histogram>
<histogram
name="MobileFre.Lightweight.IsDeviceOwnedCheckSpeed{MobileFreInflationSpeedComparison}"
units="ms" expires_after="2021-07-31">
<owner>skym@chromium.org</owner>
<owner>wenyufu@chromium.org</owner>
<summary>
Android: Records the time it takes from from Lightweight FRE launch to the
device ownership check completing. This check is often skipped when its
result becomes irrelevant. {MobileFreInflationSpeedComparison}
</summary>
<token key="MobileFreInflationSpeedComparison"
variants="MobileFreInflationSpeedComparison"/>
</histogram>
<histogram name="MobileFre.Lightweight.LoadingDuration" units="ms"
expires_after="2021-07-31">
<owner>skym@chromium.org</owner>
<owner>wenyufu@chromium.org</owner>
<summary>
Android: Records how long it takes waiting from the dialog for Lightweight
FRE created to content fully loaded - either ToS is shown or FRE is skipped.
</summary>
</histogram>
<histogram name="MobileFre.Progress" enum="MobileFreProgress"
expires_after="2021-04-18">
<owner>bsazonov@chromium.org</owner>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment