summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xapi/current.txt2
-rw-r--r--core/java/android/widget/Magnifier.java173
-rw-r--r--core/res/res/values/attrs.xml1
-rw-r--r--core/res/res/values/colors.xml4
-rw-r--r--core/res/res/values/styles.xml1
-rw-r--r--core/res/res/values/symbols.xml2
6 files changed, 166 insertions, 17 deletions
diff --git a/api/current.txt b/api/current.txt
index f22856c58fbe..d083a31e435b 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -54341,6 +54341,7 @@ package android.widget {
method public int getDefaultVerticalSourceToMagnifierOffset();
method public float getElevation();
method public int getHeight();
+ method public android.graphics.drawable.Drawable getOverlay();
method public android.graphics.Point getPosition();
method public int getSourceHeight();
method public android.graphics.Point getSourcePosition();
@@ -54364,6 +54365,7 @@ package android.widget {
method public android.widget.Magnifier.Builder setDefaultSourceToMagnifierOffset(int, int);
method public android.widget.Magnifier.Builder setElevation(float);
method public android.widget.Magnifier.Builder setForcePositionWithinWindowSystemInsetsBounds(boolean);
+ method public android.widget.Magnifier.Builder setOverlay(android.graphics.drawable.Drawable);
method public android.widget.Magnifier.Builder setSize(int, int);
method public android.widget.Magnifier.Builder setSourceBounds(int, int, int, int);
method public android.widget.Magnifier.Builder setZoom(float);
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 7756a19d847f..932f182891a5 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -28,6 +28,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Outline;
@@ -38,6 +39,8 @@ import android.graphics.PointF;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderNode;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
@@ -95,6 +98,8 @@ public final class Magnifier {
private final float mWindowElevation;
// The corner radius of the window containing the magnifier.
private final float mWindowCornerRadius;
+ // The overlay to be drawn on the top of the magnifier content.
+ private final Drawable mOverlay;
// The horizontal offset between the source and window coords when #show(float, float) is used.
private final int mDefaultHorizontalSourceToMagnifierOffset;
// The vertical offset between the source and window coords when #show(float, float) is used.
@@ -153,6 +158,7 @@ public final class Magnifier {
mSourceHeight = Math.round(mWindowHeight / mZoom);
mWindowElevation = params.mElevation;
mWindowCornerRadius = params.mCornerRadius;
+ mOverlay = params.mOverlay;
mDefaultHorizontalSourceToMagnifierOffset =
params.mHorizontalDefaultSourceToMagnifierOffset;
mDefaultVerticalSourceToMagnifierOffset =
@@ -225,8 +231,9 @@ public final class Magnifier {
if (mWindow == null) {
synchronized (mLock) {
mWindow = new InternalPopupWindow(mView.getContext(), mView.getDisplay(),
- mParentSurface.mSurface,
- mWindowWidth, mWindowHeight, mWindowElevation, mWindowCornerRadius,
+ mParentSurface.mSurface, mWindowWidth, mWindowHeight,
+ mWindowElevation, mWindowCornerRadius,
+ mOverlay != null ? mOverlay : new ColorDrawable(Color.TRANSPARENT),
Handler.getMain() /* draw the magnifier on the UI thread */, mLock,
mCallback);
}
@@ -400,6 +407,17 @@ public final class Magnifier {
}
/**
+ * Returns the overlay to be drawn on the top of the magnifier content, or
+ * {@code null} if no overlay should be drawn.
+ * @return the overlay
+ * @see Magnifier.Builder#setOverlay(Drawable)
+ */
+ @Nullable
+ public Drawable getOverlay() {
+ return mOverlay;
+ }
+
+ /**
* Returns whether the magnifier position will be adjusted such that the magnifier will be
* fully within the bounds of the main application window, by also avoiding any overlap with
* system insets (such as the one corresponding to the status bar).
@@ -698,9 +716,6 @@ public final class Magnifier {
* producing a shakiness effect for the magnifier content.
*/
private static class InternalPopupWindow {
- // The alpha set on the magnifier's content, which defines how
- // prominent the white background is.
- private static final int CONTENT_BITMAP_ALPHA = 242;
// The z of the magnifier surface, defining its z order in the list of
// siblings having the same parent surface (usually the main app surface).
private static final int SURFACE_Z = 5;
@@ -716,6 +731,8 @@ public final class Magnifier {
// The insets of the content inside the allocated surface.
private final int mOffsetX;
private final int mOffsetY;
+ // The overlay to be drawn on the top of the content.
+ private final Drawable mOverlay;
// The surface we allocate for the magnifier content + shadow.
private final SurfaceSession mSurfaceSession;
private final SurfaceControl mSurfaceControl;
@@ -724,6 +741,8 @@ public final class Magnifier {
private final ThreadedRenderer.SimpleRenderer mRenderer;
// The RenderNode used to draw the magnifier content in the surface.
private final RenderNode mBitmapRenderNode;
+ // The RenderNode used to draw the overlay over the magnifier content.
+ private final RenderNode mOverlayRenderNode;
// The job that will be post'd to apply the pending magnifier updates to the surface.
private final Runnable mMagnifierUpdater;
// The handler where the magnifier updater jobs will be post'd.
@@ -740,7 +759,7 @@ public final class Magnifier {
private final Object mLock;
// Whether a magnifier frame draw is currently pending in the UI thread queue.
private boolean mFrameDrawScheduled;
- // The content bitmap.
+ // The content bitmap, as returned by pixel copy.
private Bitmap mBitmap;
// Whether the next draw will be the first one for the current instance.
private boolean mFirstDraw = true;
@@ -756,11 +775,15 @@ public final class Magnifier {
// mDestroyLock should be acquired before mLock in order to avoid deadlocks.
private final Object mDestroyLock = new Object();
+ // The current content of the magnifier. It is mBitmap + mOverlay, only used for testing.
+ private Bitmap mCurrentContent;
+
InternalPopupWindow(final Context context, final Display display,
- final Surface parentSurface,
- final int width, final int height, final float elevation, final float cornerRadius,
+ final Surface parentSurface, final int width, final int height,
+ final float elevation, final float cornerRadius, final Drawable overlay,
final Handler handler, final Object lock, final Callback callback) {
mDisplay = display;
+ mOverlay = overlay;
mLock = lock;
mCallback = callback;
@@ -781,7 +804,9 @@ public final class Magnifier {
mSurface = new Surface();
mSurface.copyFrom(mSurfaceControl);
- // Setup the RenderNode tree. The root has only one child, which contains the bitmap.
+ // Setup the RenderNode tree. The root has two children, one containing the bitmap
+ // and one containing the overlay. We use a separate render node for the overlay
+ // to avoid drawing this as the same rate we do for content.
mRenderer = new ThreadedRenderer.SimpleRenderer(
context,
"magnifier renderer",
@@ -792,15 +817,27 @@ public final class Magnifier {
elevation,
cornerRadius
);
+ mOverlayRenderNode = createRenderNodeForOverlay(
+ "magnifier overlay",
+ cornerRadius
+ );
+ setupOverlay();
final RecordingCanvas canvas = mRenderer.getRootNode().start(width, height);
try {
canvas.insertReorderBarrier();
canvas.drawRenderNode(mBitmapRenderNode);
canvas.insertInorderBarrier();
+ canvas.drawRenderNode(mOverlayRenderNode);
+ canvas.insertInorderBarrier();
} finally {
mRenderer.getRootNode().end(canvas);
}
+ if (mCallback != null) {
+ mCurrentContent =
+ Bitmap.createBitmap(mContentWidth, mContentHeight, Bitmap.Config.ARGB_8888);
+ updateCurrentContentForTesting();
+ }
// Initialize the update job and the handler where this will be post'd.
mHandler = handler;
@@ -835,6 +872,61 @@ public final class Magnifier {
return bitmapRenderNode;
}
+ private RenderNode createRenderNodeForOverlay(final String name, final float cornerRadius) {
+ final RenderNode overlayRenderNode = RenderNode.create(name, null);
+
+ // Define the position of the overlay in the parent render node.
+ // This coincides with the position of the content.
+ overlayRenderNode.setLeftTopRightBottom(mOffsetX, mOffsetY,
+ mOffsetX + mContentWidth, mOffsetY + mContentHeight);
+
+ final Outline outline = new Outline();
+ outline.setRoundRect(0, 0, mContentWidth, mContentHeight, cornerRadius);
+ outline.setAlpha(1.0f);
+ overlayRenderNode.setOutline(outline);
+ overlayRenderNode.setClipToOutline(true);
+
+ return overlayRenderNode;
+ }
+
+ private void setupOverlay() {
+ drawOverlay();
+
+ mOverlay.setCallback(new Drawable.Callback() {
+ @Override
+ public void invalidateDrawable(Drawable who) {
+ // When the overlay drawable is invalidated, redraw it to the render node.
+ drawOverlay();
+ if (mCallback != null) {
+ updateCurrentContentForTesting();
+ }
+ }
+
+ @Override
+ public void scheduleDrawable(Drawable who, Runnable what, long when) {
+ Handler.getMain().postAtTime(what, who, when);
+ }
+
+ @Override
+ public void unscheduleDrawable(Drawable who, Runnable what) {
+ Handler.getMain().removeCallbacks(what, who);
+ }
+ });
+ }
+
+ private void drawOverlay() {
+ // Draw the drawable to the render node. This happens once during
+ // initialization and whenever the overlay drawable is invalidated.
+ final RecordingCanvas canvas =
+ mOverlayRenderNode.startRecording(mContentWidth, mContentHeight);
+ try {
+ mOverlay.setBounds(0, 0, mContentWidth, mContentHeight);
+ mOverlay.draw(canvas);
+ } finally {
+ mOverlayRenderNode.endRecording();
+ }
+ }
+
/**
* Sets the position of the magnifier content relative to the parent surface.
* The position update will happen in the same frame with the next draw.
@@ -909,13 +1001,10 @@ public final class Magnifier {
final RecordingCanvas canvas =
mBitmapRenderNode.start(mContentWidth, mContentHeight);
try {
- canvas.drawColor(Color.WHITE);
-
final Rect srcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
final Rect dstRect = new Rect(0, 0, mContentWidth, mContentHeight);
final Paint paint = new Paint();
paint.setFilterBitmap(true);
- paint.setAlpha(CONTENT_BITMAP_ALPHA);
canvas.drawBitmap(mBitmap, srcRect, dstRect, paint);
} finally {
mBitmapRenderNode.end(canvas);
@@ -962,9 +1051,29 @@ public final class Magnifier {
mRenderer.draw(callback);
if (mCallback != null) {
+ // The current content bitmap is only used in testing, so, for performance,
+ // we only want to update it when running tests. For this, we check that
+ // mCallback is not null, as it can only be set from a @TestApi.
+ updateCurrentContentForTesting();
mCallback.onOperationComplete();
}
}
+
+ /**
+ * Updates mCurrentContent, which reproduces what is currently supposed to be
+ * drawn in the magnifier. mCurrentContent is only used for testing, so this method
+ * should only be called otherwise.
+ */
+ private void updateCurrentContentForTesting() {
+ final Canvas canvas = new Canvas(mCurrentContent);
+ final Rect bounds = new Rect(0, 0, mContentWidth, mContentHeight);
+ if (mBitmap != null && !mBitmap.isRecycled()) {
+ final Rect originalBounds = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+ canvas.drawBitmap(mBitmap, originalBounds, bounds, null);
+ }
+ mOverlay.setBounds(bounds);
+ mOverlay.draw(canvas);
+ }
}
/**
@@ -977,6 +1086,7 @@ public final class Magnifier {
private float mZoom;
private @FloatRange(from = 0f) float mElevation;
private @FloatRange(from = 0f) float mCornerRadius;
+ private @Nullable Drawable mOverlay;
private int mHorizontalDefaultSourceToMagnifierOffset;
private int mVerticalDefaultSourceToMagnifierOffset;
private boolean mForcePositionWithinWindowSystemInsetsBounds;
@@ -1007,6 +1117,8 @@ public final class Magnifier {
a.getDimensionPixelSize(R.styleable.Magnifier_magnifierHorizontalOffset, 0);
mVerticalDefaultSourceToMagnifierOffset =
a.getDimensionPixelSize(R.styleable.Magnifier_magnifierVerticalOffset, 0);
+ mOverlay = new ColorDrawable(a.getColor(
+ R.styleable.Magnifier_magnifierColorOverlay, Color.TRANSPARENT));
a.recycle();
mForcePositionWithinWindowSystemInsetsBounds = true;
mLeftContentBound = SOURCE_BOUND_MAX_VISIBLE;
@@ -1037,6 +1149,7 @@ public final class Magnifier {
* @param width the window width to be set
* @param height the window height to be set
*/
+ @NonNull
public Builder setSize(@Px @IntRange(from = 0) int width,
@Px @IntRange(from = 0) int height) {
Preconditions.checkArgumentPositive(width, "Width should be positive");
@@ -1054,6 +1167,7 @@ public final class Magnifier {
* be just copied to the magnifier with no scaling). The zoom defaults to 1.25.
* @param zoom the zoom to be set
*/
+ @NonNull
public Builder setZoom(@FloatRange(from = 0f) float zoom) {
Preconditions.checkArgumentPositive(zoom, "Zoom should be positive");
mZoom = zoom;
@@ -1064,6 +1178,7 @@ public final class Magnifier {
* Sets the elevation of the magnifier window, in pixels. Defaults to 4dp.
* @param elevation the elevation to be set
*/
+ @NonNull
public Builder setElevation(@Px @FloatRange(from = 0) float elevation) {
Preconditions.checkArgumentNonNegative(elevation, "Elevation should be non-negative");
mElevation = elevation;
@@ -1075,6 +1190,7 @@ public final class Magnifier {
* Defaults to the corner radius defined in the device default theme.
* @param cornerRadius the corner radius to be set
*/
+ @NonNull
public Builder setCornerRadius(@Px @FloatRange(from = 0) float cornerRadius) {
Preconditions.checkArgumentNonNegative(cornerRadius,
"Corner radius should be non-negative");
@@ -1083,13 +1199,32 @@ public final class Magnifier {
}
/**
- * Sets an offset, in pixels, that should be added to the content source center to obtain
+ * Sets an overlay that will be drawn on the top of the magnifier content.
+ * In general, the overlay should not be opaque, in order to let the expected magnifier
+ * content be partially visible. The default overlay is a white {@link ColorDrawable},
+ * with 5% alpha, aiming to make the magnifier distinguishable when shown in dark
+ * application regions. To disable this default (or in general to have no overlay), the
+ * parameter should be set to {@code null}. The overlay will be automatically redrawn
+ * when the drawable is invalidated. To achieve this, the magnifier will set a new
+ * {@link android.graphics.drawable.Drawable.Callback} for the overlay drawable,
+ * so keep in mind that any existing one set by the application will be lost.
+ * @param overlay the overlay to be drawn on top
+ */
+ @NonNull
+ public Builder setOverlay(@Nullable Drawable overlay) {
+ mOverlay = overlay;
+ return this;
+ }
+
+ /**
+ * Sets an offset that should be added to the content source center to obtain
* the position of the magnifier window, when the {@link #show(float, float)}
* method is called. The offset is ignored when {@link #show(float, float, float, float)}
* is used. The offset can be negative, and it defaults to (0dp, -42dp).
* @param horizontalOffset the horizontal component of the offset
* @param verticalOffset the vertical component of the offset
*/
+ @NonNull
public Builder setDefaultSourceToMagnifierOffset(@Px int horizontalOffset,
@Px int verticalOffset) {
mHorizontalDefaultSourceToMagnifierOffset = horizontalOffset;
@@ -1114,6 +1249,7 @@ public final class Magnifier {
* </ul>
* @param force whether the magnifier position will be adjusted
*/
+ @NonNull
public Builder setForcePositionWithinWindowSystemInsetsBounds(boolean force) {
mForcePositionWithinWindowSystemInsetsBounds = force;
return this;
@@ -1156,6 +1292,7 @@ public final class Magnifier {
* @param right the right bound for content copy
* @param bottom the bottom bound for content copy
*/
+ @NonNull
public Builder setSourceBounds(@SourceBound int left, @SourceBound int top,
@SourceBound int right, @SourceBound int bottom) {
mLeftContentBound = left;
@@ -1205,7 +1342,7 @@ public final class Magnifier {
@Retention(RetentionPolicy.SOURCE)
public @interface SourceBound {}
- // The rest of the file consists of test APIs.
+ // The rest of the file consists of test APIs and methods relevant for tests.
/**
* See {@link #setOnOperationCompleteCallback(Callback)}.
@@ -1228,7 +1365,7 @@ public final class Magnifier {
}
/**
- * @return the content being currently displayed in the magnifier, as bitmap
+ * @return the drawing being currently displayed in the magnifier, as bitmap
*
* @hide
*/
@@ -1238,12 +1375,14 @@ public final class Magnifier {
return null;
}
synchronized (mWindow.mLock) {
- return Bitmap.createScaledBitmap(mWindow.mBitmap, mWindowWidth, mWindowHeight, true);
+ return mWindow.mCurrentContent;
}
}
/**
- * @return the content to be magnified, as bitmap
+ * Returns a bitmap containing the content that was magnified and drew to the
+ * magnifier, at its original size, without the overlay applied.
+ * @return the content that is magnified, as bitmap
*
* @hide
*/
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a99b9421e3b3..605662a91db2 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -9001,5 +9001,6 @@
<attr name="magnifierElevation" format="dimension" />
<attr name="magnifierVerticalOffset" format="dimension" />
<attr name="magnifierHorizontalOffset" format="dimension" />
+ <attr name="magnifierColorOverlay" format="color" />
</declare-styleable>
</resources>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 79a7b903f82f..ffcd30030271 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -209,4 +209,8 @@
<!-- FloatingToolbar -->
<color name="floating_popup_divider_dark">#2F2F2F</color>
<color name="floating_popup_divider_light">#E9E9E9</color>
+
+ <!-- Magnifier -->
+ <color name="magnifier_color_overlay">#0EFFFFFF</color>
+
</resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index ef286e2037bf..ec22f42e14ef 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -806,6 +806,7 @@ please see styles_device_defaults.xml.
<item name="magnifierElevation">@dimen/magnifier_elevation</item>
<item name="magnifierVerticalOffset">@dimen/magnifier_vertical_offset</item>
<item name="magnifierHorizontalOffset">@dimen/magnifier_horizontal_offset</item>
+ <item name="magnifierColorOverlay">@color/magnifier_color_overlay</item>
</style>
<!-- Text Appearances -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4eb723eb973d..69c4abc44827 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2643,12 +2643,14 @@
<java-symbol type="dimen" name="magnifier_zoom" />
<java-symbol type="dimen" name="magnifier_vertical_offset" />
<java-symbol type="dimen" name="magnifier_horizontal_offset" />
+ <java-symbol type="color" name="magnifier_color_overlay" />
<java-symbol type="attr" name="magnifierWidth" />
<java-symbol type="attr" name="magnifierHeight" />
<java-symbol type="attr" name="magnifierElevation" />
<java-symbol type="attr" name="magnifierZoom" />
<java-symbol type="attr" name="magnifierVerticalOffset" />
<java-symbol type="attr" name="magnifierHorizontalOffset" />
+ <java-symbol type="attr" name="magnifierColorOverlay" />
<java-symbol type="attr" name="magnifierStyle" />
<java-symbol type="string" name="date_picker_prev_month_button" />