Merge "feat(magnification settings): Add Diagonal resize handles for Window magnification"

This commit is contained in:
TreeHugger Robot
2022-11-14 20:56:53 +00:00
committed by Android (Google) Code Review
11 changed files with 413 additions and 48 deletions

View File

@@ -0,0 +1,26 @@
<!--
Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/accessibility_window_magnifier_button_bg" />
<size
android:width="40dp"
android:height="40dp"/>
<corners android:radius="2dp"/>
<stroke
android:color="@color/accessibility_window_magnifier_button_bg_stroke"
android:width="1dp" />
</shape>

View File

@@ -0,0 +1,33 @@
<!--
Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M0,24l0,-4l24,-0l0,4z"
android:strokeWidth="1"
android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
<path
android:pathData="M0,24l0,-24l4,-0l0,24z"
android:strokeWidth="1"
android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@@ -0,0 +1,33 @@
<!--
Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M24,24l-4,-0l-0,-24l4,-0z"
android:strokeWidth="1"
android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
<path
android:pathData="M24,24l-24,-0l-0,-4l24,-0z"
android:strokeWidth="1"
android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@@ -0,0 +1,33 @@
<!--
Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M0,0h4v24h-4z"
android:strokeWidth="1"
android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
<path
android:pathData="M0,0h24v4h-24z"
android:strokeWidth="1"
android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@@ -0,0 +1,33 @@
<!--
Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M24,0l-0,4l-24,0l-0,-4z"
android:strokeWidth="1"
android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
<path
android:pathData="M24,0l-0,24l-4,0l-0,-24z"
android:strokeWidth="1"
android:fillColor="@color/accessibility_window_magnifier_corner_view_color"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@@ -40,19 +40,22 @@
android:id="@+id/top_handle"
android:layout_width="match_parent"
android:layout_height="@dimen/magnification_border_drag_size"
android:layout_alignParentTop="true"/>
android:layout_alignParentTop="true"
android:accessibilityTraversalAfter="@id/left_handle"/>
<View
android:id="@+id/right_handle"
android:layout_width="@dimen/magnification_border_drag_size"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"/>
android:layout_alignParentEnd="true"
android:accessibilityTraversalAfter="@id/top_handle"/>
<View
android:id="@+id/bottom_handle"
android:layout_width="match_parent"
android:layout_height="@dimen/magnification_border_drag_size"
android:layout_alignParentBottom="true"/>
android:layout_alignParentBottom="true"
android:accessibilityTraversalAfter="@id/right_handle"/>
<SurfaceView
android:id="@+id/surface_view"
@@ -61,6 +64,58 @@
android:layout_margin="@dimen/magnification_mirror_surface_margin"/>
</RelativeLayout>
<ImageView
android:id="@+id/top_right_corner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/magnification_outer_border_margin"
android:layout_gravity="right|top"
android:paddingTop="@dimen/magnifier_drag_handle_padding"
android:paddingEnd="@dimen/magnifier_drag_handle_padding"
android:scaleType="center"
android:contentDescription="@string/magnification_drag_corner_to_resize"
android:src="@drawable/ic_magnification_corner_top_right"
android:accessibilityTraversalAfter="@id/top_left_corner"/>
<ImageView
android:id="@+id/top_left_corner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/magnification_outer_border_margin"
android:layout_gravity="left|top"
android:paddingTop="@dimen/magnifier_drag_handle_padding"
android:paddingStart="@dimen/magnifier_drag_handle_padding"
android:scaleType="center"
android:contentDescription="@string/magnification_drag_corner_to_resize"
android:src="@drawable/ic_magnification_corner_top_left"
android:accessibilityTraversalAfter="@id/bottom_handle"/>
<ImageView
android:id="@+id/bottom_right_corner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/magnification_outer_border_margin"
android:layout_gravity="right|bottom"
android:paddingEnd="@dimen/magnifier_drag_handle_padding"
android:paddingBottom="@dimen/magnifier_drag_handle_padding"
android:scaleType="center"
android:contentDescription="@string/magnification_drag_corner_to_resize"
android:src="@drawable/ic_magnification_corner_bottom_right"
android:accessibilityTraversalAfter="@id/top_right_corner"/>
<ImageView
android:id="@+id/bottom_left_corner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/magnification_outer_border_margin"
android:layout_gravity="left|bottom"
android:paddingStart="@dimen/magnifier_drag_handle_padding"
android:paddingBottom="@dimen/magnifier_drag_handle_padding"
android:scaleType="center"
android:contentDescription="@string/magnification_drag_corner_to_resize"
android:src="@drawable/ic_magnification_corner_bottom_left"
android:accessibilityTraversalAfter="@id/bottom_right_corner"/>
<ImageView
android:id="@+id/drag_handle"
android:layout_width="@dimen/magnification_drag_view_size"

