diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fba62691c951b..bed7442d576e5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -245,10 +245,14 @@
Dismiss screenshot
Screenshot preview
-
- Top boundary
-
- Bottom boundary
+
+ Top boundary %1$d percent
+
+ Bottom boundary %1$d percent
+
+ Left boundary %1$d percent
+
+ Right boundary %1$d percent
Screen Recorder
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index 78ee896075703..bbce7b09e9b67 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -28,21 +28,26 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
-import android.util.IntArray;
import android.util.Log;
import android.util.MathUtils;
import android.util.Range;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.SeekBar;
import androidx.annotation.Nullable;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.customview.widget.ExploreByTouchHelper;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
-import com.android.internal.widget.ExploreByTouchHelper;
import com.android.systemui.R;
+import java.util.List;
+
/**
* CropView has top and bottom draggable crop handles, with a scrim to darken the areas being
* cropped out.
@@ -74,6 +79,7 @@ public class CropView extends View {
private Range mMotionRange;
private CropInteractionListener mCropInteractionListener;
+ private final ExploreByTouchHelper mExploreByTouchHelper;
public CropView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
@@ -94,7 +100,8 @@ public class CropView extends View {
// 48 dp touchable region around each handle.
mCropTouchMargin = 24 * getResources().getDisplayMetrics().density;
- setAccessibilityDelegate(new AccessibilityHelper());
+ mExploreByTouchHelper = new AccessibilityHelper();
+ ViewCompat.setAccessibilityDelegate(this, mExploreByTouchHelper);
}
@Override
@@ -141,28 +148,7 @@ public class CropView extends View {
mStartingX = event.getX();
mMovementStartValue = getBoundaryPosition(mCurrentDraggingBoundary);
updateListener(event);
- switch (mCurrentDraggingBoundary) {
- case TOP:
- mMotionRange = new Range<>(0f,
- mCrop.bottom - pixelDistanceToFraction(mCropTouchMargin,
- CropBoundary.BOTTOM));
- break;
- case BOTTOM:
- mMotionRange = new Range<>(
- mCrop.top + pixelDistanceToFraction(mCropTouchMargin,
- CropBoundary.TOP), 1f);
- break;
- case LEFT:
- mMotionRange = new Range<>(0f,
- mCrop.right - pixelDistanceToFraction(mCropTouchMargin,
- CropBoundary.RIGHT));
- break;
- case RIGHT:
- mMotionRange = new Range<>(
- mCrop.left + pixelDistanceToFraction(mCropTouchMargin,
- CropBoundary.LEFT), 1f);
- break;
- }
+ mMotionRange = getAllowedValues(mCurrentDraggingBoundary);
}
return true;
case MotionEvent.ACTION_MOVE:
@@ -185,10 +171,30 @@ public class CropView extends View {
return super.onTouchEvent(event);
}
+ @Override
+ public boolean dispatchHoverEvent(MotionEvent event) {
+ return mExploreByTouchHelper.dispatchHoverEvent(event)
+ || super.dispatchHoverEvent(event);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return mExploreByTouchHelper.dispatchKeyEvent(event)
+ || super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public void onFocusChanged(boolean gainFocus, int direction,
+ Rect previouslyFocusedRect) {
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ mExploreByTouchHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ }
+
/**
* Set the given boundary to the given value without animation.
*/
public void setBoundaryPosition(CropBoundary boundary, float position) {
+ position = (float) getAllowedValues(boundary).clamp(position);
switch (boundary) {
case TOP:
mCrop.top = position;
@@ -280,6 +286,28 @@ public class CropView extends View {
mCropInteractionListener = listener;
}
+ private Range getAllowedValues(CropBoundary boundary) {
+ switch (boundary) {
+ case TOP:
+ return new Range<>(0f,
+ mCrop.bottom - pixelDistanceToFraction(mCropTouchMargin,
+ CropBoundary.BOTTOM));
+ case BOTTOM:
+ return new Range<>(
+ mCrop.top + pixelDistanceToFraction(mCropTouchMargin,
+ CropBoundary.TOP), 1f);
+ case LEFT:
+ return new Range<>(0f,
+ mCrop.right - pixelDistanceToFraction(mCropTouchMargin,
+ CropBoundary.RIGHT));
+ case RIGHT:
+ return new Range<>(
+ mCrop.left + pixelDistanceToFraction(mCropTouchMargin,
+ CropBoundary.LEFT), 1f);
+ }
+ return null;
+ }
+
private void updateListener(MotionEvent event) {
if (mCropInteractionListener != null && (isVertical(mCurrentDraggingBoundary))) {
float boundaryPosition = getBoundaryPosition(mCurrentDraggingBoundary);
@@ -371,6 +399,8 @@ public class CropView extends View {
private static final int TOP_HANDLE_ID = 1;
private static final int BOTTOM_HANDLE_ID = 2;
+ private static final int LEFT_HANDLE_ID = 3;
+ private static final int RIGHT_HANDLE_ID = 4;
AccessibilityHelper() {
super(CropView.this);
@@ -384,62 +414,125 @@ public class CropView extends View {
if (Math.abs(y - fractionToVerticalPixels(mCrop.bottom)) < mCropTouchMargin) {
return BOTTOM_HANDLE_ID;
}
- return ExploreByTouchHelper.INVALID_ID;
+ if (y > fractionToVerticalPixels(mCrop.top)
+ && y < fractionToVerticalPixels(mCrop.bottom)) {
+ if (Math.abs(x - fractionToHorizontalPixels(mCrop.left)) < mCropTouchMargin) {
+ return LEFT_HANDLE_ID;
+ }
+ if (Math.abs(x - fractionToHorizontalPixels(mCrop.right)) < mCropTouchMargin) {
+ return RIGHT_HANDLE_ID;
+ }
+ }
+
+ return ExploreByTouchHelper.HOST_ID;
}
@Override
- protected void getVisibleVirtualViews(IntArray virtualViewIds) {
+ protected void getVisibleVirtualViews(List virtualViewIds) {
+ // Add views in traversal order
virtualViewIds.add(TOP_HANDLE_ID);
+ virtualViewIds.add(LEFT_HANDLE_ID);
+ virtualViewIds.add(RIGHT_HANDLE_ID);
virtualViewIds.add(BOTTOM_HANDLE_ID);
}
@Override
protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
- switch (virtualViewId) {
- case TOP_HANDLE_ID:
- event.setContentDescription(
- getResources().getString(R.string.screenshot_top_boundary));
- break;
- case BOTTOM_HANDLE_ID:
- event.setContentDescription(
- getResources().getString(R.string.screenshot_bottom_boundary));
- break;
- }
+ CropBoundary boundary = viewIdToBoundary(virtualViewId);
+ event.setContentDescription(getBoundaryContentDescription(boundary));
}
@Override
protected void onPopulateNodeForVirtualView(int virtualViewId,
- AccessibilityNodeInfo node) {
- switch (virtualViewId) {
- case TOP_HANDLE_ID:
- node.setContentDescription(
- getResources().getString(R.string.screenshot_top_boundary));
- setNodePositions(mCrop.top, node);
- break;
- case BOTTOM_HANDLE_ID:
- node.setContentDescription(
- getResources().getString(R.string.screenshot_bottom_boundary));
- setNodePositions(mCrop.bottom, node);
- break;
- }
+ AccessibilityNodeInfoCompat node) {
+ CropBoundary boundary = viewIdToBoundary(virtualViewId);
+ node.setContentDescription(getBoundaryContentDescription(boundary));
+ setNodePosition(getNodeRect(boundary), node);
- // TODO: need to figure out the full set of actions to support here.
- node.addAction(
- AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
- node.setClickable(true);
- node.setFocusable(true);
+ // Intentionally set the class name to SeekBar so that TalkBack uses volume control to
+ // scroll.
+ node.setClassName(SeekBar.class.getName());
+ node.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
+ node.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
}
@Override
protected boolean onPerformActionForVirtualView(
int virtualViewId, int action, Bundle arguments) {
- return false;
+ if (action != AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
+ && action != AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+ return false;
+ }
+ CropBoundary boundary = viewIdToBoundary(virtualViewId);
+ float delta = pixelDistanceToFraction(mCropTouchMargin, boundary);
+ if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
+ delta = -delta;
+ }
+ setBoundaryPosition(boundary, delta + getBoundaryPosition(boundary));
+ invalidateVirtualView(virtualViewId);
+ sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_SELECTED);
+ return true;
}
- private void setNodePositions(float fraction, AccessibilityNodeInfo node) {
- int pixels = fractionToVerticalPixels(fraction);
- Rect rect = new Rect(0, (int) (pixels - mCropTouchMargin),
- getWidth(), (int) (pixels + mCropTouchMargin));
+ private CharSequence getBoundaryContentDescription(CropBoundary boundary) {
+ int template;
+ switch (boundary) {
+ case TOP:
+ template = R.string.screenshot_top_boundary_pct;
+ break;
+ case BOTTOM:
+ template = R.string.screenshot_bottom_boundary_pct;
+ break;
+ case LEFT:
+ template = R.string.screenshot_left_boundary_pct;
+ break;
+ case RIGHT:
+ template = R.string.screenshot_right_boundary_pct;
+ break;
+ default:
+ return "";
+ }
+
+ return getResources().getString(template,
+ Math.round(getBoundaryPosition(boundary) * 100));
+ }
+
+ private CropBoundary viewIdToBoundary(int viewId) {
+ switch (viewId) {
+ case TOP_HANDLE_ID:
+ return CropBoundary.TOP;
+ case BOTTOM_HANDLE_ID:
+ return CropBoundary.BOTTOM;
+ case LEFT_HANDLE_ID:
+ return CropBoundary.LEFT;
+ case RIGHT_HANDLE_ID:
+ return CropBoundary.RIGHT;
+ }
+ return CropBoundary.NONE;
+ }
+
+ private Rect getNodeRect(CropBoundary boundary) {
+ Rect rect;
+ if (isVertical(boundary)) {
+ int pixels = fractionToVerticalPixels(getBoundaryPosition(boundary));
+ rect = new Rect(0, (int) (pixels - mCropTouchMargin),
+ getWidth(), (int) (pixels + mCropTouchMargin));
+ // Top boundary can sometimes go beyond the view, shift it down to compensate so
+ // the area is big enough.
+ if (rect.top < 0) {
+ rect.offset(0, -rect.top);
+ }
+ } else {
+ int pixels = fractionToHorizontalPixels(getBoundaryPosition(boundary));
+ rect = new Rect((int) (pixels - mCropTouchMargin),
+ (int) (fractionToVerticalPixels(mCrop.top) + mCropTouchMargin),
+ (int) (pixels + mCropTouchMargin),
+ (int) (fractionToVerticalPixels(mCrop.bottom) - mCropTouchMargin));
+ }
+ return rect;
+ }
+
+ private void setNodePosition(Rect rect, AccessibilityNodeInfoCompat node) {
node.setBoundsInParent(rect);
int[] pos = new int[2];
getLocationOnScreen(pos);