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 ...@@ -2117,11 +2117,6 @@ To change this setting, <ph name="BEGIN_LINK">&lt;resetlink&gt;</ph>reset sync<p
Work profile Work profile
</message> </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 --> <!-- Download UI -->
<message name="IDS_DOWNLOAD_NOTIFICATION_COMPLETED" desc="Download notification to be displayed when a download completes."> <message name="IDS_DOWNLOAD_NOTIFICATION_COMPLETED" desc="Download notification to be displayed when a download completes.">
Download complete Download complete
......
...@@ -718,6 +718,11 @@ ...@@ -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."> <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>. 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> </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> </messages>
</release> </release>
</grit> </grit>
...@@ -34,7 +34,6 @@ android_library("weblayer_java_tests") { ...@@ -34,7 +34,6 @@ android_library("weblayer_java_tests") {
"src/org/chromium/weblayer/test/TabCallbackTest.java", "src/org/chromium/weblayer/test/TabCallbackTest.java",
"src/org/chromium/weblayer/test/TabListCallbackTest.java", "src/org/chromium/weblayer/test/TabListCallbackTest.java",
"src/org/chromium/weblayer/test/TabTest.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/TopControlsTest.java",
"src/org/chromium/weblayer/test/WebLayerLoadingTest.java", "src/org/chromium/weblayer/test/WebLayerLoadingTest.java",
"src/org/chromium/weblayer/test/WebLayerTest.java", "src/org/chromium/weblayer/test/WebLayerTest.java",
...@@ -68,6 +67,7 @@ android_library("weblayer_private_java_tests") { ...@@ -68,6 +67,7 @@ android_library("weblayer_private_java_tests") {
testonly = true testonly = true
sources = [ sources = [
"src/org/chromium/weblayer/test/BrowserControlsTest.java", "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/GeolocationTest.java",
"src/org/chromium/weblayer/test/InfoBarTest.java", "src/org/chromium/weblayer/test/InfoBarTest.java",
"src/org/chromium/weblayer/test/MediaCaptureTest.java", "src/org/chromium/weblayer/test/MediaCaptureTest.java",
...@@ -112,6 +112,7 @@ android_library("weblayer_java_test_support") { ...@@ -112,6 +112,7 @@ android_library("weblayer_java_test_support") {
"src/org/chromium/weblayer/test/NavigationWaiter.java", "src/org/chromium/weblayer/test/NavigationWaiter.java",
"src/org/chromium/weblayer/test/NewTabCallbackImpl.java", "src/org/chromium/weblayer/test/NewTabCallbackImpl.java",
"src/org/chromium/weblayer/test/ResourceUtil.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/WebLayerActivityTestRule.java",
"src/org/chromium/weblayer/test/WebLayerJUnit4ClassRunner.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") { ...@@ -108,6 +108,7 @@ android_library("java") {
"org/chromium/weblayer_private/FragmentAndroidPermissionDelegate.java", "org/chromium/weblayer_private/FragmentAndroidPermissionDelegate.java",
"org/chromium/weblayer_private/FragmentWindowAndroid.java", "org/chromium/weblayer_private/FragmentWindowAndroid.java",
"org/chromium/weblayer_private/FullscreenCallbackProxy.java", "org/chromium/weblayer_private/FullscreenCallbackProxy.java",
"org/chromium/weblayer_private/FullscreenToast.java",
"org/chromium/weblayer_private/GoogleAccountsCallbackProxy.java", "org/chromium/weblayer_private/GoogleAccountsCallbackProxy.java",
"org/chromium/weblayer_private/InfoBarContainer.java", "org/chromium/weblayer_private/InfoBarContainer.java",
"org/chromium/weblayer_private/InfoBarContainerView.java", "org/chromium/weblayer_private/InfoBarContainerView.java",
......
...@@ -21,6 +21,7 @@ import org.chromium.base.ObserverList; ...@@ -21,6 +21,7 @@ import org.chromium.base.ObserverList;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods; 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.DeviceFormFactor;
import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.base.WindowAndroid;
import org.chromium.weblayer_private.interfaces.APICallException; import org.chromium.weblayer_private.interfaces.APICallException;
...@@ -137,7 +138,7 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan ...@@ -137,7 +138,7 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan
return mWindowAndroid; return mWindowAndroid;
} }
public ViewGroup getViewAndroidDelegateContainerView() { public ContentView getViewAndroidDelegateContainerView() {
if (mViewController == null) return null; if (mViewController == null) return null;
return mViewController.getContentView(); return mViewController.getContentView();
} }
...@@ -410,7 +411,7 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan ...@@ -410,7 +411,7 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan
return true; return true;
} }
public TabImpl getActiveTab() { public @Nullable TabImpl getActiveTab() {
return BrowserImplJni.get().getActiveTab(mNativeBrowser); return BrowserImplJni.get().getActiveTab(mNativeBrowser);
} }
......
...@@ -162,7 +162,7 @@ public final class BrowserViewController ...@@ -162,7 +162,7 @@ public final class BrowserViewController
return mContentViewRenderView; return mContentViewRenderView;
} }
public ViewGroup getContentView() { public ContentView getContentView() {
return mContentView; return mContentView;
} }
......
...@@ -7,6 +7,8 @@ package org.chromium.weblayer_private; ...@@ -7,6 +7,8 @@ package org.chromium.weblayer_private;
import android.os.RemoteException; import android.os.RemoteException;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.ThreadUtils; import org.chromium.base.ThreadUtils;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
...@@ -23,12 +25,15 @@ import org.chromium.weblayer_private.interfaces.ObjectWrapper; ...@@ -23,12 +25,15 @@ import org.chromium.weblayer_private.interfaces.ObjectWrapper;
public final class FullscreenCallbackProxy { public final class FullscreenCallbackProxy {
private long mNativeFullscreenCallbackProxy; private long mNativeFullscreenCallbackProxy;
private IFullscreenCallbackClient mClient; private IFullscreenCallbackClient mClient;
private TabImpl mTab;
private FullscreenToast mToast;
FullscreenCallbackProxy(long tab, IFullscreenCallbackClient client) { FullscreenCallbackProxy(TabImpl tab, long nativeTab, IFullscreenCallbackClient client) {
assert client != null; assert client != null;
mClient = client; mClient = client;
mTab = tab;
mNativeFullscreenCallbackProxy = mNativeFullscreenCallbackProxy =
FullscreenCallbackProxyJni.get().createFullscreenCallbackProxy(this, tab); FullscreenCallbackProxyJni.get().createFullscreenCallbackProxy(this, nativeTab);
} }
public void setClient(IFullscreenCallbackClient client) { public void setClient(IFullscreenCallbackClient client) {
...@@ -40,6 +45,19 @@ public final class FullscreenCallbackProxy { ...@@ -40,6 +45,19 @@ public final class FullscreenCallbackProxy {
FullscreenCallbackProxyJni.get().deleteFullscreenCallbackProxy( FullscreenCallbackProxyJni.get().deleteFullscreenCallbackProxy(
mNativeFullscreenCallbackProxy); mNativeFullscreenCallbackProxy);
mNativeFullscreenCallbackProxy = 0; 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 @CalledByNative
...@@ -51,15 +69,19 @@ public final class FullscreenCallbackProxy { ...@@ -51,15 +69,19 @@ public final class FullscreenCallbackProxy {
if (mNativeFullscreenCallbackProxy == 0) { if (mNativeFullscreenCallbackProxy == 0) {
throw new IllegalStateException("Called after destroy()"); throw new IllegalStateException("Called after destroy()");
} }
destroyToast();
FullscreenCallbackProxyJni.get().doExitFullscreen(mNativeFullscreenCallbackProxy); FullscreenCallbackProxyJni.get().doExitFullscreen(mNativeFullscreenCallbackProxy);
} }
}; };
destroyToast();
mToast = new FullscreenToast(mTab);
mClient.enterFullscreen(ObjectWrapper.wrap(exitFullscreenCallback)); mClient.enterFullscreen(ObjectWrapper.wrap(exitFullscreenCallback));
} }
@CalledByNative @CalledByNative
private void exitFullscreen() throws RemoteException { private void exitFullscreen() throws RemoteException {
mClient.exitFullscreen(); mClient.exitFullscreen();
destroyToast();
} }
@NativeMethods @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 { ...@@ -419,6 +419,8 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
mAutofillProvider.hidePopup(); mAutofillProvider.hidePopup();
} }
if (mFullscreenCallbackProxy != null) mFullscreenCallbackProxy.destroyToast();
hideFindInPageUiAndNotifyClient(); hideFindInPageUiAndNotifyClient();
updateWebContentsVisibility(); updateWebContentsVisibility();
updateDisplayCutoutController(); updateDisplayCutoutController();
...@@ -526,7 +528,7 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer { ...@@ -526,7 +528,7 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
StrictModeWorkaround.apply(); StrictModeWorkaround.apply();
if (client != null) { if (client != null) {
if (mFullscreenCallbackProxy == null) { if (mFullscreenCallbackProxy == null) {
mFullscreenCallbackProxy = new FullscreenCallbackProxy(mNativeTab, client); mFullscreenCallbackProxy = new FullscreenCallbackProxy(this, mNativeTab, client);
} else { } else {
mFullscreenCallbackProxy.setClient(client); mFullscreenCallbackProxy.setClient(client);
} }
...@@ -984,6 +986,12 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer { ...@@ -984,6 +986,12 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
return mBrowserControlsVisibility.get() == BrowserControlsState.BOTH; return mBrowserControlsVisibility.get() == BrowserControlsState.BOTH;
} }
@VisibleForTesting
public boolean didShowFullscreenToast() {
return mFullscreenCallbackProxy != null
&& mFullscreenCallbackProxy.didShowFullscreenToast();
}
private void onBrowserControlsConstraintUpdated(int constraint) { private void onBrowserControlsConstraintUpdated(int constraint) {
// If something has overridden the FIP's SHOWN constraint, cancel FIP. This causes FIP to // If something has overridden the FIP's SHOWN constraint, cancel FIP. This causes FIP to
// dismiss when entering fullscreen. // dismiss when entering fullscreen.
......
...@@ -185,4 +185,10 @@ public final class TestWebLayerImpl extends ITestWebLayer.Stub { ...@@ -185,4 +185,10 @@ public final class TestWebLayerImpl extends ITestWebLayer.Stub {
TabImpl tabImpl = (TabImpl) tab; TabImpl tabImpl = (TabImpl) tab;
return tabImpl.getTranslateInfoBarTargetLanguageForTesting(); return tabImpl.getTranslateInfoBarTargetLanguageForTesting();
} }
@Override
public boolean didShowFullscreenToast(ITab tab) {
TabImpl tabImpl = (TabImpl) tab;
return tabImpl.didShowFullscreenToast();
}
} }
...@@ -50,4 +50,7 @@ interface ITestWebLayer { ...@@ -50,4 +50,7 @@ interface ITestWebLayer {
// Returns the target language of the currently-showing translate infobar, or null if no translate // Returns the target language of the currently-showing translate infobar, or null if no translate
// infobar is currently showing. // infobar is currently showing.
String getTranslateInfoBarTargetLanguage(in ITab tab) = 16; 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 { ...@@ -125,4 +125,8 @@ public final class TestWebLayer {
public static void disableWebViewCompatibilityMode() { public static void disableWebViewCompatibilityMode() {
WebLayer.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