Bug 1780093 - Use custom SurfaceView which allows magnifier widget to work. r=geckoview-reviewers,owlish

The android magnifier widget does not work when using our
SurfaceControl rendering path, as we no longer render in to the
Surface provided by the SurfaceView, but instead into a child Surface
we have created and attached the SurfaceControl.

To fix this, we create a SurfaceView subclass, MagnifiableSurfaceView,
which allows us to set an override Surface to be used by the
magnifier. This class works by overriding getHolder() to return a
custom SurfaceHolder, which returns our override Surface rather than
the default one when called by the Magnifier class.

Depends on D157308

Differential Revision: https://phabricator.services.mozilla.com/D157309
This commit is contained in:
Jamie Nicol 2022-09-21 17:30:26 +00:00
parent 5e60f35505
commit 49c0f0ed43
4 changed files with 165 additions and 3 deletions

View File

@ -0,0 +1,137 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* A {@link android.view.SurfaceView} which allows a {@link android.widget.Magnifier} widget to
* magnify a custom {@link android.view.Surface} rather than the SurfaceView's default Surface.
*/
public class MagnifiableSurfaceView extends SurfaceView {
private static final String LOGTAG = "MagnifiableSurfaceView";
private SurfaceHolderWrapper mHolder;
public MagnifiableSurfaceView(final Context context) {
super(context);
}
@Override
public SurfaceHolder getHolder() {
if (mHolder != null) {
// Only return our custom holder if we are being called from the Magnifier class.
// Throwable.getStackTrace() is faster than Thread.getStackTrace(), but still has a cost,
// hence why we only check the caller if we have set an override Surface.
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
if (stackTrace.length >= 2
&& stackTrace[1].getClassName().equals("android.widget.Magnifier")) {
return mHolder;
}
}
return super.getHolder();
}
/**
* Sets the Surface that should be magnified by a Magnifier widget.
*
* <p>This should be set immediately before calling {@link android.widget.Magnifier#show()} or
* {@link android.widget.Magnifier#update()}, and unset immediately afterwards.
*
* @param surface The Surface to be magnified. If null, the SurfaceView's default Surface will be
* used.
*/
public void setMagnifierSurface(final Surface surface) {
if (surface != null) {
mHolder = new SurfaceHolderWrapper(getHolder(), surface);
} else {
mHolder = null;
}
}
/**
* A {@link android.view.SurfaceHolder} implementation that simply forwards all methods to a
* provided SurfaceHolder instance, except for getSurface() which returns a custom Surface.
*/
private class SurfaceHolderWrapper implements SurfaceHolder {
private final SurfaceHolder mHolder;
private final Surface mSurface;
public SurfaceHolderWrapper(final SurfaceHolder holder, final Surface surface) {
mHolder = holder;
mSurface = surface;
}
@Override
public void addCallback(final Callback callback) {
mHolder.addCallback(callback);
}
@Override
public void removeCallback(final Callback callback) {
mHolder.removeCallback(callback);
}
@Override
public boolean isCreating() {
return mHolder.isCreating();
}
@Override
public void setType(final int type) {
mHolder.setType(type);
}
@Override
public void setFixedSize(final int width, final int height) {
mHolder.setFixedSize(width, height);
}
@Override
public void setSizeFromLayout() {
mHolder.setSizeFromLayout();
}
@Override
public void setFormat(final int format) {
mHolder.setFormat(format);
}
@Override
public void setKeepScreenOn(final boolean screenOn) {
mHolder.setKeepScreenOn(screenOn);
}
@Override
public Canvas lockCanvas() {
return mHolder.lockCanvas();
}
@Override
public Canvas lockCanvas(final Rect dirty) {
return mHolder.lockCanvas(dirty);
}
@Override
public void unlockCanvasAndPost(final Canvas canvas) {
mHolder.unlockCanvasAndPost(canvas);
}
@Override
public Rect getSurfaceFrame() {
return mHolder.getSurfaceFrame();
}
@Override
public Surface getSurface() {
return mSurface;
}
}
}

View File

@ -36,7 +36,7 @@ public class SurfaceViewWrapper {
}
private void initSurfaceView(final Context context) {
mSurfaceView = new SurfaceView(context);
mSurfaceView = new MagnifiableSurfaceView(context);
mSurfaceView.setBackgroundColor(Color.TRANSPARENT);
mSurfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT);
mView = mSurfaceView;

View File

@ -28,6 +28,7 @@ import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import android.view.PointerIcon;
import android.view.Surface;
import android.view.View;
import android.view.ViewStructure;
import android.view.inputmethod.CursorAnchorInfo;
@ -68,6 +69,7 @@ import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.IGeckoEditableParent;
import org.mozilla.gecko.MagnifiableSurfaceView;
import org.mozilla.gecko.NativeQueue;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.mozglue.JNIObject;
@ -170,9 +172,14 @@ public class GeckoSession {
}
@TargetApi(Build.VERSION_CODES.P)
private static class SessionMagnifierP implements GeckoSession.SessionMagnifier {
private class SessionMagnifierP implements GeckoSession.SessionMagnifier {
private @Nullable View mView;
private @Nullable Magnifier mMagnifier;
private final @NonNull Compositor mCompositor;
private SessionMagnifierP(final Compositor compositor) {
mCompositor = compositor;
}
@Override
@UiThread
@ -206,7 +213,15 @@ public class GeckoSession {
mMagnifier = new Magnifier(mView);
}
if (mView instanceof MagnifiableSurfaceView) {
final MagnifiableSurfaceView view = (MagnifiableSurfaceView) mView;
view.setMagnifierSurface(mCompositor.getMagnifiableSurface());
}
mMagnifier.show(sourceCenter.x, sourceCenter.y);
if (mView instanceof MagnifiableSurfaceView) {
final MagnifiableSurfaceView view = (MagnifiableSurfaceView) mView;
view.setMagnifierSurface(null);
}
}
@Override
@ -311,6 +326,12 @@ public class GeckoSession {
public native void syncResumeResizeCompositor(
int x, int y, int width, int height, Object surface, Object surfaceControl);
// Returns a Surface that content has been rendered in to, which should be used when the
// magnifier is shown. This may differ from the Surface we have passed to
// syncResumeResizeCompositor().
@WrapForJNI(calledFrom = "ui", dispatchTo = "current")
public native Surface getMagnifiableSurface();
@WrapForJNI(calledFrom = "ui", dispatchTo = "current")
public native void setMaxToolbarHeight(int height);
@ -1632,7 +1653,7 @@ public class GeckoSession {
ThreadUtils.assertOnUiThread();
if (mMagnifier == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
mMagnifier = new SessionMagnifierP();
mMagnifier = new SessionMagnifierP(mCompositor);
} else {
mMagnifier = new SessionMagnifier() {};
}

View File

@ -1309,6 +1309,10 @@ class LayerViewSupport final
MakeUnique<LayerViewEvent>(MakeUnique<OnResumedEvent>(aObj)));
}
mozilla::jni::Object::LocalRef GetMagnifiableSurface() {
return mozilla::jni::Object::LocalRef::From(GetSurface());
}
void SyncInvalidateAndScheduleComposite() {
if (!mUiCompositorControllerChild) {
return;