Commit 7b5ccaad authored by Robbie McElrath's avatar Robbie McElrath Committed by Commit Bot

[WebLayer] Save browser controls UI state between BrowserFragment restarts

This CL makes BrowserImpl save the UI state of BrowserViewController
when destroying/creating it so we can save the controls location. It
doesn't use onStoreInstanceState directly because the
BrowserControlsContainerView lifecycle doesn't exactly match what
Android expects (onStoreInstanceState gets called after BCCV is already
destroyed).

Bug: 1120209
Change-Id: Ibedf4f8ad1e01f29518b404d94961b0e97689b9c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2368479Reviewed-by: default avatarBo <boliu@chromium.org>
Commit-Queue: Robbie McElrath <rmcelrath@chromium.org>
Cr-Commit-Position: refs/heads/master@{#801158}
parent 39af6d5c
...@@ -11,6 +11,8 @@ import android.view.View; ...@@ -11,6 +11,8 @@ import android.view.View;
import android.view.ViewParent; import android.view.ViewParent;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import org.chromium.base.MathUtils; import org.chromium.base.MathUtils;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
...@@ -59,9 +61,25 @@ class BrowserControlsContainerView extends FrameLayout { ...@@ -59,9 +61,25 @@ class BrowserControlsContainerView extends FrameLayout {
private static final long SYSTEM_UI_VIEWPORT_UPDATE_DELAY_MS = 500; private static final long SYSTEM_UI_VIEWPORT_UPDATE_DELAY_MS = 500;
/** Stores the state needed to reconstruct offsets after recreating this class. */
/* package */ static class State {
private final int mControlsOffset;
private final int mContentOffset;
private State(int controlsOffset, int contentOffset) {
mControlsOffset = controlsOffset;
mContentOffset = contentOffset;
}
}
private final Delegate mDelegate; private final Delegate mDelegate;
private final boolean mIsTop; private final boolean mIsTop;
// The state returned by a previous BrowserControlsContainerView instance's getState() method.
// This is saved rather than directly applied because layout needs to occur before we can apply
// the offsets.
private State mSavedState;
private long mNativeBrowserControlsContainerView; private long mNativeBrowserControlsContainerView;
private ViewResourceAdapter mViewResourceAdapter; private ViewResourceAdapter mViewResourceAdapter;
...@@ -132,10 +150,11 @@ class BrowserControlsContainerView extends FrameLayout { ...@@ -132,10 +150,11 @@ class BrowserControlsContainerView extends FrameLayout {
} }
BrowserControlsContainerView(Context context, ContentViewRenderView contentViewRenderView, BrowserControlsContainerView(Context context, ContentViewRenderView contentViewRenderView,
Delegate delegate, boolean isTop) { Delegate delegate, boolean isTop, @Nullable State savedState) {
super(context); super(context);
mDelegate = delegate; mDelegate = delegate;
mIsTop = isTop; mIsTop = isTop;
mSavedState = savedState;
mContentViewRenderView = contentViewRenderView; mContentViewRenderView = contentViewRenderView;
mNativeBrowserControlsContainerView = mNativeBrowserControlsContainerView =
BrowserControlsContainerViewJni.get().createBrowserControlsContainerView( BrowserControlsContainerViewJni.get().createBrowserControlsContainerView(
...@@ -333,9 +352,21 @@ class BrowserControlsContainerView extends FrameLayout { ...@@ -333,9 +352,21 @@ class BrowserControlsContainerView extends FrameLayout {
createAdapterAndLayer(); createAdapterAndLayer();
if (prevHeight == 0) { if (prevHeight == 0) {
assert heightChanged; assert heightChanged;
// If there wasn't a View before (or it had 0 height), move the new View off the // If there wasn't a View before and we have non-empty saved state from a previous
// screen until we know where to position it. // BrowserControlsContainerView instance, apply those saved offsets now. We can't
moveControlsOffScreen(); // rely on BrowserControlsOffsetManager to notify us of the correct location as we
// usually do because it only notifies us when offsets change, but it likely didn't
// get destroyed when the BrowserFragment got recreated, so it won't notify us
// because it thinks we already have the correct offsets.
if (mSavedState != null) {
onOffsetsChanged(mSavedState.mControlsOffset, mSavedState.mContentOffset);
} else {
// If there wasn't a View before (or it had 0 height) and there's no state from
// a previous instance of this class, move the new View off the screen until
// BrowserControlsOffsetManager tells us where to position it.
moveControlsOffScreen();
}
mSavedState = null;
} }
} else if (mViewResourceAdapter != null) { } else if (mViewResourceAdapter != null) {
BrowserControlsContainerViewJni.get().setControlsSize( BrowserControlsContainerViewJni.get().setControlsSize(
...@@ -352,6 +383,10 @@ class BrowserControlsContainerView extends FrameLayout { ...@@ -352,6 +383,10 @@ class BrowserControlsContainerView extends FrameLayout {
cancelDelayedFullscreenRunnable(); cancelDelayedFullscreenRunnable();
} }
/* package */ State getState() {
return new State(mControlsOffset, mContentOffset);
}
private void cancelDelayedFullscreenRunnable() { private void cancelDelayedFullscreenRunnable() {
if (mSystemUiFullscreenResizeRunnable == null) return; if (mSystemUiFullscreenResizeRunnable == null) return;
removeCallbacks(mSystemUiFullscreenResizeRunnable); removeCallbacks(mSystemUiFullscreenResizeRunnable);
......
...@@ -56,6 +56,10 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan ...@@ -56,6 +56,10 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan
private final ProfileImpl mProfile; private final ProfileImpl mProfile;
private Context mEmbedderActivityContext; private Context mEmbedderActivityContext;
private BrowserViewController mViewController; private BrowserViewController mViewController;
// Used to save UI state between destroyAttachmentState() and createAttachmentState() calls so
// it can be preserved during device rotations or other events that cause the Fragment to be
// recreated.
private BrowserViewController.State mViewControllerState;
private FragmentWindowAndroid mWindowAndroid; private FragmentWindowAndroid mWindowAndroid;
private IBrowserClient mClient; private IBrowserClient mClient;
private LocaleChangedBroadcastReceiver mLocaleReceiver; private LocaleChangedBroadcastReceiver mLocaleReceiver;
...@@ -136,7 +140,7 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan ...@@ -136,7 +140,7 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan
assert mEmbedderActivityContext == null; assert mEmbedderActivityContext == null;
mWindowAndroid = windowAndroid; mWindowAndroid = windowAndroid;
mEmbedderActivityContext = embedderAppContext; mEmbedderActivityContext = embedderAppContext;
mViewController = new BrowserViewController(windowAndroid, this); mViewController = new BrowserViewController(windowAndroid, this, mViewControllerState);
mLocaleReceiver = new LocaleChangedBroadcastReceiver(windowAndroid.getContext().get()); mLocaleReceiver = new LocaleChangedBroadcastReceiver(windowAndroid.getContext().get());
mPasswordEchoEnabled = null; mPasswordEchoEnabled = null;
} }
...@@ -533,6 +537,7 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan ...@@ -533,6 +537,7 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan
mLocaleReceiver = null; mLocaleReceiver = null;
} }
if (mViewController != null) { if (mViewController != null) {
mViewControllerState = mViewController.getState();
mViewController.destroy(); mViewController.destroy();
mViewController = null; mViewController = null;
mViewAttachedToWindow = false; mViewAttachedToWindow = false;
......
...@@ -15,6 +15,8 @@ import android.webkit.ValueCallback; ...@@ -15,6 +15,8 @@ import android.webkit.ValueCallback;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import androidx.annotation.Nullable;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
import org.chromium.components.browser_ui.modaldialog.AppModalPresenter; import org.chromium.components.browser_ui.modaldialog.AppModalPresenter;
import org.chromium.components.browser_ui.widget.InsetObserverView; import org.chromium.components.browser_ui.widget.InsetObserverView;
...@@ -36,6 +38,18 @@ public final class BrowserViewController ...@@ -36,6 +38,18 @@ public final class BrowserViewController
implements BrowserControlsContainerView.Delegate, implements BrowserControlsContainerView.Delegate,
WebContentsGestureStateTracker.OnGestureStateChangedListener, WebContentsGestureStateTracker.OnGestureStateChangedListener,
ModalDialogManager.ModalDialogManagerObserver { ModalDialogManager.ModalDialogManagerObserver {
/** Information needed to restore the UI state after recreating the BrowserViewController. */
/* package */ static class State {
private BrowserControlsContainerView.State mTopControlsState;
private BrowserControlsContainerView.State mBottomControlsState;
private State(BrowserControlsContainerView.State topControlsState,
BrowserControlsContainerView.State bottomControlsState) {
mTopControlsState = topControlsState;
mBottomControlsState = bottomControlsState;
}
}
private final ContentViewRenderView mContentViewRenderView; private final ContentViewRenderView mContentViewRenderView;
// Child of mContentViewRenderView. Be very careful adding Views to this, as any Views are not // Child of mContentViewRenderView. Be very careful adding Views to this, as any Views are not
// accessible (ContentView provides it's own accessible implementation that interacts with // accessible (ContentView provides it's own accessible implementation that interacts with
...@@ -72,8 +86,8 @@ public final class BrowserViewController ...@@ -72,8 +86,8 @@ public final class BrowserViewController
*/ */
private boolean mCachedDoBrowserControlsShrinkRendererSize; private boolean mCachedDoBrowserControlsShrinkRendererSize;
public BrowserViewController( public BrowserViewController(FragmentWindowAndroid windowAndroid,
FragmentWindowAndroid windowAndroid, View.OnAttachStateChangeListener listener) { View.OnAttachStateChangeListener listener, @Nullable State savedState) {
mWindowAndroid = windowAndroid; mWindowAndroid = windowAndroid;
mOnAttachedStateChangeListener = listener; mOnAttachedStateChangeListener = listener;
Context context = mWindowAndroid.getContext().get(); Context context = mWindowAndroid.getContext().get();
...@@ -83,10 +97,12 @@ public final class BrowserViewController ...@@ -83,10 +97,12 @@ public final class BrowserViewController
mContentViewRenderView.onNativeLibraryLoaded( mContentViewRenderView.onNativeLibraryLoaded(
mWindowAndroid, ContentViewRenderView.MODE_SURFACE_VIEW); mWindowAndroid, ContentViewRenderView.MODE_SURFACE_VIEW);
mTopControlsContainerView = mTopControlsContainerView =
new BrowserControlsContainerView(context, mContentViewRenderView, this, true); new BrowserControlsContainerView(context, mContentViewRenderView, this, true,
(savedState == null) ? null : savedState.mTopControlsState);
mTopControlsContainerView.setId(View.generateViewId()); mTopControlsContainerView.setId(View.generateViewId());
mBottomControlsContainerView = mBottomControlsContainerView =
new BrowserControlsContainerView(context, mContentViewRenderView, this, false); new BrowserControlsContainerView(context, mContentViewRenderView, this, false,
(savedState == null) ? null : savedState.mBottomControlsState);
mBottomControlsContainerView.setId(View.generateViewId()); mBottomControlsContainerView.setId(View.generateViewId());
mContentView = ContentView.createContentView( mContentView = ContentView.createContentView(
context, mTopControlsContainerView.getEventOffsetHandler(), null /* webContents */); context, mTopControlsContainerView.getEventOffsetHandler(), null /* webContents */);
...@@ -275,6 +291,11 @@ public final class BrowserViewController ...@@ -275,6 +291,11 @@ public final class BrowserViewController
onDialogVisibilityChanged(false); onDialogVisibilityChanged(false);
} }
/* package */ State getState() {
return new State(
mTopControlsContainerView.getState(), mBottomControlsContainerView.getState());
}
private void onDialogVisibilityChanged(boolean showing) { private void onDialogVisibilityChanged(boolean showing) {
if (WebLayerFactoryImpl.getClientMajorVersion() < 82) return; if (WebLayerFactoryImpl.getClientMajorVersion() < 82) return;
......
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