Commit 67acc8fb authored by PKH's avatar PKH Committed by Commit Bot

Merge Webapp & TWA splash controllers 1/X

This CL moves logic which can be shared with TWAs from SameActivityWebappSplashDelegate
to SplashController. The following logic is moved:
- tracing
- hide animation code

BUG=817263

Change-Id: I21fce0d2eae964d9499f72a89e3d8e7d3d4ab6d3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1592829Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Commit-Queue: Peter Kotwicz <pkotwicz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#656395}
parent f1a607d9
......@@ -8,7 +8,6 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import org.chromium.base.ApiCompatibilityUtils;
......@@ -22,19 +21,14 @@ import org.chromium.webapk.lib.common.WebApkCommonUtils;
/** Delegate which uses splash screen screenshot from the WebAPK's content provider. */
public class ProvidedByWebApkSplashDelegate implements SplashDelegate {
private ViewGroup mParentView;
private ImageView mSplashView;
@Override
public void showSplash(ViewGroup parentView, WebappInfo webappInfo) {
mParentView = parentView;
public View buildSplashView(WebappInfo webappInfo) {
Context appContext = ContextUtils.getApplicationContext();
mSplashView = new ImageView(appContext);
ImageView splashView = new ImageView(appContext);
int backgroundColor =
ColorUtils.getOpaqueColor(webappInfo.backgroundColor(ApiCompatibilityUtils.getColor(
appContext.getResources(), R.color.webapp_default_bg)));
mSplashView.setBackgroundColor(backgroundColor);
splashView.setBackgroundColor(backgroundColor);
Bitmap splashBitmap = null;
try (StrictModeContext smc = StrictModeContext.allowDiskReads()) {
......@@ -43,28 +37,18 @@ public class ProvidedByWebApkSplashDelegate implements SplashDelegate {
webappInfo.webApkPackageName())));
}
if (splashBitmap != null) {
mSplashView.setScaleType(ImageView.ScaleType.FIT_CENTER);
mSplashView.setImageBitmap(splashBitmap);
splashView.setScaleType(ImageView.ScaleType.FIT_CENTER);
splashView.setImageBitmap(splashBitmap);
}
parentView.addView(mSplashView);
return splashView;
}
@Override
public void hideSplash(Tab tab, Runnable finishedHidingCallback) {
public void onSplashHidden(Tab tab) {
// TODO(pkotwicz) implement.
}
@Override
public boolean isSplashVisible() {
return true;
}
@Override
public View getSplashViewIfChildOf(ViewGroup parent) {
return (mParentView == parent) ? mSplashView : null;
}
@Override
public boolean shouldWaitForSubsequentPageLoadToHideSplash() {
return false;
......
......@@ -11,12 +11,10 @@ import android.graphics.Bitmap;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.TraceEvent;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
......@@ -35,14 +33,6 @@ public class SameActivityWebappSplashDelegate implements SplashDelegate, NativeI
private ActivityLifecycleDispatcher mLifecycleDispatcher;
private TabObserverRegistrar mTabObserverRegistrar;
/** View to which the splash screen is added. */
private ViewGroup mParentView;
private ViewGroup mSplashScreen;
/** Whether the splash screen is visible and not in the process of hiding. */
private boolean mIsSplashVisible;
/** Whether native was loaded. Native must be loaded in order to record metrics. */
private boolean mNativeLoaded;
......@@ -52,31 +42,6 @@ public class SameActivityWebappSplashDelegate implements SplashDelegate, NativeI
private WebApkSplashNetworkErrorObserver mWebApkNetworkErrorObserver;
private static class SingleShotOnDrawListener implements ViewTreeObserver.OnDrawListener {
private final View mView;
private final Runnable mAction;
private boolean mHasRun;
public static void install(View view, Runnable action) {
SingleShotOnDrawListener listener = new SingleShotOnDrawListener(view, action);
view.getViewTreeObserver().addOnDrawListener(listener);
}
private SingleShotOnDrawListener(View view, Runnable action) {
mView = view;
mAction = action;
}
@Override
public void onDraw() {
if (mHasRun) return;
mHasRun = true;
mAction.run();
// Cannot call removeOnDrawListener within OnDraw, so do on next tick.
mView.post(() -> mView.getViewTreeObserver().removeOnDrawListener(this));
}
};
public SameActivityWebappSplashDelegate(Activity activity,
ActivityLifecycleDispatcher lifecycleDispatcher,
TabObserverRegistrar tabObserverRegistrar) {
......@@ -88,10 +53,8 @@ public class SameActivityWebappSplashDelegate implements SplashDelegate, NativeI
}
@Override
public void showSplash(ViewGroup parentView, WebappInfo webappInfo) {
mParentView = parentView;
public View buildSplashView(WebappInfo webappInfo) {
mWebappInfo = webappInfo;
mIsSplashVisible = true;
if (mWebappInfo.isForWebApk()) {
mWebApkNetworkErrorObserver =
......@@ -103,29 +66,29 @@ public class SameActivityWebappSplashDelegate implements SplashDelegate, NativeI
final int backgroundColor = ColorUtils.getOpaqueColor(webappInfo.backgroundColor(
ApiCompatibilityUtils.getColor(context.getResources(), R.color.webapp_default_bg)));
mSplashScreen = new FrameLayout(context);
mSplashScreen.setBackgroundColor(backgroundColor);
mParentView.addView(mSplashScreen);
recordTraceEventsShowedSplash();
ViewGroup splashScreen = new FrameLayout(context);
splashScreen.setBackgroundColor(backgroundColor);
if (webappInfo.isForWebApk()) {
initializeLayout(webappInfo, backgroundColor, ((WebApkInfo) webappInfo).splashIcon());
return;
initializeLayout(webappInfo, splashScreen, backgroundColor,
((WebApkInfo) webappInfo).splashIcon());
return splashScreen;
}
WebappDataStorage storage =
WebappRegistry.getInstance().getWebappDataStorage(webappInfo.id());
if (storage == null) {
initializeLayout(webappInfo, backgroundColor, null);
return;
initializeLayout(webappInfo, splashScreen, backgroundColor, null);
return splashScreen;
}
storage.getSplashScreenImage(new WebappDataStorage.FetchCallback<Bitmap>() {
@Override
public void onDataRetrieved(Bitmap splashImage) {
initializeLayout(webappInfo, backgroundColor, splashImage);
initializeLayout(webappInfo, splashScreen, backgroundColor, splashImage);
}
});
return splashScreen;
}
@Override
......@@ -135,38 +98,15 @@ public class SameActivityWebappSplashDelegate implements SplashDelegate, NativeI
}
@Override
public void hideSplash(Tab tab, final Runnable finishedHidingCallback) {
assert mIsSplashVisible;
mIsSplashVisible = false;
recordTraceEventsStartedHidingSplash();
mSplashScreen.animate().alpha(0f).withEndAction(new Runnable() {
@Override
public void run() {
mParentView.removeView(mSplashScreen);
if (mWebApkNetworkErrorObserver != null) {
mTabObserverRegistrar.unregisterTabObserver(mWebApkNetworkErrorObserver);
tab.removeObserver(mWebApkNetworkErrorObserver);
mWebApkNetworkErrorObserver = null;
}
mLifecycleDispatcher.unregister(SameActivityWebappSplashDelegate.this);
recordTraceEventsFinishedHidingSplash();
mActivity = null;
mSplashScreen = null;
finishedHidingCallback.run();
}
});
}
@Override
public boolean isSplashVisible() {
return mIsSplashVisible;
}
public void onSplashHidden(Tab tab) {
if (mWebApkNetworkErrorObserver != null) {
mTabObserverRegistrar.unregisterTabObserver(mWebApkNetworkErrorObserver);
tab.removeObserver(mWebApkNetworkErrorObserver);
mWebApkNetworkErrorObserver = null;
}
mLifecycleDispatcher.unregister(SameActivityWebappSplashDelegate.this);
@Override
public View getSplashViewIfChildOf(ViewGroup parent) {
return (mParentView == parent) ? mSplashScreen : null;
mActivity = null;
}
@Override
......@@ -176,7 +116,8 @@ public class SameActivityWebappSplashDelegate implements SplashDelegate, NativeI
}
/** Sets the splash screen layout and sets the splash screen's title and icon. */
private void initializeLayout(WebappInfo webappInfo, int backgroundColor, Bitmap splashImage) {
private void initializeLayout(WebappInfo webappInfo, ViewGroup splashScreen,
int backgroundColor, Bitmap splashImage) {
Context context = ContextUtils.getApplicationContext();
Resources resources = context.getResources();
......@@ -192,7 +133,7 @@ public class SameActivityWebappSplashDelegate implements SplashDelegate, NativeI
int selectedIconClassification = SplashLayout.classifyIcon(
context.getResources(), selectedIcon, selectedIconGenerated);
SplashLayout.createLayout(context, mSplashScreen, selectedIcon, selectedIconAdaptive,
SplashLayout.createLayout(context, splashScreen, selectedIcon, selectedIconAdaptive,
selectedIconClassification, webappInfo.name(),
ColorUtils.shouldUseLightForegroundOnBackground(backgroundColor));
......@@ -229,19 +170,4 @@ public class SameActivityWebappSplashDelegate implements SplashDelegate, NativeI
if (mNativeLoaded) mUmaCache.commitMetrics();
}
private void recordTraceEventsShowedSplash() {
SingleShotOnDrawListener.install(mParentView,
() -> { TraceEvent.startAsync("WebappSplashScreen.visible", hashCode()); });
}
private void recordTraceEventsStartedHidingSplash() {
TraceEvent.startAsync("WebappSplashScreen.hidingAnimation", hashCode());
}
private void recordTraceEventsFinishedHidingSplash() {
TraceEvent.finishAsync("WebappSplashScreen.hidingAnimation", hashCode());
SingleShotOnDrawListener.install(mParentView,
() -> { TraceEvent.finishAsync("WebappSplashScreen.visible", hashCode()); });
}
}
......@@ -8,8 +8,10 @@ import android.os.SystemClock;
import android.support.annotation.IntDef;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import org.chromium.base.ObserverList;
import org.chromium.base.TraceEvent;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.WarmupManager;
......@@ -23,6 +25,31 @@ import java.lang.annotation.RetentionPolicy;
/** Shows and hides splash screen. */
public class SplashController extends EmptyTabObserver {
private static class SingleShotOnDrawListener implements ViewTreeObserver.OnDrawListener {
private final View mView;
private final Runnable mAction;
private boolean mHasRun;
public static void install(View view, Runnable action) {
view.getViewTreeObserver().addOnDrawListener(
new SingleShotOnDrawListener(view, action));
}
private SingleShotOnDrawListener(View view, Runnable action) {
mView = view;
mAction = action;
}
@Override
public void onDraw() {
if (mHasRun) return;
mHasRun = true;
mAction.run();
// Cannot call removeOnDrawListener within OnDraw, so do on next tick.
mView.post(() -> mView.getViewTreeObserver().removeOnDrawListener(this));
}
};
// SplashHidesReason defined in tools/metrics/histograms/enums.xml.
@IntDef({SplashHidesReason.PAINT, SplashHidesReason.LOAD_FINISHED,
SplashHidesReason.LOAD_FAILED, SplashHidesReason.CRASH})
......@@ -45,6 +72,11 @@ public class SplashController extends EmptyTabObserver {
/** View to which the splash screen is added. */
private ViewGroup mParentView;
private View mSplashView;
/** Whether the splash hide animation was started. */
private boolean mWasSplashHideAnimationStarted;
/** Time that the splash screen was shown. */
private long mSplashShownTimestamp;
......@@ -63,7 +95,8 @@ public class SplashController extends EmptyTabObserver {
mParentView = parentView;
mSplashShownTimestamp = SystemClock.elapsedRealtime();
mDelegate.showSplash(parentView, webappInfo);
mSplashView = mDelegate.buildSplashView(webappInfo);
mParentView.addView(mSplashView);
notifySplashscreenVisible(mSplashShownTimestamp);
}
......@@ -73,17 +106,15 @@ public class SplashController extends EmptyTabObserver {
* splashscreen on top.
*/
public void setViewHierarchyBelowSplashscreen(ViewGroup viewHierarchy) {
View splashView = mDelegate.getSplashViewIfChildOf(mParentView);
WarmupManager.transferViewHeirarchy(viewHierarchy, mParentView);
if (splashView != null) {
mParentView.bringChildToFront(splashView);
if (mSplashView != null) {
mParentView.bringChildToFront(mSplashView);
}
}
@VisibleForTesting
View getSplashScreenForTests() {
if (mDelegate == null) return null;
return mDelegate.getSplashViewIfChildOf(mParentView);
return mSplashView;
}
@Override
......@@ -117,24 +148,9 @@ public class SplashController extends EmptyTabObserver {
}
/** Hides the splash screen. */
private void hideSplash(Tab tab, final @SplashHidesReason int reason) {
if (!mDelegate.isSplashVisible()) return;
final Runnable onHiddenCallback = new Runnable() {
@Override
public void run() {
mTabObserverRegistrar.unregisterTabObserver(SplashController.this);
tab.removeObserver(SplashController.this);
mDelegate = null;
long splashHiddenTimestamp = SystemClock.elapsedRealtime();
notifySplashscreenHidden(splashHiddenTimestamp);
recordSplashHiddenUma(reason, splashHiddenTimestamp);
}
};
private void hideSplash(final Tab tab, final @SplashHidesReason int reason) {
if (reason == SplashHidesReason.LOAD_FAILED || reason == SplashHidesReason.CRASH) {
mDelegate.hideSplash(tab, onHiddenCallback);
animateHideSplash(tab, reason);
return;
}
// Delay hiding the splash screen till the compositor has finished drawing the next frame.
......@@ -142,10 +158,33 @@ public class SplashController extends EmptyTabObserver {
// the web content (crbug.com/734500).
CompositorView compositorView =
tab.getActivity().getCompositorViewHolder().getCompositorView();
compositorView.surfaceRedrawNeededAsync(() -> {
if (mDelegate == null || !mDelegate.isSplashVisible()) return;
mDelegate.hideSplash(tab, onHiddenCallback);
});
compositorView.surfaceRedrawNeededAsync(() -> { animateHideSplash(tab, reason); });
}
private void animateHideSplash(final Tab tab, final @SplashHidesReason int reason) {
if (mWasSplashHideAnimationStarted) return;
mWasSplashHideAnimationStarted = true;
mTabObserverRegistrar.unregisterTabObserver(this);
tab.removeObserver(this);
recordTraceEventsStartedHidingSplash();
mSplashView.animate().alpha(0f).withEndAction(() -> { hideSplashNow(tab, reason); });
}
private void hideSplashNow(Tab tab, @SplashHidesReason int reason) {
mParentView.removeView(mSplashView);
long splashHiddenTimestamp = SystemClock.elapsedRealtime();
recordTraceEventsFinishedHidingSplash();
mDelegate.onSplashHidden(tab);
recordSplashHiddenUma(reason, splashHiddenTimestamp);
notifySplashscreenHidden(splashHiddenTimestamp);
mDelegate = null;
mSplashView = null;
}
/** Called once the splash screen is hidden to record UMA metrics. */
......@@ -184,4 +223,19 @@ public class SplashController extends EmptyTabObserver {
}
mObservers.clear();
}
private void recordTraceEventsShowedSplash() {
SingleShotOnDrawListener.install(
mParentView, () -> { TraceEvent.startAsync("SplashScreen.visible", hashCode()); });
}
private void recordTraceEventsStartedHidingSplash() {
TraceEvent.startAsync("SplashScreen.hidingAnimation", hashCode());
}
private void recordTraceEventsFinishedHidingSplash() {
TraceEvent.finishAsync("SplashScreen.hidingAnimation", hashCode());
SingleShotOnDrawListener.install(mParentView,
() -> { TraceEvent.finishAsync("WebappSplashScreen.visible", hashCode()); });
}
}
......@@ -5,26 +5,16 @@
package org.chromium.chrome.browser.webapps;
import android.view.View;
import android.view.ViewGroup;
import org.chromium.chrome.browser.tab.Tab;
/** Delegate for {@link SplashController}. */
interface SplashDelegate {
/** Shows the splash screen. */
void showSplash(ViewGroup parentView, WebappInfo webappInfo);
/** Builds the splash view. */
View buildSplashView(WebappInfo webappInfo);
/** Hides the splash screen. Runs the callback once the splash screen is hidden. */
void hideSplash(Tab tab, Runnable finishedHidingCallback);
/** Returns whether the splash screen is visible and not in the process of hiding. */
boolean isSplashVisible();
/**
* Returns the {@link View} containing the splash screen if it is a direct child of
* the passed-in view.
*/
View getSplashViewIfChildOf(ViewGroup parent);
/** Called when splash screen has been hidden. */
void onSplashHidden(Tab tab);
/** Returns whether to wait for a subsequent page load to hide the splash screen. */
boolean shouldWaitForSubsequentPageLoadToHideSplash();
......
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