Commit 61c7526b authored by jdduke's avatar jdduke Committed by Commit bot

Reland "[Android] Preserve the front buffer when the activity is paused"

This change was reverted in r322170 due to WebView breakage. The
ApplicationStatus dependency has been made optional, allowing
WebView to opt-out of its use. It was speculatively reverted
again in r327092, but that turned out to be a false alarm.

Original description: ----------------------------

Currently, when an activity is stopped, we explicitly hide the
foreground Tab. This is problematic, as current hiding semantics
might clear the visual front buffer before the window is hidden.
This in turn causes an unpleasant flickering during activity
transitions, e.g., when backgrounding Chrome or locking the screen.

Wire Activity onPause/onResume notifications to WindowAndroidObservers,
allowing the foreground tab to preserve its front buffer while hiding
its web content. If the tab is explicitly hidden, or the root window
is lost, the front buffer will be cleared as usual.

BUG=481450,434401

Review URL: https://codereview.chromium.org/1001573003

Cr-Commit-Position: refs/heads/master@{#329283}
parent 7fd7d99a
......@@ -871,9 +871,11 @@ public class AwContents implements SmartClipProvider,
WebContents webContents = nativeGetWebContents(mNativeAwContents);
// WebView does not currently initialize ApplicationStatus, crbug.com/470582.
final boolean listenToActivityState = false;
Activity activity = ContentViewCore.activityFromContext(mContext);
mWindowAndroid = activity != null
? new ActivityWindowAndroid(activity)
? new ActivityWindowAndroid(activity, listenToActivityState)
: new WindowAndroid(mContext.getApplicationContext());
mContentViewCore = createAndInitializeContentViewCore(
mContainerView, mContext, mInternalAccessAdapter, webContents,
......
......@@ -1211,7 +1211,13 @@ public class Tab implements ViewGroup.OnHierarchyChangeListener,
* on both cold and warm starts.
*/
public void onActivityStart() {
show(TabSelectionType.FROM_USER);
if (isHidden()) {
show(TabSelectionType.FROM_USER);
} else {
// The visible Tab's renderer process may have died after the activity was paused.
// Ensure that it's restored appropriately.
loadIfNeeded();
}
// When resuming the activity, force an update to the fullscreen state to ensure a
// subactivity did not change the fullscreen configuration of this ChromeTab's renderer in
......@@ -1223,7 +1229,7 @@ public class Tab implements ViewGroup.OnHierarchyChangeListener,
* Called on the foreground tab when the Activity is stopped.
*/
public void onActivityStop() {
hide();
// TODO(jdduke): Remove this method when all downstream callers have been removed.
}
/**
......
......@@ -6,7 +6,9 @@ package org.chromium.chrome.browser;
import android.test.suitebuilder.annotation.SmallTest;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType;
import org.chromium.chrome.shell.ChromeShellTestBase;
import org.chromium.content.browser.test.util.CallbackHelper;
......@@ -48,4 +50,51 @@ public class TabTest extends ChromeShellTestBase {
mOnTitleUpdatedHelper.waitForCallback(currentCallCount);
assertEquals("title does not update", newTitle, mTab.getTitle());
}
@SmallTest
@Feature({"Tab"})
public void testTabRestoredIfKilledWhileActivityStopped() {
launchChromeShellWithBlankPage();
final Tab tab = getActivity().getActiveTab();
// Ensure the tab is showing before stopping the activity.
ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@Override
public void run() {
tab.show(TabSelectionType.FROM_NEW);
}
});
assertFalse(tab.needsReload());
assertFalse(tab.isHidden());
assertFalse(tab.isShowingSadTab());
// Stop the activity and simulate a killed renderer.
ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@Override
public void run() {
getInstrumentation().callActivityOnPause(getActivity());
getInstrumentation().callActivityOnStop(getActivity());
tab.simulateRendererKilledForTesting(false);
}
});
assertTrue(tab.needsReload());
assertFalse(tab.isHidden());
assertFalse(tab.isShowingSadTab());
// Resume the activity.
ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@Override
public void run() {
getInstrumentation().callActivityOnStart(getActivity());
getInstrumentation().callActivityOnResume(getActivity());
}
});
// The tab should be restored and visible.
assertFalse(tab.isHidden());
assertFalse(tab.needsReload());
assertFalse(tab.isShowingSadTab());
}
}
......@@ -83,7 +83,8 @@ public class ChromeShellActivity extends AppCompatActivity implements AppMenuPro
new ActivityWindowAndroidFactory() {
@Override
public ActivityWindowAndroid getActivityWindowAndroid(Activity activity) {
return new ActivityWindowAndroid(activity);
final boolean listenToActivityState = true;
return new ActivityWindowAndroid(activity, listenToActivityState);
}
};
......@@ -278,9 +279,6 @@ public class ChromeShellActivity extends AppCompatActivity implements AppMenuPro
super.onStop();
if (mToolbar != null) mToolbar.hideSuggestions();
Tab activeTab = getActiveTab();
if (activeTab != null) activeTab.onActivityStop();
}
@Override
......
......@@ -474,6 +474,7 @@ void CompositorImpl::SetVisible(bool visible) {
CreateLayerTreeHost();
ui_resource_provider_.SetLayerTreeHost(host_.get());
}
root_window_->OnVisibilityChanged(visible);
}
void CompositorImpl::setDeviceScaleFactor(float factor) {
......
......@@ -10,11 +10,13 @@ namespace content {
DelegatedFrameEvictor::DelegatedFrameEvictor(
DelegatedFrameEvictorClient* client)
: client_(client), has_frame_(false) {}
: client_(client), has_frame_(false), visible_(false) {
}
DelegatedFrameEvictor::~DelegatedFrameEvictor() { DiscardedFrame(); }
void DelegatedFrameEvictor::SwappedFrame(bool visible) {
visible_ = visible;
has_frame_ = true;
RendererFrameManager::GetInstance()->AddFrame(this, visible);
}
......@@ -25,11 +27,14 @@ void DelegatedFrameEvictor::DiscardedFrame() {
}
void DelegatedFrameEvictor::SetVisible(bool visible) {
if (visible_ == visible)
return;
visible_ = visible;
if (has_frame_) {
if (visible) {
RendererFrameManager::GetInstance()->LockFrame(this);
LockFrame();
} else {
RendererFrameManager::GetInstance()->UnlockFrame(this);
UnlockFrame();
}
}
}
......
......@@ -35,6 +35,7 @@ class CONTENT_EXPORT DelegatedFrameEvictor : public RendererFrameManagerClient {
DelegatedFrameEvictorClient* client_;
bool has_frame_;
bool visible_;
DISALLOW_COPY_AND_ASSIGN(DelegatedFrameEvictor);
};
......
......@@ -549,23 +549,7 @@ void RenderWidgetHostViewAndroid::Show() {
return;
is_showing_ = true;
if (layer_.get())
layer_->SetHideLayerAndSubtree(false);
if (overscroll_controller_)
overscroll_controller_->Enable();
frame_evictor_->SetVisible(true);
if (!host_ || !host_->is_hidden())
return;
host_->WasShown(ui::LatencyInfo());
if (content_view_core_) {
StartObservingRootWindow();
RequestVSyncUpdate(BEGIN_FRAME);
}
ShowInternal();
}
void RenderWidgetHostViewAndroid::Hide() {
......@@ -573,24 +557,10 @@ void RenderWidgetHostViewAndroid::Hide() {
return;
is_showing_ = false;
if (layer_.get() && locks_on_frame_count_ == 0)
layer_->SetHideLayerAndSubtree(true);
if (overscroll_controller_)
overscroll_controller_->Disable();
frame_evictor_->SetVisible(false);
RunAckCallbacks(cc::SurfaceDrawStatus::DRAW_SKIPPED);
if (!host_ || host_->is_hidden())
return;
// Inform the renderer that we are being hidden so it can reduce its resource
// utilization.
host_->WasHidden();
StopObservingRootWindow();
bool hide_frontbuffer = true;
bool stop_observing_root_window = true;
HideInternal(hide_frontbuffer, stop_observing_root_window);
}
bool RenderWidgetHostViewAndroid::IsShowing() {
......@@ -1407,6 +1377,53 @@ void RenderWidgetHostViewAndroid::AcceleratedSurfaceInitialized(int route_id) {
accelerated_surface_route_id_ = route_id;
}
void RenderWidgetHostViewAndroid::ShowInternal() {
DCHECK(is_showing_);
if (!host_ || !host_->is_hidden())
return;
if (layer_.get())
layer_->SetHideLayerAndSubtree(false);
frame_evictor_->SetVisible(true);
if (overscroll_controller_)
overscroll_controller_->Enable();
host_->WasShown(ui::LatencyInfo());
if (content_view_core_) {
StartObservingRootWindow();
RequestVSyncUpdate(BEGIN_FRAME);
}
}
void RenderWidgetHostViewAndroid::HideInternal(
bool hide_frontbuffer,
bool stop_observing_root_window) {
if (hide_frontbuffer) {
if (layer_.get() && locks_on_frame_count_ == 0)
layer_->SetHideLayerAndSubtree(true);
frame_evictor_->SetVisible(false);
}
if (stop_observing_root_window)
StopObservingRootWindow();
if (!host_ || host_->is_hidden())
return;
if (overscroll_controller_)
overscroll_controller_->Disable();
RunAckCallbacks(cc::SurfaceDrawStatus::DRAW_SKIPPED);
// Inform the renderer that we are being hidden so it can reduce its resource
// utilization.
host_->WasHidden();
}
void RenderWidgetHostViewAndroid::AttachLayers() {
if (!content_view_core_)
return;
......@@ -1434,6 +1451,12 @@ void RenderWidgetHostViewAndroid::RemoveLayers() {
void RenderWidgetHostViewAndroid::RequestVSyncUpdate(uint32 requests) {
bool should_request_vsync = !outstanding_vsync_requests_ && requests;
outstanding_vsync_requests_ |= requests;
// If the host has been hidden, defer vsync requests until it is shown
// again via |Show()|.
if (!host_ || host_->is_hidden())
return;
// Note that if we're not currently observing the root window, outstanding
// vsync requests will be pushed if/when we resume observing in
// |StartObservingRootWindow()|.
......@@ -1443,6 +1466,7 @@ void RenderWidgetHostViewAndroid::RequestVSyncUpdate(uint32 requests) {
void RenderWidgetHostViewAndroid::StartObservingRootWindow() {
DCHECK(content_view_core_window_android_);
DCHECK(is_showing_);
if (observing_root_window_)
return;
......@@ -1755,7 +1779,8 @@ void RenderWidgetHostViewAndroid::SetContentViewCore(
if (!content_view_core_)
return;
StartObservingRootWindow();
if (is_showing_)
StartObservingRootWindow();
if (resize)
WasResized();
......@@ -1796,6 +1821,17 @@ void RenderWidgetHostViewAndroid::OnCompositingDidCommit() {
RunAckCallbacks(cc::SurfaceDrawStatus::DRAWN);
}
void RenderWidgetHostViewAndroid::OnRootWindowVisibilityChanged(bool visible) {
DCHECK(is_showing_);
if (visible) {
ShowInternal();
} else {
bool hide_frontbuffer = true;
bool stop_observing_root_window = false;
HideInternal(hide_frontbuffer, stop_observing_root_window);
}
}
void RenderWidgetHostViewAndroid::OnAttachCompositor() {
DCHECK(content_view_core_);
if (!overscroll_controller_)
......@@ -1812,7 +1848,7 @@ void RenderWidgetHostViewAndroid::OnDetachCompositor() {
void RenderWidgetHostViewAndroid::OnVSync(base::TimeTicks frame_time,
base::TimeDelta vsync_period) {
TRACE_EVENT0("cc,benchmark", "RenderWidgetHostViewAndroid::OnVSync");
if (!host_)
if (!host_ || host_->is_hidden())
return;
if (outstanding_vsync_requests_ & FLUSH_INPUT) {
......@@ -1838,6 +1874,20 @@ void RenderWidgetHostViewAndroid::OnAnimate(base::TimeTicks begin_frame_time) {
SetNeedsAnimate();
}
void RenderWidgetHostViewAndroid::OnActivityPaused() {
TRACE_EVENT0("browser", "RenderWidgetHostViewAndroid::OnActivityPaused");
DCHECK(is_showing_);
bool hide_frontbuffer = false;
bool stop_observing_root_window = false;
HideInternal(hide_frontbuffer, stop_observing_root_window);
}
void RenderWidgetHostViewAndroid::OnActivityResumed() {
TRACE_EVENT0("browser", "RenderWidgetHostViewAndroid::OnActivityResumed");
DCHECK(is_showing_);
ShowInternal();
}
void RenderWidgetHostViewAndroid::OnLostResources() {
ReleaseLocksOnSurface();
if (layer_.get())
......
......@@ -175,11 +175,14 @@ class CONTENT_EXPORT RenderWidgetHostViewAndroid
// ui::WindowAndroidObserver implementation.
void OnCompositingDidCommit() override;
void OnRootWindowVisibilityChanged(bool visible) override;
void OnAttachCompositor() override;
void OnDetachCompositor() override;
void OnVSync(base::TimeTicks frame_time,
base::TimeDelta vsync_period) override;
void OnAnimate(base::TimeTicks begin_frame_time) override;
void OnActivityPaused() override;
void OnActivityResumed() override;
// DelegatedFrameEvictor implementation
void EvictDelegatedFrame() override;
......@@ -269,6 +272,8 @@ class CONTENT_EXPORT RenderWidgetHostViewAndroid
const cc::CompositorFrameMetadata& frame_metadata);
void ComputeContentsSize(const cc::CompositorFrameMetadata& frame_metadata);
void ShowInternal();
void HideInternal(bool hide_frontbuffer, bool stop_observing_root_window);
void AttachLayers();
void RemoveLayers();
......
......@@ -72,7 +72,8 @@ public class ContentShellActivity extends Activity {
setContentView(R.layout.content_shell_activity);
mShellManager = (ShellManager) findViewById(R.id.shell_container);
mWindowAndroid = new ActivityWindowAndroid(this);
final boolean listenToActivityState = true;
mWindowAndroid = new ActivityWindowAndroid(this, listenToActivityState);
mWindowAndroid.restoreInstanceState(savedInstanceState);
mShellManager.setWindow(mWindowAndroid);
// Set up the animation placeholder to be the SurfaceView. This disables the
......@@ -183,14 +184,6 @@ public class ContentShellActivity extends Activity {
}
}
@Override
protected void onStop() {
super.onStop();
ContentViewCore contentViewCore = getActiveContentViewCore();
if (contentViewCore != null) contentViewCore.onHide();
}
@Override
protected void onStart() {
super.onStart();
......
......@@ -11,6 +11,8 @@ import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.view.View;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.ui.UiUtils;
import java.lang.ref.WeakReference;
......@@ -20,7 +22,9 @@ import java.lang.ref.WeakReference;
* Activity Instance.
* Only instantiate this class when you need the implemented features.
*/
public class ActivityWindowAndroid extends WindowAndroid implements View.OnLayoutChangeListener {
public class ActivityWindowAndroid
extends WindowAndroid
implements ApplicationStatus.ActivityStateListener, View.OnLayoutChangeListener {
// Constants used for intent request code bounding.
private static final int REQUEST_CODE_PREFIX = 1000;
private static final int REQUEST_CODE_RANGE_SIZE = 100;
......@@ -29,10 +33,28 @@ public class ActivityWindowAndroid extends WindowAndroid implements View.OnLayou
private final WeakReference<Activity> mActivityRef;
private int mNextRequestCode = 0;
/**
* Creates an Activity-specific WindowAndroid with associated intent functionality.
* TODO(jdduke): Remove this overload when all callsites have been updated to
* indicate their activity state listening preference.
* @param activity The activity associated with the WindowAndroid.
*/
public ActivityWindowAndroid(Activity activity) {
this(activity, true);
}
/**
* Creates an Activity-specific WindowAndroid with associated intent functionality.
* @param activity The activity associated with the WindowAndroid.
* @param listenToActivityState Whether to listen to activity state changes.
*/
public ActivityWindowAndroid(Activity activity, boolean listenToActivityState) {
super(activity.getApplicationContext());
mActivityRef = new WeakReference<Activity>(activity);
activity.findViewById(android.R.id.content).addOnLayoutChangeListener(this);
if (listenToActivityState) {
ApplicationStatus.registerStateListenerForActivity(this, activity);
}
}
@Override
......@@ -103,6 +125,15 @@ public class ActivityWindowAndroid extends WindowAndroid implements View.OnLayou
return new WeakReference<Activity>(mActivityRef.get());
}
@Override
public void onActivityStateChange(Activity activity, int newState) {
if (newState == ActivityState.PAUSED) {
onActivityPaused();
} else if (newState == ActivityState.RESUMED) {
onActivityResumed();
}
}
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
......
......@@ -250,6 +250,24 @@ public class WindowAndroid {
}
}
/**
* For window instances associated with an activity, notifies any listeners
* that the activity has been paused.
*/
protected void onActivityPaused() {
if (mNativeWindowAndroid == 0) return;
nativeOnActivityPaused(mNativeWindowAndroid);
}
/**
* For window instances associated with an activity, notifies any listeners
* that the activity has been paused.
*/
protected void onActivityResumed() {
if (mNativeWindowAndroid == 0) return;
nativeOnActivityResumed(mNativeWindowAndroid);
}
/**
* Responds to the intent result if the intent was created by the native window.
* @param requestCode Request code of the requested intent.
......@@ -414,6 +432,8 @@ public class WindowAndroid {
private native void nativeOnVSync(long nativeWindowAndroid,
long vsyncTimeMicros,
long vsyncPeriodMicros);
private native void nativeOnActivityPaused(long nativeWindowAndroid);
private native void nativeOnActivityResumed(long nativeWindowAndroid);
private native void nativeDestroy(long nativeWindowAndroid);
}
......@@ -43,6 +43,11 @@ void WindowAndroid::OnCompositingDidCommit() {
OnCompositingDidCommit());
}
void WindowAndroid::OnVisibilityChanged(bool visible) {
FOR_EACH_OBSERVER(WindowAndroidObserver, observer_list_,
OnRootWindowVisibilityChanged(visible));
}
void WindowAndroid::AddObserver(WindowAndroidObserver* observer) {
if (!observer_list_.HasObserver(observer))
observer_list_.AddObserver(observer);
......@@ -100,6 +105,14 @@ void WindowAndroid::OnVSync(JNIEnv* env,
compositor_->OnVSync(frame_time, vsync_period);
}
void WindowAndroid::OnActivityResumed(JNIEnv* env, jobject obj) {
FOR_EACH_OBSERVER(WindowAndroidObserver, observer_list_, OnActivityResumed());
}
void WindowAndroid::OnActivityPaused(JNIEnv* env, jobject obj) {
FOR_EACH_OBSERVER(WindowAndroidObserver, observer_list_, OnActivityPaused());
}
// ----------------------------------------------------------------------------
// Native JNI methods
// ----------------------------------------------------------------------------
......
......@@ -43,6 +43,7 @@ class UI_ANDROID_EXPORT WindowAndroid {
// Compositor callback relay.
void OnCompositingDidCommit();
void OnVisibilityChanged(bool visible);
void AttachCompositor(WindowAndroidCompositor* compositor);
void DetachCompositor();
......@@ -59,6 +60,8 @@ class UI_ANDROID_EXPORT WindowAndroid {
jlong time_micros,
jlong period_micros);
void Animate(base::TimeTicks begin_frame_time);
void OnActivityPaused(JNIEnv* env, jobject obj);
void OnActivityResumed(JNIEnv* env, jobject obj);
private:
~WindowAndroid();
......
......@@ -12,12 +12,19 @@ namespace ui {
class UI_ANDROID_EXPORT WindowAndroidObserver {
public:
virtual void OnCompositingDidCommit() = 0;
virtual void OnRootWindowVisibilityChanged(bool visible) = 0;
virtual void OnAttachCompositor() = 0;
virtual void OnDetachCompositor() = 0;
virtual void OnVSync(base::TimeTicks frame_time,
base::TimeDelta vsync_period) = 0;
virtual void OnAnimate(base::TimeTicks frame_begin_time) {}
// Note that activity state callbacks will only be made if the WindowAndroid
// has been explicitly subscribed to receive them. The observer instance
// should account for whether or not this is the case.
virtual void OnActivityPaused() = 0;
virtual void OnActivityResumed() = 0;
protected:
virtual ~WindowAndroidObserver() {}
};
......
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