Commit 9c6253e4 authored by Dmitry Skiba's avatar Dmitry Skiba Committed by Commit Bot

Fix UI glitches during FRE transition on tablets.

On tablets where first run experience activity shows in a dialog, there
is a UI glitch (flash of white) when ChromeTabbedActivity (or any similar
fillscreen activity) transitions to FRE.

CTA transitions to FRE in onCreate(), aborting CTA launch and canceling
currently running window animation midway. By the time onCreate() is
called, the CTA window is already partially visible, so its removal is
perceived as a flash.

This CL introduces TabbedModeFRE activity, which has the same background
as CTA, but renders its content in a FRE-like dialog on tablets. Because
the background is the same, transition from CTA window looks smooth, and
since content is laid out as a dialog, the end experience looks like FRE
dialog appearing on top of starting CTA activity.


Bug: 771545
Change-Id: I4709a83de147e04ce94ba319cfa820a2ec6dea17
Reviewed-on: https://chromium-review.googlesource.com/723725
Commit-Queue: Dmitry Skiba <dskiba@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#512893}
parent 5ee49b9b
......@@ -451,19 +451,21 @@ by a child template that "extends" this file.
</activity>
<activity android:name="org.chromium.chrome.browser.firstrun.LightweightFirstRunActivity"
android:theme="@style/SimpleDialog"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:autoRemoveFromRecents="true"
android:windowSoftInputMode="stateHidden|adjustPan"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize">
{{ self.first_run_activity_common() }}>
</activity>
<activity android:name="org.chromium.chrome.browser.firstrun.FirstRunActivity"
android:theme="@style/DialogWhenLarge"
android:theme="@style/FirstRunTheme"
{% block first_run_activity_common %}
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:autoRemoveFromRecents="true"
android:windowSoftInputMode="stateHidden|adjustPan"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize">
{% endblock %}
</activity>
<activity android:name="org.chromium.chrome.browser.firstrun.TabbedModeFirstRunActivity"
android:theme="@style/TabbedModeFirstRunTheme"
{{ self.first_run_activity_common() }}>
</activity>
{% if enable_vr == "true" %}
<activity android:name="org.chromium.chrome.browser.vr_shell.VrFirstRunActivity"
......
......@@ -5,9 +5,14 @@
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/black" />
</shape>
</item>
<item
android:top="25dp"
android:bottom="0px">
<nine-patch android:src="@drawable/toolbar_background" />
</item>
</layer-list>
\ No newline at end of file
<nine-patch android:src="@drawable/toolbar_background" />
</item>
</layer-list>
......@@ -6,9 +6,14 @@
<!-- Tablets from api 19 and up have the top notification bar -->
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/black" />
</shape>
</item>
<item
android:top="25dp"
android:bottom="0px">
<nine-patch android:src="@drawable/toolbar_background" />
</item>
</layer-list>
\ No newline at end of file
</layer-list>
......@@ -9,4 +9,4 @@
<item>
<nine-patch android:src="@drawable/toolbar_background" />
</item>
</layer-list>
\ No newline at end of file
</layer-list>
......@@ -5,4 +5,11 @@
<resources>
<dimen name="fre_content_margin">60dp</dimen>
<!-- Copy of corresponding abc_* values from AppCompat. Used by
TabbedModeFirstRunActivity to simulate DialogWhenLarge layout. -->
<item type="dimen" name="dialog_fixed_width_major">60%</item>
<item type="dimen" name="dialog_fixed_width_minor">90%</item>
<item type="dimen" name="dialog_fixed_height_major">60%</item>
<item type="dimen" name="dialog_fixed_height_minor">90%</item>
</resources>
......@@ -51,6 +51,12 @@
<item name="android:windowBackground">@android:color/white</item>
</style>
<style name="FirstRunTheme" parent="DialogWhenLarge">
</style>
<style name="TabbedModeFirstRunTheme" parent="TabbedModeTheme">
</style>
<!-- The theme to use when starting Chrome in VR mode.-->
<style name="VrSupportTheme" parent="MainTheme">
<!-- Android shows a preview window to give the user immediate feedback that the
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<resources>
<!-- Copy of corresponding abc_* values from AppCompat. Used by
TabbedModeFirstRunActivity to simulate DialogWhenLarge layout. -->
<item type="dimen" name="dialog_fixed_width_major">50%</item>
<item type="dimen" name="dialog_fixed_width_minor">70%</item>
<item type="dimen" name="dialog_fixed_height_major">60%</item>
<item type="dimen" name="dialog_fixed_height_minor">90%</item>
</resources>
......@@ -7,8 +7,10 @@ package org.chromium.chrome.browser.firstrun;
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.StringRes;
import android.text.TextUtils;
import android.view.View;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
......@@ -179,16 +181,26 @@ public class FirstRunActivity extends FirstRunActivityBase implements FirstRunPa
return null;
}
/**
* Creates the content view for this activity.
* The only thing subclasses can do is wrapping the view returned by super implementation
* in some extra layout.
*/
@CallSuper
protected View createContentView() {
mPager = new FirstRunViewPager(this);
mPager.setId(R.id.fre_pager);
mPager.setOffscreenPageLimit(3);
return mPager;
}
@Override
public void setContentView() {
initializeStateFromLaunchData();
setFinishOnTouchOutside(true);
mPager = new FirstRunViewPager(this);
mPager.setId(R.id.fre_pager);
mPager.setOffscreenPageLimit(3);
setContentView(mPager);
setContentView(createContentView());
mFirstRunFlowSequencer = new FirstRunFlowSequencer(this) {
@Override
......
......@@ -9,6 +9,7 @@ import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.text.TextUtils;
......@@ -18,6 +19,7 @@ import org.chromium.base.ApplicationStatus;
import org.chromium.base.CommandLine;
import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.locale.LocaleManager;
import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
......@@ -366,6 +368,14 @@ public abstract class FirstRunFlowSequencer {
freCallerIntent = new Intent(intent);
VrIntentUtils.updateFreCallerIntent(caller, intent);
}
if (maybeSwitchToTabbedMode(caller, freIntent)) {
// We switched to TabbedModeFRE. We need to disable animation on the original
// intent, to make transition seamless.
intent = new Intent(intent);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
}
// Add a PendingIntent so that the intent used to launch Chrome will be resent when
// First Run is completed or canceled.
addPendingIntent(caller, freIntent, intent, requiresBroadcast);
......@@ -384,4 +394,39 @@ public abstract class FirstRunFlowSequencer {
}
return true;
}
/**
* On tablets, where FRE activity is a dialog, transitions from fillscreen activities
* (the ones that use TabbedModeTheme, e.g. ChromeTabbedActivity) look ugly, because
* when FRE is started from CTA.onCreate(), currently running animation for CTA window
* is aborted. This is perceived as a flash of white and doesn't look good.
*
* To solve this, we added TabbedMode FRE activity, which has the same window background
* as TabbedModeTheme activities, but shows content in a FRE-like dialog.
*
* This function attempts to switch FRE to TabbedModeFRE if certain conditions are met.
*/
private static boolean maybeSwitchToTabbedMode(Context caller, Intent freIntent) {
// Caller must be an activity.
if (!(caller instanceof Activity)) return false;
// We must be on a tablet (where FRE is a dialog).
boolean isTablet = caller.getResources().getBoolean(R.bool.is_tablet);
if (!isTablet) return false;
// Caller must use a theme with @drawable/window_background (the same background
// used by TabbedModeFRE).
TypedArray a = caller.obtainStyledAttributes(new int[] {android.R.attr.windowBackground});
int backgroundResourceId = a.getResourceId(0 /* index */, 0);
a.recycle();
if (backgroundResourceId != R.drawable.window_background) return false;
// Switch FRE -> TabbedModeFRE.
if (FirstRunActivity.class.getName().equals(freIntent.getComponent().getClassName())) {
freIntent.setClass(caller, TabbedModeFirstRunActivity.class);
return true;
}
return false;
}
}
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.firstrun;
import android.content.Context;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.chromium.chrome.R;
/**
* FirstRunActivity variant that fills the whole screen, but displays the content
* in a dialog-like layout.
*/
public class TabbedModeFirstRunActivity extends FirstRunActivity {
@Override
protected View createContentView() {
return wrapInDialogLayout(super.createContentView());
}
/**
* Wraps contentView into layout that resembles DialogWhenLarge. The layout centers
* the content and dims the background to simulate a modal dialog.
*/
private View wrapInDialogLayout(View contentView) {
ContentLayout contentLayout = new ContentLayout(this);
contentLayout.addView(contentView);
contentLayout.setBackgroundResource(R.drawable.bg_white_dialog);
// We need an outer layout for two things:
// * centering the content
// * dimming the background
FrameLayout outerLayout = new FrameLayout(this);
outerLayout.addView(contentLayout,
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
outerLayout.setBackgroundResource(R.color.modal_dialog_scrim_color);
return outerLayout;
}
/**
* Layout that sizes itself according to DialogWhenLarge constraints.
*/
private static class ContentLayout extends FrameLayout {
private TypedValue mFixedWidthMajor = new TypedValue();
private TypedValue mFixedWidthMinor = new TypedValue();
private TypedValue mFixedHeightMajor = new TypedValue();
private TypedValue mFixedHeightMinor = new TypedValue();
public ContentLayout(Context context) {
super(context);
fetchConstraints();
}
private void fetchConstraints() {
// Fetch size constraints. These are copies of corresponding abc_* AppCompat values,
// because abc_* values are private, and even though corresponding theme attributes
// are public in AppCompat (e.g. windowFixedWidthMinor), there is no guarantee that
// AppCompat.DialogWhenLarge is actually defined by AppCompat and not based on
// system DialogWhenLarge theme.
// Note that we don't care about the return values, because onMeasure() handles null
// constraints (and they will be null when the device is not considered "large").
getContext().getResources().getValue(
R.dimen.dialog_fixed_width_minor, mFixedWidthMinor, true);
getContext().getResources().getValue(
R.dimen.dialog_fixed_width_major, mFixedWidthMajor, true);
getContext().getResources().getValue(
R.dimen.dialog_fixed_height_minor, mFixedHeightMinor, true);
getContext().getResources().getValue(
R.dimen.dialog_fixed_height_major, mFixedHeightMajor, true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
// Constraint handling is adapted from
// sdk/sources/android-25/android/support/v7/widget/ContentFrameLayout.java.
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
assert widthMode == MeasureSpec.AT_MOST;
{
final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor;
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (tvw.type != TypedValue.TYPE_NULL) {
assert tvw.type == TypedValue.TYPE_FRACTION;
int width = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
widthSize = Math.min(width, widthSize);
}
// This either sets the width calculated from the constraints, or simply
// takes all of the available space (as if MATCH_PARENT was specified).
// The behavior is similar to how DialogWhenLarge windows are sized - they
// are either sized by the constraints, or are full screen, but are never
// sized based on the content size.
widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
}
// This is similar to the above block that calculates width.
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
assert heightMode == MeasureSpec.AT_MOST;
{
final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor;
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (tvh.type != TypedValue.TYPE_NULL) {
assert tvh.type == TypedValue.TYPE_FRACTION;
int height = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
heightSize = Math.min(height, heightSize);
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
......@@ -428,6 +428,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/firstrun/FirstRunView.java",
"java/src/org/chromium/chrome/browser/firstrun/FirstRunViewPager.java",
"java/src/org/chromium/chrome/browser/firstrun/ForcedSigninProcessor.java",
"java/src/org/chromium/chrome/browser/firstrun/TabbedModeFirstRunActivity.java",
"java/src/org/chromium/chrome/browser/firstrun/ToSAckedReceiver.java",
"java/src/org/chromium/chrome/browser/firstrun/ToSAndUMAFirstRunFragment.java",
"java/src/org/chromium/chrome/browser/fullscreen/BrowserStateBrowserControlsVisibilityDelegate.java",
......
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