View File

@@ -172,6 +172,10 @@
<color name="accessibility_magnifier_bg">#FCFCFC</color>
<color name="accessibility_magnifier_bg_stroke">#E0E0E0</color>
<color name="accessibility_magnifier_icon_color">#252525</color>
<color name="accessibility_window_magnifier_button_bg">#0680FD</color>
<color name="accessibility_window_magnifier_icon_color">#FAFAFA</color>
<color name="accessibility_window_magnifier_button_bg_stroke">#252525</color>
<color name="accessibility_window_magnifier_corner_view_color">#0680FD</color>
<!-- Volume dialog colors -->
<color name="volume_dialog_background_color">@android:color/transparent</color>

View File

@@ -2237,6 +2237,8 @@
<string name="magnification_mode_switch_state_window">Magnify part of screen</string>
<!-- Click action label for magnification switch. [CHAR LIMIT=NONE] -->
<string name="magnification_mode_switch_click_label">Switch</string>
<!-- Label of the corner of a rectangle that you can tap and drag to resize the magnification area. [CHAR LIMIT=NONE] -->
<string name="magnification_drag_corner_to_resize">Drag corner to resize</string>
<!-- Title of the magnification option button allow diagonal scrolling [CHAR LIMIT=NONE]-->
<string name="accessibility_allow_diagonal_scrolling">Allow diagonal scrolling</string>

View File

