Commit 24073a6b authored by Yuwei Huang's avatar Yuwei Huang Committed by Commit Bot

[Remoting Android] Allow panning canvas out of the cutout area

Currently the Android app doesn't adjust the desktop canvas for cutouts
on a notched device, so part of the desktop will get obstructed by the
cutout.

This CL fixes this by introducing a concept of safe area to
DesktopCanvas. User can still see through and interact with the content
in the unsafe area and pan the canvas out of it, while we use the safe
area to calculate the minimum zoom level.

Bug: 831670
Change-Id: I6a42bab8f383fa682bd2d2363b41bc6bb7a6d1c3
Reviewed-on: https://chromium-review.googlesource.com/1174982Reviewed-by: default avatarJoe Downing <joedow@chromium.org>
Commit-Queue: Yuwei Huang <yuweih@chromium.org>
Cr-Commit-Position: refs/heads/master@{#584535}
parent 31a1ccc0
...@@ -7,6 +7,7 @@ package org.chromium.chromoting; ...@@ -7,6 +7,7 @@ package org.chromium.chromoting;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
...@@ -14,6 +15,7 @@ import android.os.Handler; ...@@ -14,6 +15,7 @@ import android.os.Handler;
import android.support.v7.app.ActionBar.OnMenuVisibilityListener; import android.support.v7.app.ActionBar.OnMenuVisibilityListener;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.DisplayCutout;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
...@@ -21,6 +23,8 @@ import android.view.MotionEvent; ...@@ -21,6 +23,8 @@ import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.View.OnLayoutChangeListener; import android.view.View.OnLayoutChangeListener;
import android.view.View.OnTouchListener; import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
...@@ -136,6 +140,15 @@ public class Desktop ...@@ -136,6 +140,15 @@ public class Desktop
// background. Setting the background color to match our canvas will prevent the flash. // background. Setting the background color to match our canvas will prevent the flash.
getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK)); getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// Short edges mode makes DesktopView stretch to the whole screen even if it gets
// obstructed by the cutouts.
WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
layoutParams.layoutInDisplayCutoutMode =
LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(layoutParams);
}
mActivityLifecycleListener = mClient.getCapabilityManager().onActivityAcceptingListener( mActivityLifecycleListener = mClient.getCapabilityManager().onActivityAcceptingListener(
this, Capabilities.CAST_CAPABILITY); this, Capabilities.CAST_CAPABILITY);
mActivityLifecycleListener.onActivityCreated(this, savedInstanceState); mActivityLifecycleListener.onActivityCreated(this, savedInstanceState);
...@@ -462,6 +475,24 @@ public class Desktop ...@@ -462,6 +475,24 @@ public class Desktop
return flags; return flags;
} }
/**
* @return The insets from each edge on the screen that avoid display cutouts.
*/
@SuppressLint("InlinedApi")
public Rect getSafeInsets() {
Rect insets = new Rect();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
return insets;
}
DisplayCutout cutout = mRemoteHostDesktop.getRootWindowInsets().getDisplayCutout();
if (cutout != null) {
insets.set(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
}
return insets;
}
/** /**
* Shows the soft keyboard if no physical keyboard is attached. * Shows the soft keyboard if no physical keyboard is attached.
*/ */
......
...@@ -25,6 +25,12 @@ public class DesktopCanvas { ...@@ -25,6 +25,12 @@ public class DesktopCanvas {
private final RenderStub mRenderStub; private final RenderStub mRenderStub;
private final RenderData mRenderData; private final RenderData mRenderData;
/**
* The insets from each edge of the screen which avoid display cutouts. Note that this is not
* a real rectangle as left > right and top > bottom can be true.
*/
private final Rect mSafeInsets = new Rect();
/** /**
* Represents the desired center of the viewport in image space. This value may not represent * Represents the desired center of the viewport in image space. This value may not represent
* the actual center of the viewport as adjustments are made to ensure as much of the desktop is * the actual center of the viewport as adjustments are made to ensure as much of the desktop is
...@@ -137,6 +143,20 @@ public class DesktopCanvas { ...@@ -137,6 +143,20 @@ public class DesktopCanvas {
} }
} }
/**
* Sets the insets from each edge on the screen that avoid display cutouts.
*
* @param insets The insets from each edge on the screen that avoid display cutouts.
*/
public void setSafeInsets(Rect insets) {
mSafeInsets.set(insets);
if (mRenderData.initialized()) {
// The viewport center may have changed so update the position to reflect the new value.
repositionImage();
}
}
public void adjustViewportForSystemUi(boolean adjustViewportForSystemUi) { public void adjustViewportForSystemUi(boolean adjustViewportForSystemUi) {
mAdjustViewportSizeForSystemUi = adjustViewportForSystemUi; mAdjustViewportSizeForSystemUi = adjustViewportForSystemUi;
...@@ -153,8 +173,9 @@ public class DesktopCanvas { ...@@ -153,8 +173,9 @@ public class DesktopCanvas {
return; return;
} }
float widthRatio = (float) mRenderData.screenWidth / mRenderData.imageWidth; float widthRatio = getSafeScreenWidth() / mRenderData.imageWidth;
float heightRatio = (float) mRenderData.screenHeight / mRenderData.imageHeight; float heightRatio =
calculateSafeScreenHeight(mRenderData.screenHeight) / mRenderData.imageHeight;
float screenToImageScale = Math.max(widthRatio, heightRatio); float screenToImageScale = Math.max(widthRatio, heightRatio);
// If the image is smaller than the screen in either dimension, then we want to scale it // If the image is smaller than the screen in either dimension, then we want to scale it
...@@ -195,11 +216,12 @@ public class DesktopCanvas { ...@@ -195,11 +216,12 @@ public class DesktopCanvas {
float[] imageSize = {mRenderData.imageWidth, mRenderData.imageHeight}; float[] imageSize = {mRenderData.imageWidth, mRenderData.imageHeight};
mRenderData.transform.mapVectors(imageSize); mRenderData.transform.mapVectors(imageSize);
if (imageSize[0] < mRenderData.screenWidth && imageSize[1] < mRenderData.screenHeight) { if (imageSize[0] < getSafeScreenWidth()
&& imageSize[1] < calculateSafeScreenHeight(mRenderData.screenHeight)) {
// Displayed image is too small in both directions, so apply the minimum zoom // Displayed image is too small in both directions, so apply the minimum zoom
// level needed to fit either the width or height. // level needed to fit either the width or height.
float scale = Math.min((float) mRenderData.screenWidth / mRenderData.imageWidth, float scale = Math.min(getSafeScreenWidth() / mRenderData.imageWidth,
(float) mRenderData.screenHeight / mRenderData.imageHeight); calculateSafeScreenHeight(mRenderData.screenHeight) / mRenderData.imageHeight);
mRenderData.transform.setScale(scale, scale); mRenderData.transform.setScale(scale, scale);
} }
...@@ -208,8 +230,9 @@ public class DesktopCanvas { ...@@ -208,8 +230,9 @@ public class DesktopCanvas {
} else { } else {
// Find the new screen center (it probably changed during the zoom operation) and update // Find the new screen center (it probably changed during the zoom operation) and update
// the viewport to smoothly track the zoom gesture. // the viewport to smoothly track the zoom gesture.
float[] mappedPoints = {((float) mRenderData.screenWidth / 2) - mViewportOffset.x, PointF safeScreenCenter = calculateSafeScreenCenterPoint(mRenderData.screenHeight);
((float) mRenderData.screenHeight / 2) - mViewportOffset.y}; float[] mappedPoints = {
safeScreenCenter.x - mViewportOffset.x, safeScreenCenter.y - mViewportOffset.y};
Matrix screenToImage = new Matrix(); Matrix screenToImage = new Matrix();
mRenderData.transform.invert(screenToImage); mRenderData.transform.invert(screenToImage);
screenToImage.mapPoints(mappedPoints); screenToImage.mapPoints(mappedPoints);
...@@ -252,7 +275,7 @@ public class DesktopCanvas { ...@@ -252,7 +275,7 @@ public class DesktopCanvas {
* account. * account.
*/ */
private PointF getViewportScreenCenter() { private PointF getViewportScreenCenter() {
return new PointF((float) mRenderData.screenWidth / 2, getAdjustedScreenHeight() / 2); return calculateSafeScreenCenterPoint(getAdjustedScreenHeight());
} }
/** /**
...@@ -313,15 +336,17 @@ public class DesktopCanvas { ...@@ -313,15 +336,17 @@ public class DesktopCanvas {
float[] screenVectors = {viewportCenter.x, viewportCenter.y}; float[] screenVectors = {viewportCenter.x, viewportCenter.y};
screenToImage.mapVectors(screenVectors); screenToImage.mapVectors(screenVectors);
PointF letterboxPadding = getLetterboxPadding(); RectF letterboxPadding = getLetterboxPadding();
float[] letterboxPaddingVectors = {letterboxPadding.x, letterboxPadding.y}; float[] letterboxPaddingLeftTop = {letterboxPadding.left, letterboxPadding.top};
screenToImage.mapVectors(letterboxPaddingVectors); float[] letterboxPaddingRightBottom = {letterboxPadding.right, letterboxPadding.bottom};
screenToImage.mapVectors(letterboxPaddingLeftTop);
screenToImage.mapVectors(letterboxPaddingRightBottom);
// screenCenter values are 1/2 of a particular screen dimension mapped to image space.
float screenCenterX = screenVectors[0] - letterboxPaddingVectors[0];
float screenCenterY = screenVectors[1] - letterboxPaddingVectors[1];
RectF imageBounds = getImageBounds(); RectF imageBounds = getImageBounds();
imageBounds.inset(screenCenterX, screenCenterY); imageBounds.left += screenVectors[0] - letterboxPaddingLeftTop[0];
imageBounds.top += screenVectors[1] - letterboxPaddingLeftTop[1];
imageBounds.right -= screenVectors[0] - letterboxPaddingRightBottom[0];
imageBounds.bottom -= screenVectors[1] - letterboxPaddingRightBottom[1];
return imageBounds; return imageBounds;
} }
...@@ -348,18 +373,25 @@ public class DesktopCanvas { ...@@ -348,18 +373,25 @@ public class DesktopCanvas {
} }
/** /**
* Provides the amount of padding needed to center the image content on the screen. * Provides the amount of padding needed to center the image content on the screen. Safe insets
* are fixed constant letterbox padding that exist even when the image is not smaller than the
* screen.
*/ */
private PointF getLetterboxPadding() { private RectF getLetterboxPadding() {
float[] imageVectors = {mRenderData.imageWidth, mRenderData.imageHeight}; float[] imageVectors = {mRenderData.imageWidth, mRenderData.imageHeight};
mRenderData.transform.mapVectors(imageVectors); mRenderData.transform.mapVectors(imageVectors);
// We want to letterbox when the image is smaller than the screen in a specific dimension. // We want to letterbox when the image is smaller than the screen in a specific dimension.
// Since we center the image, split the difference so it is equally distributed. // Since we center the image, split the difference so it is equally distributed.
float widthAdjust = Math.max(((float) mRenderData.screenWidth - imageVectors[0]) / 2, 0); float widthAdjust = Math.max((getSafeScreenWidth() - imageVectors[0]) / 2, 0);
float heightAdjust = Math.max((getAdjustedScreenHeight() - imageVectors[1]) / 2, 0); float heightAdjust = Math.max(
(calculateSafeScreenHeight(getAdjustedScreenHeight()) - imageVectors[1]) / 2, 0);
return new PointF(widthAdjust, heightAdjust); // Add the safe insets.
RectF padding = new RectF(widthAdjust + mSafeInsets.left, heightAdjust + mSafeInsets.top,
widthAdjust + mSafeInsets.right, heightAdjust + mSafeInsets.bottom);
return padding;
} }
/** /**
...@@ -376,12 +408,14 @@ public class DesktopCanvas { ...@@ -376,12 +408,14 @@ public class DesktopCanvas {
// between the System UI and the remote desktop image. // between the System UI and the remote desktop image.
// Note: Ignore negative padding (clamp to 0) since that means no overlap exists. // Note: Ignore negative padding (clamp to 0) since that means no overlap exists.
float adjustedScreenHeight = getAdjustedScreenHeight(); float adjustedScreenHeight = getAdjustedScreenHeight();
PointF letterboxPadding = getLetterboxPadding(); RectF letterboxPadding = getLetterboxPadding();
return new RectF(Math.max(mSystemUiScreenRect.left - letterboxPadding.x, 0.0f), return new RectF(Math.max(mSystemUiScreenRect.left - letterboxPadding.left, 0.0f),
Math.max(mSystemUiScreenRect.top - letterboxPadding.y, 0.0f), Math.max(mSystemUiScreenRect.top - letterboxPadding.top, 0.0f),
Math.max(mRenderData.screenWidth - mSystemUiScreenRect.right - letterboxPadding.x, Math.max(mRenderData.screenWidth - mSystemUiScreenRect.right
- letterboxPadding.right,
0.0f), 0.0f),
Math.max(adjustedScreenHeight - mSystemUiScreenRect.bottom - letterboxPadding.y, Math.max(
adjustedScreenHeight - mSystemUiScreenRect.bottom - letterboxPadding.bottom,
0.0f)); 0.0f));
} }
...@@ -465,4 +499,31 @@ public class DesktopCanvas { ...@@ -465,4 +499,31 @@ public class DesktopCanvas {
private boolean arePointsEqual(PointF a, PointF b, float epsilon) { private boolean arePointsEqual(PointF a, PointF b, float epsilon) {
return Math.abs(a.x - b.x) < epsilon && Math.abs(a.y - b.y) < epsilon; return Math.abs(a.x - b.x) < epsilon && Math.abs(a.y - b.y) < epsilon;
} }
/**
* @return Screen width inset by {@link #mSafeInsets}.
*/
private float getSafeScreenWidth() {
return mRenderData.screenWidth - mSafeInsets.left - mSafeInsets.right;
}
/**
* @param height The height to be used to calculate the safe screen height. Generally either
* {@link #getAdjustedScreenHeight()} or mRenderData.screenHeight.
* @return Screen height inset by {@link #mSafeInsets}.
*/
private float calculateSafeScreenHeight(float height) {
return height - mSafeInsets.top - mSafeInsets.bottom;
}
/**
* @param height The height to be used to calculate the safe screen height. Generally either
* {@link #getAdjustedScreenHeight()} or mRenderData.screenHeight.
* @return Center of the safe area on the screen inset by {@link #mSafeInsets}.
*/
private PointF calculateSafeScreenCenterPoint(float height) {
return new PointF(
(float) mRenderData.screenWidth / 2 + mSafeInsets.left - mSafeInsets.right,
height / 2 + mSafeInsets.top - mSafeInsets.bottom);
}
} }
...@@ -384,6 +384,8 @@ public class TouchInputHandler { ...@@ -384,6 +384,8 @@ public class TouchInputHandler {
mRenderData.screenWidth = width; mRenderData.screenWidth = width;
mRenderData.screenHeight = height; mRenderData.screenHeight = height;
mDesktopCanvas.setSafeInsets(mDesktop.getSafeInsets());
mPanGestureBounds = new Rect( mPanGestureBounds = new Rect(
mEdgeSlopInPx, mEdgeSlopInPx, width - mEdgeSlopInPx, height - mEdgeSlopInPx); mEdgeSlopInPx, mEdgeSlopInPx, width - mEdgeSlopInPx, height - mEdgeSlopInPx);
resizeImageToFitScreen(); resizeImageToFitScreen();
......
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