Commit d7f8f34e authored by Scott Violet's avatar Scott Violet Committed by Commit Bot

weblayer: shows toast when entering fullscreen

As the delegate is ultimately responsible for configuring fullscreen
the code adds a OnSystemUiVisibilityChangeListener. If the right
mode is entered in a short amount of time, the toast is shown.

I'm adding Skip-Translation-Screenshots-Check as I'm moving the
string, which has already been translated and didn't have a screenshot.

BUG=1127486
TEST=weblayer private tests verify this

Skip-Translation-Screenshots-Check: True
Change-Id: I788f9df3493b828e66afd57669f34d470865f60d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2419661Reviewed-by: default avatarBo <boliu@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Commit-Queue: Scott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/master@{#809107}
parent a0dcdd24
......@@ -2117,11 +2117,6 @@ To change this setting, <ph name="BEGIN_LINK">&lt;resetlink&gt;</ph>reset sync<p
Work profile
</message>
<!-- Fullscreen -->
<message name="IDS_IMMERSIVE_FULLSCREEN_API_NOTIFICATION" desc="Notification message when a site has entered immersive fullscreen and the directions of how to exit.">
Drag from top and touch the back button to exit full screen.
</message>
<!-- Download UI -->
<message name="IDS_DOWNLOAD_NOTIFICATION_COMPLETED" desc="Download notification to be displayed when a download completes.">
Download complete
......
......@@ -718,6 +718,11 @@
<message name="IDS_ACCESSIBILITY_PLAYBACK_TIME" desc="The accessibility string for how much time has elapsed while viewing the video and how long the video is in total.">
Elapsed time <ph name="ELAPSED_TIME">%1$s<ex>0:01</ex></ph> of <ph name="TOTAL_TIME">%2$s<ex>0:45</ex></ph>.
</message>
<!-- Fullscreen -->
<message name="IDS_IMMERSIVE_FULLSCREEN_API_NOTIFICATION" desc="Notification message when a site has entered immersive fullscreen and the directions of how to exit.">
Drag from top and touch the back button to exit full screen.
</message>
</messages>
</release>
</grit>
......@@ -34,7 +34,6 @@ android_library("weblayer_java_tests") {
"src/org/chromium/weblayer/test/TabCallbackTest.java",
"src/org/chromium/weblayer/test/TabListCallbackTest.java",
"src/org/chromium/weblayer/test/TabTest.java",
"src/org/chromium/weblayer/test/TestFullscreenCallback.java",
"src/org/chromium/weblayer/test/TopControlsTest.java",
"src/org/chromium/weblayer/test/WebLayerLoadingTest.java",
"src/org/chromium/weblayer/test/WebLayerTest.java",
......@@ -68,6 +67,7 @@ android_library("weblayer_private_java_tests") {
testonly = true
sources = [
"src/org/chromium/weblayer/test/BrowserControlsTest.java",
"src/org/chromium/weblayer/test/FullscreenCallbackPrivateTest.java",
"src/org/chromium/weblayer/test/GeolocationTest.java",
"src/org/chromium/weblayer/test/InfoBarTest.java",
"src/org/chromium/weblayer/test/MediaCaptureTest.java",
......@@ -112,6 +112,7 @@ android_library("weblayer_java_test_support") {
"src/org/chromium/weblayer/test/NavigationWaiter.java",
"src/org/chromium/weblayer/test/NewTabCallbackImpl.java",
"src/org/chromium/weblayer/test/ResourceUtil.java",
"src/org/chromium/weblayer/test/TestFullscreenCallback.java",
"src/org/chromium/weblayer/test/WebLayerActivityTestRule.java",
"src/org/chromium/weblayer/test/WebLayerJUnit4ClassRunner.java",
]
......
// Copyright 2019 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.weblayer.test;
import android.os.RemoteException;
import android.view.View;
import androidx.test.filters.SmallTest;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.weblayer.FullscreenCallback;
import org.chromium.weblayer.TestWebLayer;
import org.chromium.weblayer.shell.InstrumentationActivity;
/**
* Fullscreen test assertions that can only be done using private API.
*/
@RunWith(WebLayerJUnit4ClassRunner.class)
public class FullscreenCallbackPrivateTest {
@Rule
public InstrumentationActivityTestRule mActivityTestRule =
new InstrumentationActivityTestRule();
private InstrumentationActivity mActivity;
private TestFullscreenCallback mDelegate;
@Before
public void setUp() {
String url = mActivityTestRule.getTestDataURL("fullscreen.html");
mActivity = mActivityTestRule.launchShellWithUrl(url);
Assert.assertNotNull(mActivity);
mDelegate = new TestFullscreenCallback();
TestThreadUtils.runOnUiThreadBlocking(
() -> { mActivity.getTab().setFullscreenCallback(mDelegate); });
}
private TestWebLayer getTestWebLayer() {
return TestWebLayer.getTestWebLayer(
mActivityTestRule.getActivity().getApplicationContext());
}
@Test
@SmallTest
public void testToastNotShownWhenFullscreenRequestIgnored() throws Throwable {
Assert.assertFalse(TestThreadUtils.runOnUiThreadBlocking(
() -> { return getTestWebLayer().didShowFullscreenToast(mActivity.getTab()); }));
// Touch to enter fullscreen
EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
mDelegate.waitForFullscreen();
Assert.assertEquals(1, mDelegate.mEnterFullscreenCount);
Assert.assertFalse(TestThreadUtils.runOnUiThreadBlocking(
() -> { return getTestWebLayer().didShowFullscreenToast(mActivity.getTab()); }));
}
@Test
@SmallTest
public void testToastShown() throws Throwable {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mActivity.getTab().setFullscreenCallback(new FullscreenCallback() {
@Override
public void onEnterFullscreen(Runnable exitFullscreenRunner) {
mActivity.getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
}
@Override
public void onExitFullscreen() {}
});
});
// Touch to enter fullscreen
EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
CriteriaHelper.pollUiThread(() -> {
try {
Criteria.checkThat(getTestWebLayer().didShowFullscreenToast(mActivity.getTab()),
Matchers.is(true));
} catch (RemoteException e) {
throw new RuntimeException(e);
}
});
}
}
......@@ -108,6 +108,7 @@ android_library("java") {
"org/chromium/weblayer_private/FragmentAndroidPermissionDelegate.java",
"org/chromium/weblayer_private/FragmentWindowAndroid.java",
"org/chromium/weblayer_private/FullscreenCallbackProxy.java",
"org/chromium/weblayer_private/FullscreenToast.java",
"org/chromium/weblayer_private/GoogleAccountsCallbackProxy.java",
"org/chromium/weblayer_private/InfoBarContainer.java",
"org/chromium/weblayer_private/InfoBarContainerView.java",
......
......@@ -21,6 +21,7 @@ import org.chromium.base.ObserverList;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.components.embedder_support.view.ContentView;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.weblayer_private.interfaces.APICallException;
......@@ -137,7 +138,7 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan
return mWindowAndroid;
}
public ViewGroup getViewAndroidDelegateContainerView() {
public ContentView getViewAndroidDelegateContainerView() {
if (mViewController == null) return null;
return mViewController.getContentView();
}
......@@ -410,7 +411,7 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan
return true;
}
public TabImpl getActiveTab() {
public @Nullable TabImpl getActiveTab() {
return BrowserImplJni.get().getActiveTab(mNativeBrowser);
}
......
......@@ -162,7 +162,7 @@ public final class BrowserViewController
return mContentViewRenderView;
}
public ViewGroup getContentView() {
public ContentView getContentView() {
return mContentView;
}
......
......@@ -7,6 +7,8 @@ package org.chromium.weblayer_private;
import android.os.RemoteException;
import android.webkit.ValueCallback;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.ThreadUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
......@@ -23,12 +25,15 @@ import org.chromium.weblayer_private.interfaces.ObjectWrapper;
public final class FullscreenCallbackProxy {
private long mNativeFullscreenCallbackProxy;
private IFullscreenCallbackClient mClient;
private TabImpl mTab;
private FullscreenToast mToast;
FullscreenCallbackProxy(long tab, IFullscreenCallbackClient client) {
FullscreenCallbackProxy(TabImpl tab, long nativeTab, IFullscreenCallbackClient client) {
assert client != null;
mClient = client;
mTab = tab;
mNativeFullscreenCallbackProxy =
FullscreenCallbackProxyJni.get().createFullscreenCallbackProxy(this, tab);
FullscreenCallbackProxyJni.get().createFullscreenCallbackProxy(this, nativeTab);
}
public void setClient(IFullscreenCallbackClient client) {
......@@ -40,6 +45,19 @@ public final class FullscreenCallbackProxy {
FullscreenCallbackProxyJni.get().deleteFullscreenCallbackProxy(
mNativeFullscreenCallbackProxy);
mNativeFullscreenCallbackProxy = 0;
destroyToast();
mTab = null;
}
public void destroyToast() {
if (mToast == null) return;
mToast.destroy();
mToast = null;
}
@VisibleForTesting
public boolean didShowFullscreenToast() {
return mToast != null && mToast.didShowFullscreenToast();
}
@CalledByNative
......@@ -51,15 +69,19 @@ public final class FullscreenCallbackProxy {
if (mNativeFullscreenCallbackProxy == 0) {
throw new IllegalStateException("Called after destroy()");
}
destroyToast();
FullscreenCallbackProxyJni.get().doExitFullscreen(mNativeFullscreenCallbackProxy);
}
};
destroyToast();
mToast = new FullscreenToast(mTab);
mClient.enterFullscreen(ObjectWrapper.wrap(exitFullscreenCallback));
}
@CalledByNative
private void exitFullscreen() throws RemoteException {
mClient.exitFullscreen();
destroyToast();
}
@NativeMethods
......
// Copyright 2020 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.weblayer_private;
import android.view.Gravity;
import android.view.View;
import androidx.annotation.VisibleForTesting;
import org.chromium.components.embedder_support.view.ContentView;
import org.chromium.ui.widget.Toast;
/**
* FullscreenToast is responsible for showing toast when fullscreen mode is entered. As the embedder
* is responsible for entering fullscreen mode, there is no guarantee when or if fullscreen mode
* will be entered. This waits for the system to enter fullscreen mode and then show the toast. If
* fullscreen isn't entered after a short delay this assumes the embedder won't enter fullscreen
* and the toast is never shown.
*/
public final class FullscreenToast {
// The tab the toast is showing from.
private TabImpl mTab;
// View used to register for system ui change notification.
private ContentView mView;
private View.OnSystemUiVisibilityChangeListener mSystemUiVisibilityChangeListener;
// Set to true once toast is shown.
private boolean mDidShowToast;
// The toast.
private Toast mToast;
FullscreenToast(TabImpl tab) {
mTab = tab;
// TODO(https://crbug.com/1130096): This should really be handled lower down in the stack.
if (tab.getBrowser().getActiveTab() != tab) return;
addSystemUiChangedObserver();
}
@VisibleForTesting
public boolean didShowFullscreenToast() {
return mDidShowToast;
}
public void destroy() {
// This may be called more than once.
if (mTab == null) return;
if (mSystemUiVisibilityChangeListener != null) {
// mSystemUiVisibilityChangeListener is only installed if mView is non-null.
assert mView != null;
mView.removeOnSystemUiVisibilityChangeListener(mSystemUiVisibilityChangeListener);
mSystemUiVisibilityChangeListener = null;
}
mTab = null;
mView = null;
if (mToast != null) {
mToast.cancel();
mToast = null;
}
}
private void addSystemUiChangedObserver() {
if (mTab.getBrowser().getViewAndroidDelegateContainerView() == null) {
return;
}
mView = mTab.getBrowser().getViewAndroidDelegateContainerView();
mSystemUiVisibilityChangeListener = new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
// The listener should have been removed if destroy() was called.
assert mTab != null;
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
// No longer in fullscreen. Destroy.
destroy();
} else if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
&& !mDidShowToast) {
// Only show the toast when navigation is hidden and toast wasn't already shown.
showToast();
mDidShowToast = true;
}
}
};
mView.addOnSystemUiVisibilityChangeListener(mSystemUiVisibilityChangeListener);
// See class description for details on why a timeout is used.
mView.postDelayed(() -> {
if (!mDidShowToast) destroy();
}, 1000);
}
private void showToast() {
assert mToast == null;
mDidShowToast = true;
int resId = R.string.immersive_fullscreen_api_notification;
mToast = Toast.makeText(mView.getContext(), resId, Toast.LENGTH_LONG);
mToast.setGravity(Gravity.TOP | Gravity.CENTER, 0, 0);
mToast.show();
}
}
......@@ -419,6 +419,8 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
mAutofillProvider.hidePopup();
}
if (mFullscreenCallbackProxy != null) mFullscreenCallbackProxy.destroyToast();
hideFindInPageUiAndNotifyClient();
updateWebContentsVisibility();
updateDisplayCutoutController();
......@@ -526,7 +528,7 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
StrictModeWorkaround.apply();
if (client != null) {
if (mFullscreenCallbackProxy == null) {
mFullscreenCallbackProxy = new FullscreenCallbackProxy(mNativeTab, client);
mFullscreenCallbackProxy = new FullscreenCallbackProxy(this, mNativeTab, client);
} else {
mFullscreenCallbackProxy.setClient(client);
}
......@@ -984,6 +986,12 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
return mBrowserControlsVisibility.get() == BrowserControlsState.BOTH;
}
@VisibleForTesting
public boolean didShowFullscreenToast() {
return mFullscreenCallbackProxy != null
&& mFullscreenCallbackProxy.didShowFullscreenToast();
}
private void onBrowserControlsConstraintUpdated(int constraint) {
// If something has overridden the FIP's SHOWN constraint, cancel FIP. This causes FIP to
// dismiss when entering fullscreen.
......
......@@ -185,4 +185,10 @@ public final class TestWebLayerImpl extends ITestWebLayer.Stub {
TabImpl tabImpl = (TabImpl) tab;
return tabImpl.getTranslateInfoBarTargetLanguageForTesting();
}
@Override
public boolean didShowFullscreenToast(ITab tab) {
TabImpl tabImpl = (TabImpl) tab;
return tabImpl.didShowFullscreenToast();
}
}
......@@ -50,4 +50,7 @@ interface ITestWebLayer {
// Returns the target language of the currently-showing translate infobar, or null if no translate
// infobar is currently showing.
String getTranslateInfoBarTargetLanguage(in ITab tab) = 16;
// Returns true if a fullscreen toast was shown for |tab|.
boolean didShowFullscreenToast(in ITab tab) = 17;
}
......@@ -125,4 +125,8 @@ public final class TestWebLayer {
public static void disableWebViewCompatibilityMode() {
WebLayer.disableWebViewCompatibilityMode();
}
public boolean didShowFullscreenToast(Tab tab) throws RemoteException {
return mITestWebLayer.didShowFullscreenToast(tab.getITab());
}
}
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