@@ -158,6 +158,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private View mTopDrag;
private View mRightDrag;
private View mBottomDrag;
private ImageView mTopLeftCornerView;
private ImageView mTopRightCornerView;
private ImageView mBottomLeftCornerView;
private ImageView mBottomRightCornerView;
private final Configuration mConfiguration;
@NonNull
@@ -357,13 +361,15 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return false;
}
private void changeMagnificationSize(@MagnificationSize int index) {
@VisibleForTesting
void changeMagnificationSize(@MagnificationSize int index) {
final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
int size = (int) (initSize * MAGNIFICATION_SCALE_OPTIONS[index]);
setWindowSize(size, size);
}
private void setEditMagnifierSizeMode(boolean enable) {
@VisibleForTesting
void setEditMagnifierSizeMode(boolean enable) {
mEditSizeEnable = enable;
applyResourcesValues();
@@ -639,10 +645,37 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize,
mMirrorView.getWidth() - mBorderDragSize,
mMirrorView.getHeight() - mBorderDragSize);
Region tapExcludeRegion = new Region();
Rect dragArea = new Rect();
mDragView.getHitRect(dragArea);
regionInsideDragBorder.op(dragArea, Region.Op.DIFFERENCE);
Rect topLeftArea = new Rect();
mTopLeftCornerView.getHitRect(topLeftArea);
Rect topRightArea = new Rect();
mTopRightCornerView.getHitRect(topRightArea);
Rect bottomLeftArea = new Rect();
mBottomLeftCornerView.getHitRect(bottomLeftArea);
Rect bottomRightArea = new Rect();
mBottomRightCornerView.getHitRect(bottomRightArea);
Rect closeArea = new Rect();
mCloseView.getHitRect(closeArea);
// add tapExcludeRegion for Drag or close
tapExcludeRegion.op(dragArea, Region.Op.UNION);
tapExcludeRegion.op(topLeftArea, Region.Op.UNION);
tapExcludeRegion.op(topRightArea, Region.Op.UNION);
tapExcludeRegion.op(bottomLeftArea, Region.Op.UNION);
tapExcludeRegion.op(bottomRightArea, Region.Op.UNION);
tapExcludeRegion.op(closeArea, Region.Op.UNION);
regionInsideDragBorder.op(tapExcludeRegion, Region.Op.DIFFERENCE);
return regionInsideDragBorder;
}
@@ -756,6 +789,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mRightDrag = mMirrorView.findViewById(R.id.right_handle);
mBottomDrag = mMirrorView.findViewById(R.id.bottom_handle);
mCloseView = mMirrorView.findViewById(R.id.close_button);
mTopRightCornerView = mMirrorView.findViewById(R.id.top_right_corner);
mTopLeftCornerView = mMirrorView.findViewById(R.id.top_left_corner);
mBottomRightCornerView = mMirrorView.findViewById(R.id.bottom_right_corner);
mBottomLeftCornerView = mMirrorView.findViewById(R.id.bottom_left_corner);
mDragView.setOnTouchListener(this);
mLeftDrag.setOnTouchListener(this);
@@ -763,6 +800,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mRightDrag.setOnTouchListener(this);
mBottomDrag.setOnTouchListener(this);
mCloseView.setOnTouchListener(this);
mTopLeftCornerView.setOnTouchListener(this);
mTopRightCornerView.setOnTouchListener(this);
mBottomLeftCornerView.setOnTouchListener(this);
mBottomRightCornerView.setOnTouchListener(this);
}
/**
@@ -831,8 +872,16 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
@Override
public boolean onTouch(View v, MotionEvent event) {
if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
|| v == mBottomDrag || v == mCloseView) {
if (v == mDragView
|| v == mLeftDrag
|| v == mTopDrag
|| v == mRightDrag
|| v == mBottomDrag
|| v == mTopLeftCornerView
|| v == mTopRightCornerView
|| v == mBottomLeftCornerView
|| v == mBottomRightCornerView
|| v == mCloseView) {
return mGestureDetector.onTouch(v, event);
}
return false;
@@ -1195,7 +1244,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
@Override
public boolean onDrag(View view, float offsetX, float offsetY) {
if (mEditSizeEnable) {
changeWindowSize(view, offsetX, offsetY);
return changeWindowSize(view, offsetX, offsetY);
} else {
move((int) offsetX, (int) offsetY);
}
@@ -1220,13 +1269,47 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
if (mEditSizeEnable) {
mDragView.setVisibility(View.GONE);
mCloseView.setVisibility(View.VISIBLE);
mTopRightCornerView.setVisibility(View.VISIBLE);
mTopLeftCornerView.setVisibility(View.VISIBLE);
mBottomRightCornerView.setVisibility(View.VISIBLE);
mBottomLeftCornerView.setVisibility(View.VISIBLE);
} else {
mDragView.setVisibility(View.VISIBLE);
mCloseView.setVisibility(View.GONE);
mTopRightCornerView.setVisibility(View.GONE);
mTopLeftCornerView.setVisibility(View.GONE);
mBottomRightCornerView.setVisibility(View.GONE);
mBottomLeftCornerView.setVisibility(View.GONE);
}
}
public boolean changeWindowSize(View view, float offsetX, float offsetY) {
private boolean changeWindowSize(View view, float offsetX, float offsetY) {
if (view == mLeftDrag) {
changeMagnificationFrameSize(offsetX, 0, 0, 0);
} else if (view == mRightDrag) {
changeMagnificationFrameSize(0, 0, offsetX, 0);
} else if (view == mTopDrag) {
changeMagnificationFrameSize(0, offsetY, 0, 0);
} else if (view == mBottomDrag) {
changeMagnificationFrameSize(0, 0, 0, offsetY);
} else if (view == mTopLeftCornerView) {
changeMagnificationFrameSize(offsetX, offsetY, 0, 0);
} else if (view == mTopRightCornerView) {
changeMagnificationFrameSize(0, offsetY, offsetX, 0);
} else if (view == mBottomLeftCornerView) {
changeMagnificationFrameSize(offsetX, 0, 0, offsetY);
} else if (view == mBottomRightCornerView) {
changeMagnificationFrameSize(0, 0, offsetX, offsetY);
} else {
return false;
}
return true;
}
private void changeMagnificationFrameSize(
float leftOffset, float topOffset, float rightOffset,
float bottomOffset) {
boolean bRTL = isRTL(mContext);
final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
@@ -1236,54 +1319,26 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
Rect tempRect = new Rect();
tempRect.set(mMagnificationFrame);
if (view == mLeftDrag) {
if (bRTL) {
tempRect.right += offsetX;
if (tempRect.right > mWindowBounds.width()) {
return false;
}
} else {
tempRect.left += offsetX;
if (tempRect.left < 0) {
return false;
}
}
} else if (view == mRightDrag) {
if (bRTL) {
tempRect.left += offsetX;
if (tempRect.left < 0) {
return false;
}
} else {
tempRect.right += offsetX;
if (tempRect.right > mWindowBounds.width()) {
return false;
}
}
} else if (view == mTopDrag) {
tempRect.top += offsetY;
if (tempRect.top < 0) {
return false;
}
} else if (view == mBottomDrag) {
tempRect.bottom += offsetY;
if (tempRect.bottom > mWindowBounds.height()) {
return false;
}
if (bRTL) {
tempRect.left += (int) (rightOffset);
tempRect.right += (int) (leftOffset);
} else {
tempRect.right += (int) (rightOffset);
tempRect.left += (int) (leftOffset);
}
tempRect.top += (int) (topOffset);
tempRect.bottom += (int) (bottomOffset);
if (tempRect.width() < initSize || tempRect.height() < initSize
|| tempRect.width() > maxWidthSize || tempRect.height() > maxHeightSize) {
return false;
return;
}
mMagnificationFrame.set(tempRect);
computeBounceAnimationScale();
calculateMagnificationFrameBoundary();
modifyWindowMagnification(true);
return true;
}
private static boolean isRTL(Context context) {

View File

@@ -103,7 +103,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
MagnificationSize.LARGE,
})
/** Denotes the Magnification size type. */
@interface MagnificationSize {
public @interface MagnificationSize {
int NONE = 0;
int SMALL = 1;
int MEDIUM = 2;

View File

@@ -902,6 +902,97 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
assertEquals(bounds.width(), actualWindowWidth.get());
}
@Test
public void changeMagnificationSize_expectedWindowSize() {
final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
final float magnificationScaleLarge = 2.5f;
final int initSize = Math.min(bounds.width(), bounds.height()) / 3;
final int magnificationSize = (int) (initSize * magnificationScaleLarge);
final int expectedWindowHeight = magnificationSize;
final int expectedWindowWidth = magnificationSize;
mInstrumentation.runOnMainSync(
() ->
mWindowMagnificationController.enableWindowMagnificationInternal(
Float.NaN, Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
final AtomicInteger actualWindowWidth = new AtomicInteger();
mInstrumentation.runOnMainSync(
() -> {
mWindowMagnificationController.changeMagnificationSize(
WindowMagnificationSettings.MagnificationSize.LARGE);
actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
});
assertEquals(expectedWindowHeight, actualWindowHeight.get());
assertEquals(expectedWindowWidth, actualWindowWidth.get());
}
@Test
public void editModeOnDragCorner_resizesWindow() {
final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
final int startingSize = (int) (bounds.width() / 2);
mInstrumentation.runOnMainSync(
() ->
mWindowMagnificationController.enableWindowMagnificationInternal(
Float.NaN, Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
final AtomicInteger actualWindowWidth = new AtomicInteger();
mInstrumentation.runOnMainSync(
() -> {
mWindowMagnificationController.setWindowSize(startingSize, startingSize);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
});
waitForIdleSync();
mInstrumentation.runOnMainSync(
() -> {
mWindowMagnificationController
.onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f);
actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
});
assertEquals(startingSize + 1, actualWindowHeight.get());
assertEquals(startingSize + 2, actualWindowWidth.get());
}
@Test
public void editModeOnDragEdge_resizesWindowInOnlyOneDirection() {
final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
final int startingSize = (int) (bounds.width() / 2f);
mInstrumentation.runOnMainSync(
() ->
mWindowMagnificationController.enableWindowMagnificationInternal(
Float.NaN, Float.NaN, Float.NaN));
final AtomicInteger actualWindowHeight = new AtomicInteger();
final AtomicInteger actualWindowWidth = new AtomicInteger();
mInstrumentation.runOnMainSync(
() -> {
mWindowMagnificationController.setWindowSize(startingSize, startingSize);
mWindowMagnificationController.setEditMagnifierSizeMode(true);
mWindowMagnificationController
.onDrag(getInternalView(R.id.bottom_handle), 2f, 1f);
actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
});
assertEquals(startingSize + 1, actualWindowHeight.get());
assertEquals(startingSize, actualWindowWidth.get());
}
@Test
public void setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen() {