Commit d3f7a876 authored by Becca Hughes's avatar Becca Hughes Committed by Commit Bot

[Display Cutout] Send safe areas from Android

This sends the safe areas from Android to Blink using the
DisplayCutoutClient Mojo interface.

This CL also includes instrumentation tests for end to end
testing of Display Cutout on Android.

BUG=847652

Change-Id: Ide2bed7878a22d649914fde66083c28374f4d768
Reviewed-on: https://chromium-review.googlesource.com/1097802
Commit-Queue: Becca Hughes <beccahughes@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#570293}
parent 3f084e15
......@@ -7,19 +7,21 @@ package org.chromium.chrome.browser.display_cutout;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.blink.mojom.ViewportFit;
import org.chromium.chrome.browser.InsetObserverView;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsObserver;
/**
* Controls the display cutout state for the tab.
*/
@JNINamespace("chrome")
public class DisplayCutoutController implements InsetObserverView.WindowInsetObserver {
/** These are the property names of the different cutout mode states. */
private static final String VIEWPORT_FIT_AUTO = "LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT";
......@@ -91,7 +93,7 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
if (mInsetObserverView != null || mTab.getActivity() == null) return;
mInsetObserverView = mTab.getActivity().getInsetObserverView();
if (mInsetObserverView == null) return;
mInsetObserverView.addObserver(this);
}
......@@ -129,12 +131,34 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
/** Implements {@link WindowInsetsObserver}. */
@Override
public void onSafeAreaChanged(Rect area) {
// TODO(beccahughes): Send this value to Blink.
WebContents webContents = mTab.getWebContents();
if (webContents == null) return;
float dipScale = getDipScale();
nativeSetSafeAreaOnWebContents(webContents, adjustInsetForScale(area.top, dipScale),
adjustInsetForScale(area.left, dipScale),
adjustInsetForScale(area.bottom, dipScale),
adjustInsetForScale(area.right, dipScale));
}
@Override
public void onInsetChanged(int left, int top, int right, int bottom) {}
/**
* Adjusts a WindowInset inset to a CSS pixel value.
* @param inset The inset as an integer.
* @param dipScale The devices dip scale as an integer.
* @return The CSS pixel value adjusted for scale.
*/
private int adjustInsetForScale(int inset, float dipScale) {
return (int) Math.ceil(inset / dipScale);
}
@VisibleForTesting
protected float getDipScale() {
return mTab.getWindowAndroid().getDisplay().getDipScale();
}
/**
* Converts a {@link ViewportFit} value into the Android P+ equivalent.
* @returns String containing the {@link LayoutParams} field name of the
......@@ -156,21 +180,40 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
}
}
@VisibleForTesting
protected Object getWindowAttributes() {
return mTab.getActivity().getWindow().getAttributes();
}
@VisibleForTesting
protected void setWindowAttributes(Object attributes) {
mTab.getActivity().getWindow().setAttributes((LayoutParams) attributes);
}
/** Updates the layout based on internal state. */
@VisibleForTesting
protected void maybeUpdateLayout() {
try {
Window window = mTab.getActivity().getWindow();
LayoutParams attributes = window.getAttributes();
LayoutParams.class.getDeclaredField("layoutInDisplayCutoutMode")
.setInt(attributes,
LayoutParams.class.getDeclaredField(getDisplayCutoutMode())
.getInt(null));
window.setAttributes(attributes);
Object attributes = getWindowAttributes();
int layoutValue =
attributes.getClass().getDeclaredField(getDisplayCutoutMode()).getInt(null);
attributes.getClass()
.getDeclaredField("layoutInDisplayCutoutMode")
.setInt(attributes, layoutValue);
setWindowAttributes(attributes);
} catch (Exception ex) {
// API is not available.
return;
}
}
// This is a breakglass that calls native code to send the safe area to the
// main frame of a WebContents.
// TODO(beccahughes): Remove when mojo associated interfaces are accessible
// from Java.
private static native void nativeSetSafeAreaOnWebContents(
WebContents contents, int top, int left, int bottom, int right);
}
......@@ -3493,6 +3493,14 @@ public class Tab
mTrustedCdnPublisherUrl = url;
}
/**
* Sets a custom {@link DisplayCutoutController} for testing.
*/
@VisibleForTesting
public void setDisplayCutoutController(DisplayCutoutController controller) {
mDisplayCutoutController = controller;
}
private native void nativeInit();
private native void nativeDestroy(long nativeTabAndroid);
private native void nativeInitWebContents(long nativeTabAndroid, boolean incognito,
......
......@@ -1705,6 +1705,8 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/customtabs/DetachedResourceRequestTest.java",
"javatests/src/org/chromium/chrome/browser/customtabs/RequestThrottlerTest.java",
"javatests/src/org/chromium/chrome/browser/customtabs/TrustedCdnPublisherUrlTest.java",
"javatests/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTest.java",
"javatests/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTestRule.java",
"javatests/src/org/chromium/chrome/browser/document/LauncherActivityTest.java",
"javatests/src/org/chromium/chrome/browser/dom_distiller/DistillabilityServiceTest.java",
"javatests/src/org/chromium/chrome/browser/dom_distiller/DistilledPagePrefsTest.java",
......
// Copyright 2018 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.chrome.browser.display_cutout;
import android.support.test.filters.LargeTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import java.util.concurrent.TimeoutException;
/**
* Tests the display cutout.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
"enable-blink-features=DisplayCutoutAPI,CSSViewport,CSSEnvironmentVariables"})
public class DisplayCutoutTest {
@Rule
public DisplayCutoutTestRule mTestRule = new DisplayCutoutTestRule();
/**
* Test that no safe area is applied when we have viewport fit auto
*/
@Test
@LargeTest
public void testViewportFitAuto() throws InterruptedException, TimeoutException {
mTestRule.enterFullscreen();
mTestRule.setViewportFit(DisplayCutoutTestRule.VIEWPORT_FIT_AUTO);
mTestRule.waitForSafeArea(DisplayCutoutTestRule.TEST_SAFE_AREA_WITHOUT_CUTOUT);
mTestRule.waitForLayoutInDisplayCutoutMode(
DisplayCutoutTestRule.LayoutParamsApi28.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT);
}
/**
* Test that no safe area is applied when we have viewport fit contain.
*/
@Test
@LargeTest
public void testViewportFitContain() throws InterruptedException, TimeoutException {
mTestRule.enterFullscreen();
mTestRule.setViewportFit(DisplayCutoutTestRule.VIEWPORT_FIT_CONTAIN);
mTestRule.waitForSafeArea(DisplayCutoutTestRule.TEST_SAFE_AREA_WITHOUT_CUTOUT);
mTestRule.waitForLayoutInDisplayCutoutMode(
DisplayCutoutTestRule.LayoutParamsApi28.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER);
}
/**
* Test that the safe area is applied when we have viewport fit cover.
*/
@Test
@LargeTest
public void testViewportFitCover() throws InterruptedException, TimeoutException {
mTestRule.enterFullscreen();
mTestRule.setViewportFit(DisplayCutoutTestRule.VIEWPORT_FIT_COVER);
mTestRule.waitForSafeArea(DisplayCutoutTestRule.TEST_SAFE_AREA_WITH_CUTOUT);
mTestRule.waitForLayoutInDisplayCutoutMode(
DisplayCutoutTestRule.LayoutParamsApi28.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
mTestRule.exitFullscreen();
mTestRule.waitForSafeArea(DisplayCutoutTestRule.TEST_SAFE_AREA_WITHOUT_CUTOUT);
mTestRule.waitForLayoutInDisplayCutoutMode(
DisplayCutoutTestRule.LayoutParamsApi28.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT);
}
/**
* Test that no safe area is applied when we have no viewport fit.
*/
@Test
@LargeTest
public void testViewportFitDefault() throws InterruptedException, TimeoutException {
mTestRule.enterFullscreen();
mTestRule.setViewportFit(DisplayCutoutTestRule.VIEWPORT_FIT_COVER);
mTestRule.waitForSafeArea(DisplayCutoutTestRule.TEST_SAFE_AREA_WITH_CUTOUT);
mTestRule.waitForLayoutInDisplayCutoutMode(
DisplayCutoutTestRule.LayoutParamsApi28.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
mTestRule.setViewportFit("");
mTestRule.waitForSafeArea(DisplayCutoutTestRule.TEST_SAFE_AREA_WITHOUT_CUTOUT);
mTestRule.waitForLayoutInDisplayCutoutMode(
DisplayCutoutTestRule.LayoutParamsApi28.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT);
}
/**
* Test that the safe area is calculated correctly using the device's dip scale.
*/
@Test
@LargeTest
public void testViewportFitDipScale() throws InterruptedException, TimeoutException {
mTestRule.enterFullscreen();
mTestRule.setDipScale(DisplayCutoutTestRule.TEST_HIGH_DIP_SCALE);
mTestRule.setViewportFit(DisplayCutoutTestRule.VIEWPORT_FIT_COVER);
mTestRule.waitForSafeArea(DisplayCutoutTestRule.TEST_SAFE_AREA_WITH_CUTOUT_HIGH_DIP);
mTestRule.waitForLayoutInDisplayCutoutMode(
DisplayCutoutTestRule.LayoutParamsApi28.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
}
/**
* Test that the safe area is calculated correctly when using a subframe.
*/
@Test
@LargeTest
public void testViewportFitSubframe() throws InterruptedException, TimeoutException {
mTestRule.enterFullscreen();
mTestRule.setViewportFit(DisplayCutoutTestRule.VIEWPORT_FIT_CONTAIN);
mTestRule.enterFullscreenOnSubframe();
mTestRule.waitForSafeArea(DisplayCutoutTestRule.TEST_SAFE_AREA_WITH_CUTOUT);
mTestRule.waitForLayoutInDisplayCutoutMode(
DisplayCutoutTestRule.LayoutParamsApi28.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
mTestRule.waitForSafeAreaOnSubframe(DisplayCutoutTestRule.TEST_SAFE_AREA_WITHOUT_CUTOUT);
}
/**
* Test that we do not break if we have cover but no cutout.
*/
@Test
@LargeTest
public void testViewportFitCoverNoCutout() throws InterruptedException, TimeoutException {
mTestRule.setDeviceHasCutout(false);
mTestRule.enterFullscreen();
mTestRule.setViewportFit(DisplayCutoutTestRule.VIEWPORT_FIT_COVER);
mTestRule.waitForSafeArea(DisplayCutoutTestRule.TEST_SAFE_AREA_WITHOUT_CUTOUT);
mTestRule.waitForLayoutInDisplayCutoutMode(
DisplayCutoutTestRule.LayoutParamsApi28.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES);
}
}
file://chrome/android/java/src/org/chromium/chrome/browser/display_cutout/OWNERS
# COMPONENT: Blink>Layout
beccahughes@chromium.org
mlamouri@chromium.org
file://chrome/android/java/src/org/chromium/chrome/browser/display_cutout/OWNERS
# COMPONENT: Blink>Layout
......@@ -2041,6 +2041,7 @@ jumbo_split_static_library("browser") {
"android/devtools_server.h",
"android/digital_asset_links/digital_asset_links_handler.cc",
"android/digital_asset_links/digital_asset_links_handler.h",
"android/display_cutout/display_cutout_controller_android.cc",
"android/document/document_web_contents_delegate.cc",
"android/document/document_web_contents_delegate.h",
"android/dom_distiller/distiller_ui_handle_android.cc",
......@@ -4380,6 +4381,7 @@ if (is_android) {
"../android/java/src/org/chromium/chrome/browser/database/SQLiteCursor.java",
"../android/java/src/org/chromium/chrome/browser/datausage/DataUseTabUIManager.java",
"../android/java/src/org/chromium/chrome/browser/datausage/ExternalDataUseObserver.java",
"../android/java/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutController.java",
"../android/java/src/org/chromium/chrome/browser/document/DocumentWebContentsDelegate.java",
"../android/java/src/org/chromium/chrome/browser/dom_distiller/DomDistillerServiceFactory.java",
"../android/java/src/org/chromium/chrome/browser/dom_distiller/DomDistillerTabUtils.java",
......
file://chrome/android/java/src/org/chromium/chrome/browser/display_cutout/OWNERS
# COMPONENT: Blink>Layout
// Copyright 2018 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.
#include "content/public/browser/web_contents.h"
#include "jni/DisplayCutoutController_jni.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/mojom/page/display_cutout.mojom.h"
using base::android::JavaParamRef;
namespace chrome {
// static
void JNI_DisplayCutoutController_SetSafeAreaOnWebContents(
JNIEnv* env,
const JavaParamRef<jclass>& clazz,
const JavaParamRef<jobject>& j_contents_android,
int top,
int left,
int bottom,
int right) {
content::WebContents* contents =
content::WebContents::FromJavaWebContents(j_contents_android);
if (!contents)
return;
content::RenderFrameHost* frame = contents->GetMainFrame();
if (!frame)
return;
blink::AssociatedInterfaceProvider* provider =
frame->GetRemoteAssociatedInterfaces();
if (!provider)
return;
blink::mojom::DisplayCutoutClientAssociatedPtr client;
provider->GetInterface(&client);
client->SetSafeArea(
blink::mojom::DisplayCutoutSafeArea::New(top, left, bottom, right));
}
} // namespace chrome
......@@ -16,6 +16,7 @@
"autofill.mojom.AutofillAgent",
"autofill.mojom.PasswordAutofillAgent",
"autofill.mojom.PasswordGenerationAgent",
"blink.mojom.DisplayCutoutClient",
"blink.mojom.document_metadata.CopylessPaste",
"blink.mojom.PauseSubresourceLoadingHandle",
"chrome.mojom.ChromeRenderFrame",
......
// Copyright 2018 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.
function fromPx(pxValue) {
return parseInt(pxValue.replace('px', ''), 10);
}
function getSafeAreas() {
const e = document.getElementById('target');
const style = window.getComputedStyle(e, null);
return {
top: fromPx(style.getPropertyValue('margin-top')),
left: fromPx(style.getPropertyValue('margin-left')),
right: fromPx(style.getPropertyValue('margin-right')),
bottom: fromPx(style.getPropertyValue('margin-bottom'))
};
}
<!DOCTYPE html>
<meta name="viewport" content="width=device-width initial-scale=1.0 viewport-fit=cover" />
<script src="shared.js"></script>
<style>
#target {
margin-top: env(safe-area-inset-top);
margin-left: env(safe-area-inset-left);
margin-bottom: env(safe-area-inset-bottom);
margin-right: env(safe-area-inset-right);
}
</style>
<div id=target></div>
<!DOCTYPE html>
<meta name="viewport" content="width=device-width initial-scale=1.0" />
<script src="shared.js"></script>
<style>
#target {
margin-top: env(safe-area-inset-top);
margin-left: env(safe-area-inset-left);
margin-bottom: env(safe-area-inset-bottom);
margin-right: env(safe-area-inset-right);
}
iframe {
width: 500px;
height: 500px;
}
</style>
<div id=target></div>
<button id=fullscreen>go fullscreen</button>
<button id=subframefull>fullscreen subframe</button>
<iframe src=subframe.html id=frame allow=fullscreen></iframe>
<script>
const frameWindow = document.getElementById('frame').contentWindow;
document.querySelector('#fullscreen').addEventListener('click', () => {
document.body.webkitRequestFullscreen();
}, { once: true });
document.querySelector('#subframefull').addEventListener('click', () => {
frameWindow.document.body.webkitRequestFullscreen();
}, { once: true });
const viewportTag = document.getElementsByTagName('meta')[0];
const defaultViewportContent = viewportTag.getAttribute('content');
function setViewportFit(value) {
viewportTag.setAttribute('content', defaultViewportContent + ', viewport-fit=' + value);
}
</script>
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