From 46c77c203439b3b37c99d09e326df4b1fe08c10b Mon Sep 17 00:00:00 2001
From: Justin Klaassen
@@ -306,6 +303,203 @@ public class SurfaceControl {
*/
public static final int WINDOW_TYPE_DONT_SCREENSHOT = 441731;
+ /**
+ * Builder class for {@link SurfaceControl} objects.
+ */
+ public static class Builder {
+ private SurfaceSession mSession;
+ private int mFlags = HIDDEN;
+ private int mWidth;
+ private int mHeight;
+ private int mFormat = PixelFormat.OPAQUE;
+ private String mName;
+ private SurfaceControl mParent;
+ private int mWindowType;
+ private int mOwnerUid;
+
+ /**
+ * Begin building a SurfaceControl with a given {@link SurfaceSession}.
+ *
+ * @param session The {@link SurfaceSession} with which to eventually construct the surface.
+ */
+ public Builder(SurfaceSession session) {
+ mSession = session;
+ }
+
+ /**
+ * Construct a new {@link SurfaceControl} with the set parameters.
+ */
+ public SurfaceControl build() {
+ if (mWidth <= 0 || mHeight <= 0) {
+ throw new IllegalArgumentException(
+ "width and height must be set");
+ }
+ return new SurfaceControl(mSession, mName, mWidth, mHeight, mFormat,
+ mFlags, mParent, mWindowType, mOwnerUid);
+ }
+
+ /**
+ * Set a debugging-name for the SurfaceControl.
+ *
+ * @param name A name to identify the Surface in debugging.
+ */
+ public Builder setName(String name) {
+ mName = name;
+ return this;
+ }
+
+ /**
+ * Set the initial size of the controlled surface's buffers in pixels.
+ *
+ * @param width The buffer width in pixels.
+ * @param height The buffer height in pixels.
+ */
+ public Builder setSize(int width, int height) {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException(
+ "width and height must be positive");
+ }
+ mWidth = width;
+ mHeight = height;
+ return this;
+ }
+
+ /**
+ * Set the pixel format of the controlled surface's buffers, using constants from
+ * {@link android.graphics.PixelFormat}.
+ */
+ public Builder setFormat(@PixelFormat.Format int format) {
+ mFormat = format;
+ return this;
+ }
+
+ /**
+ * Specify if the app requires a hardware-protected path to
+ * an external display sync. If protected content is enabled, but
+ * such a path is not available, then the controlled Surface will
+ * not be displayed.
+ *
+ * @param protectedContent Whether to require a protected sink.
+ */
+ public Builder setProtected(boolean protectedContent) {
+ if (protectedContent) {
+ mFlags |= PROTECTED_APP;
+ } else {
+ mFlags &= ~PROTECTED_APP;
+ }
+ return this;
+ }
+
+ /**
+ * Specify whether the Surface contains secure content. If true, the system
+ * will prevent the surfaces content from being copied by another process. In
+ * particular screenshots and VNC servers will be disabled. This is however
+ * not a complete prevention of readback as {@link #setProtected}.
+ */
+ public Builder setSecure(boolean secure) {
+ if (secure) {
+ mFlags |= SECURE;
+ } else {
+ mFlags &= ~SECURE;
+ }
+ return this;
+ }
+
+ /**
+ * Indicates whether the surface must be considered opaque,
+ * even if its pixel format is set to translucent. This can be useful if an
+ * application needs full RGBA 8888 support for instance but will
+ * still draw every pixel opaque.
+ *
+ * This flag only determines whether opacity will be sampled from the alpha channel.
+ * Plane-alpha from calls to setAlpha() can still result in blended composition
+ * regardless of the opaque setting.
+ *
+ * Combined effects are (assuming a buffer format with an alpha channel):
+ *
@@ -331,19 +525,7 @@ public class SurfaceControl {
*
* @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
*/
- public SurfaceControl(SurfaceSession session,
- String name, int w, int h, int format, int flags, int windowType, int ownerUid)
- throws OutOfResourcesException {
- this(session, name, w, h, format, flags, null, windowType, ownerUid);
- }
-
- public SurfaceControl(SurfaceSession session,
- String name, int w, int h, int format, int flags)
- throws OutOfResourcesException {
- this(session, name, w, h, format, flags, null, INVALID_WINDOW_TYPE, Binder.getCallingUid());
- }
-
- public SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
+ private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
SurfaceControl parent, int windowType, int ownerUid)
throws OutOfResourcesException {
if (session == null) {
@@ -533,7 +715,7 @@ public class SurfaceControl {
}
}
- public void setRelativeLayer(IBinder relativeTo, int zorder) {
+ public void setRelativeLayer(SurfaceControl relativeTo, int zorder) {
checkNotReleased();
synchronized(SurfaceControl.class) {
sGlobalTransaction.setRelativeLayer(this, relativeTo, zorder);
@@ -970,6 +1152,20 @@ public class SurfaceControl {
minLayer, maxLayer, allLayers, useIdentityTransform);
}
+ /**
+ * Captures a layer and its children into the provided {@link Surface}.
+ *
+ * @param layerHandleToken The root layer to capture.
+ * @param consumer The {@link Surface} to capture the layer into.
+ * @param rotation Apply a custom clockwise rotation to the screenshot, i.e.
+ * Surface.ROTATION_0,90,180,270. Surfaceflinger will always capture in its
+ * native portrait orientation by default, so this is useful for returning
+ * captures that are independent of device orientation.
+ */
+ public static void captureLayers(IBinder layerHandleToken, Surface consumer, int rotation) {
+ nativeCaptureLayers(layerHandleToken, consumer, rotation);
+ }
+
public static class Transaction implements Closeable {
public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
Transaction.class.getClassLoader(),
@@ -1035,9 +1231,9 @@ public class SurfaceControl {
return this;
}
- public Transaction setRelativeLayer(SurfaceControl sc, IBinder relativeTo, int z) {
+ public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
nativeSetRelativeLayer(mNativeObject, sc.mNativeObject,
- relativeTo, z);
+ relativeTo.getHandle(), z);
return this;
}
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
index ebb2af45..4eab496e 100644
--- a/android/view/SurfaceView.java
+++ b/android/view/SurfaceView.java
@@ -16,115 +16,1215 @@
package android.view;
-import com.android.layoutlib.bridge.MockView;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
+import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER;
+import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER;
import android.content.Context;
+import android.content.res.CompatibilityInfo.Translator;
+import android.content.res.Configuration;
import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.SystemClock;
import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.internal.view.SurfaceCallbackHelper;
+
+import java.util.ArrayList;
+import java.util.concurrent.locks.ReentrantLock;
/**
- * Mock version of the SurfaceView.
- * Only non override public methods from the real SurfaceView have been added in there.
- * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ * Provides a dedicated drawing surface embedded inside of a view hierarchy.
+ * You can control the format of this surface and, if you like, its size; the
+ * SurfaceView takes care of placing the surface at the correct location on the
+ * screen
+ *
+ * The surface is Z ordered so that it is behind the window holding its
+ * SurfaceView; the SurfaceView punches a hole in its window to allow its
+ * surface to be displayed. The view hierarchy will take care of correctly
+ * compositing with the Surface any siblings of the SurfaceView that would
+ * normally appear on top of it. This can be used to place overlays such as
+ * buttons on top of the Surface, though note however that it can have an
+ * impact on performance since a full alpha-blended composite will be performed
+ * each time the Surface changes.
+ *
+ * The transparent region that makes the surface visible is based on the
+ * layout positions in the view hierarchy. If the post-layout transform
+ * properties are used to draw a sibling view on top of the SurfaceView, the
+ * view may not be properly composited with the surface.
*
- * TODO: generate automatically.
+ * Access to the underlying surface is provided via the SurfaceHolder interface,
+ * which can be retrieved by calling {@link #getHolder}.
*
+ * The Surface will be created for you while the SurfaceView's window is
+ * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated}
+ * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the
+ * Surface is created and destroyed as the window is shown and hidden.
+ *
+ * One of the purposes of this class is to provide a surface in which a
+ * secondary thread can render into the screen. If you are going to use it
+ * this way, you need to be aware of some threading semantics:
+ *
+ * Note: Starting in platform version
+ * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is
+ * updated synchronously with other View rendering. This means that translating
+ * and scaling a SurfaceView on screen will not cause rendering artifacts. Such
+ * artifacts may occur on previous versions of the platform when its window is
+ * positioned asynchronously. Note that this must be set before the surface view's containing
+ * window is attached to the window manager.
+ *
+ * Calling this overrides any previous call to {@link #setZOrderOnTop}.
+ */
public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+ mSubLayer = isMediaOverlay
+ ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
}
+ /**
+ * Control whether the surface view's surface is placed on top of its
+ * window. Normally it is placed behind the window, to allow it to
+ * (for the most part) appear to composite with the views in the
+ * hierarchy. By setting this, you cause it to be placed above the
+ * window. This means that none of the contents of the window this
+ * SurfaceView is in will be visible on top of its surface.
+ *
+ * Note that this must be set before the surface view's containing
+ * window is attached to the window manager.
+ *
+ * Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
+ */
public void setZOrderOnTop(boolean onTop) {
+ if (onTop) {
+ mSubLayer = APPLICATION_PANEL_SUBLAYER;
+ } else {
+ mSubLayer = APPLICATION_MEDIA_SUBLAYER;
+ }
}
+ /**
+ * Control whether the surface view's content should be treated as secure,
+ * preventing it from appearing in screenshots or from being viewed on
+ * non-secure displays.
+ *
+ * Note that this must be set before the surface view's containing
+ * window is attached to the window manager.
+ *
+ * See {@link android.view.Display#FLAG_SECURE} for details.
+ *
+ * @param isSecure True if the surface view is secure.
+ */
public void setSecure(boolean isSecure) {
+ if (isSecure) {
+ mSurfaceFlags |= SurfaceControl.SECURE;
+ } else {
+ mSurfaceFlags &= ~SurfaceControl.SECURE;
+ }
}
- public SurfaceHolder getHolder() {
- return mSurfaceHolder;
+ private void updateOpaqueFlag() {
+ if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
+ mSurfaceFlags |= SurfaceControl.OPAQUE;
+ } else {
+ mSurfaceFlags &= ~SurfaceControl.OPAQUE;
+ }
}
- private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+ private Rect getParentSurfaceInsets() {
+ final ViewRootImpl root = getViewRootImpl();
+ if (root == null) {
+ return null;
+ } else {
+ return root.mWindowAttributes.surfaceInsets;
+ }
+ }
+
+ /** @hide */
+ protected void updateSurface() {
+ if (!mHaveFrame) {
+ return;
+ }
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
+ return;
+ }
+
+ mTranslator = viewRoot.mTranslator;
+ if (mTranslator != null) {
+ mSurface.setCompatibilityTranslator(mTranslator);
+ }
+
+ int myWidth = mRequestedWidth;
+ if (myWidth <= 0) myWidth = getWidth();
+ int myHeight = mRequestedHeight;
+ if (myHeight <= 0) myHeight = getHeight();
+
+ final boolean formatChanged = mFormat != mRequestedFormat;
+ final boolean visibleChanged = mVisible != mRequestedVisible;
+ final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
+ && mRequestedVisible;
+ final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
+ final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
+ boolean redrawNeeded = false;
+
+ if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
+ getLocationInWindow(mLocation);
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "Changes: creating=" + creating
+ + " format=" + formatChanged + " size=" + sizeChanged
+ + " visible=" + visibleChanged
+ + " left=" + (mWindowSpaceLeft != mLocation[0])
+ + " top=" + (mWindowSpaceTop != mLocation[1]));
+
+ try {
+ final boolean visible = mVisible = mRequestedVisible;
+ mWindowSpaceLeft = mLocation[0];
+ mWindowSpaceTop = mLocation[1];
+ mSurfaceWidth = myWidth;
+ mSurfaceHeight = myHeight;
+ mFormat = mRequestedFormat;
+ mLastWindowVisibility = mWindowVisibility;
+
+ mScreenRect.left = mWindowSpaceLeft;
+ mScreenRect.top = mWindowSpaceTop;
+ mScreenRect.right = mWindowSpaceLeft + getWidth();
+ mScreenRect.bottom = mWindowSpaceTop + getHeight();
+ if (mTranslator != null) {
+ mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+ }
+
+ final Rect surfaceInsets = getParentSurfaceInsets();
+ mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
+
+ if (creating) {
+ mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
+ mDeferredDestroySurfaceControl = mSurfaceControl;
+
+ updateOpaqueFlag();
+ final String name = "SurfaceView - " + viewRoot.getTitle().toString();
+
+ mSurfaceControl = new SurfaceControlWithBackground(
+ name,
+ (mSurfaceFlags & SurfaceControl.OPAQUE) != 0,
+ new SurfaceControl.Builder(mSurfaceSession)
+ .setSize(mSurfaceWidth, mSurfaceHeight)
+ .setFormat(mFormat)
+ .setFlags(mSurfaceFlags));
+ } else if (mSurfaceControl == null) {
+ return;
+ }
+
+ boolean realSizeChanged = false;
+
+ mSurfaceLock.lock();
+ try {
+ mDrawingStopped = !visible;
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "Cur surface: " + mSurface);
+
+ SurfaceControl.openTransaction();
+ try {
+ mSurfaceControl.setLayer(mSubLayer);
+ if (mViewVisibility) {
+ mSurfaceControl.show();
+ } else {
+ mSurfaceControl.hide();
+ }
+
+ // While creating the surface, we will set it's initial
+ // geometry. Outside of that though, we should generally
+ // leave it to the RenderThread.
+ //
+ // There is one more case when the buffer size changes we aren't yet
+ // prepared to sync (as even following the transaction applying
+ // we still need to latch a buffer).
+ // b/28866173
+ if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
+ mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
+ mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
+ 0.0f, 0.0f,
+ mScreenRect.height() / (float) mSurfaceHeight);
+ }
+ if (sizeChanged) {
+ mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
+ }
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+
+ if (sizeChanged || creating) {
+ redrawNeeded = true;
+ }
+
+ mSurfaceFrame.left = 0;
+ mSurfaceFrame.top = 0;
+ if (mTranslator == null) {
+ mSurfaceFrame.right = mSurfaceWidth;
+ mSurfaceFrame.bottom = mSurfaceHeight;
+ } else {
+ float appInvertedScale = mTranslator.applicationInvertedScale;
+ mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
+ mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
+ }
+
+ final int surfaceWidth = mSurfaceFrame.right;
+ final int surfaceHeight = mSurfaceFrame.bottom;
+ realSizeChanged = mLastSurfaceWidth != surfaceWidth
+ || mLastSurfaceHeight != surfaceHeight;
+ mLastSurfaceWidth = surfaceWidth;
+ mLastSurfaceHeight = surfaceHeight;
+ } finally {
+ mSurfaceLock.unlock();
+ }
+
+ try {
+ redrawNeeded |= visible && !mDrawFinished;
+
+ SurfaceHolder.Callback callbacks[] = null;
+
+ final boolean surfaceChanged = creating;
+ if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
+ mSurfaceCreated = false;
+ if (mSurface.isValid()) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "visibleChanged -- surfaceDestroyed");
+ callbacks = getSurfaceCallbacks();
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceDestroyed(mSurfaceHolder);
+ }
+ // Since Android N the same surface may be reused and given to us
+ // again by the system server at a later point. However
+ // as we didn't do this in previous releases, clients weren't
+ // necessarily required to clean up properly in
+ // surfaceDestroyed. This leads to problems for example when
+ // clients don't destroy their EGL context, and try
+ // and create a new one on the same surface following reuse.
+ // Since there is no valid use of the surface in-between
+ // surfaceDestroyed and surfaceCreated, we force a disconnect,
+ // so the next connect will always work if we end up reusing
+ // the surface.
+ if (mSurface.isValid()) {
+ mSurface.forceScopedDisconnect();
+ }
+ }
+ }
+
+ if (creating) {
+ mSurface.copyFrom(mSurfaceControl);
+ }
+
+ if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion
+ < Build.VERSION_CODES.O) {
+ // Some legacy applications use the underlying native {@link Surface} object
+ // as a key to whether anything has changed. In these cases, updates to the
+ // existing {@link Surface} will be ignored when the size changes.
+ // Therefore, we must explicitly recreate the {@link Surface} in these
+ // cases.
+ mSurface.createFrom(mSurfaceControl);
+ }
+
+ if (visible && mSurface.isValid()) {
+ if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+ mSurfaceCreated = true;
+ mIsCreating = true;
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "visibleChanged -- surfaceCreated");
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceCreated(mSurfaceHolder);
+ }
+ }
+ if (creating || formatChanged || sizeChanged
+ || visibleChanged || realSizeChanged) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "surfaceChanged -- format=" + mFormat
+ + " w=" + myWidth + " h=" + myHeight);
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+ }
+ }
+ if (redrawNeeded) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "surfaceRedrawNeeded");
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+
+ mPendingReportDraws++;
+ viewRoot.drawPending();
+ SurfaceCallbackHelper sch =
+ new SurfaceCallbackHelper(this::onDrawFinished);
+ sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
+ }
+ }
+ } finally {
+ mIsCreating = false;
+ if (mSurfaceControl != null && !mSurfaceCreated) {
+ mSurface.release();
+ // If we are not in the stopped state, then the destruction of the Surface
+ // represents a visual change we need to display, and we should go ahead
+ // and destroy the SurfaceControl. However if we are in the stopped state,
+ // we can just leave the Surface around so it can be a part of animations,
+ // and we let the life-time be tied to the parent surface.
+ if (!mWindowStopped) {
+ mSurfaceControl.destroy();
+ mSurfaceControl = null;
+ }
+ }
+ }
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception configuring surface", ex);
+ }
+ if (DEBUG) Log.v(
+ TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
+ + " w=" + mScreenRect.width() + " h=" + mScreenRect.height()
+ + ", frame=" + mSurfaceFrame);
+ } else {
+ // Calculate the window position in case RT loses the window
+ // and we need to fallback to a UI-thread driven position update
+ getLocationInSurface(mLocation);
+ final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
+ || mWindowSpaceTop != mLocation[1];
+ final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
+ || getHeight() != mScreenRect.height();
+ if (positionChanged || layoutSizeChanged) { // Only the position has changed
+ mWindowSpaceLeft = mLocation[0];
+ mWindowSpaceTop = mLocation[1];
+ // For our size changed check, we keep mScreenRect.width() and mScreenRect.height()
+ // in view local space.
+ mLocation[0] = getWidth();
+ mLocation[1] = getHeight();
+
+ mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
+ mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]);
+
+ if (mTranslator != null) {
+ mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+ }
+
+ if (mSurfaceControl == null) {
+ return;
+ }
+
+ if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
+ try {
+ if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
+ "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+ mScreenRect.left, mScreenRect.top,
+ mScreenRect.right, mScreenRect.bottom));
+ setParentSpaceRectangle(mScreenRect, -1);
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception configuring surface", ex);
+ }
+ }
+ }
+ }
+ }
+
+ private void onDrawFinished() {
+ if (DEBUG) {
+ Log.i(TAG, System.identityHashCode(this) + " "
+ + "finishedDrawing");
+ }
+
+ if (mDeferredDestroySurfaceControl != null) {
+ mDeferredDestroySurfaceControl.destroy();
+ mDeferredDestroySurfaceControl = null;
+ }
+
+ runOnUiThread(() -> {
+ performDrawFinished();
+ });
+ }
+
+ private void setParentSpaceRectangle(Rect position, long frameNumber) {
+ ViewRootImpl viewRoot = getViewRootImpl();
+
+ SurfaceControl.openTransaction();
+ try {
+ if (frameNumber > 0) {
+ mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber);
+ }
+ mSurfaceControl.setPosition(position.left, position.top);
+ mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth,
+ 0.0f, 0.0f,
+ position.height() / (float) mSurfaceHeight);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+
+ private Rect mRTLastReportedPosition = new Rect();
+
+ /**
+ * Called by native by a Rendering Worker thread to update the window position
+ * @hide
+ */
+ public final void updateSurfacePosition_renderWorker(long frameNumber,
+ int left, int top, int right, int bottom) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+
+ // TODO: This is teensy bit racey in that a brand new SurfaceView moving on
+ // its 2nd frame if RenderThread is running slowly could potentially see
+ // this as false, enter the branch, get pre-empted, then this comes along
+ // and reports a new position, then the UI thread resumes and reports
+ // its position. This could therefore be de-sync'd in that interval, but
+ // the synchronization would violate the rule that RT must never block
+ // on the UI thread which would open up potential deadlocks. The risk of
+ // a single-frame desync is therefore preferable for now.
+ mRtHandlingPositionUpdates = true;
+ if (mRTLastReportedPosition.left == left
+ && mRTLastReportedPosition.top == top
+ && mRTLastReportedPosition.right == right
+ && mRTLastReportedPosition.bottom == bottom) {
+ return;
+ }
+ try {
+ if (DEBUG) {
+ Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " +
+ "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+ frameNumber, left, top, right, bottom));
+ }
+ mRTLastReportedPosition.set(left, top, right, bottom);
+ setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
+ // Now overwrite mRTLastReportedPosition with our values
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception from repositionChild", ex);
+ }
+ }
+
+ /**
+ * Called by native on RenderThread to notify that the view is no longer in the
+ * draw tree. UI thread is blocked at this point.
+ * @hide
+ */
+ public final void surfacePositionLost_uiRtSync(long frameNumber) {
+ if (DEBUG) {
+ Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
+ System.identityHashCode(this), frameNumber));
+ }
+ mRTLastReportedPosition.setEmpty();
+
+ if (mSurfaceControl == null) {
+ return;
+ }
+ if (mRtHandlingPositionUpdates) {
+ mRtHandlingPositionUpdates = false;
+ // This callback will happen while the UI thread is blocked, so we can
+ // safely access other member variables at this time.
+ // So do what the UI thread would have done if RT wasn't handling position
+ // updates.
+ if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) {
+ try {
+ if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " +
+ "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+ mScreenRect.left, mScreenRect.top,
+ mScreenRect.right, mScreenRect.bottom));
+ setParentSpaceRectangle(mScreenRect, frameNumber);
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception configuring surface", ex);
+ }
+ }
+ }
+ }
+
+ private SurfaceHolder.Callback[] getSurfaceCallbacks() {
+ SurfaceHolder.Callback callbacks[];
+ synchronized (mCallbacks) {
+ callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
+ mCallbacks.toArray(callbacks);
+ }
+ return callbacks;
+ }
+
+ /**
+ * This method still exists only for compatibility reasons because some applications have relied
+ * on this method via reflection. See Issue 36345857 for details.
+ *
+ * @deprecated No platform code is using this method anymore.
+ * @hide
+ */
+ @Deprecated
+ public void setWindowType(int type) {
+ if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
+ throw new UnsupportedOperationException(
+ "SurfaceView#setWindowType() has never been a public API.");
+ }
+
+ if (type == TYPE_APPLICATION_PANEL) {
+ Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) "
+ + "just to make the SurfaceView to be placed on top of its window, you must "
+ + "call setZOrderOnTop(true) instead.", new Throwable());
+ setZOrderOnTop(true);
+ return;
+ }
+ Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. "
+ + "type=" + type, new Throwable());
+ }
+
+ private void runOnUiThread(Runnable runnable) {
+ Handler handler = getHandler();
+ if (handler != null && handler.getLooper() != Looper.myLooper()) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+
+ /**
+ * Check to see if the surface has fixed size dimensions or if the surface's
+ * dimensions are dimensions are dependent on its current layout.
+ *
+ * @return true if the surface has dimensions that are fixed in size
+ * @hide
+ */
+ public boolean isFixedSize() {
+ return (mRequestedWidth != -1 || mRequestedHeight != -1);
+ }
+
+ private boolean isAboveParent() {
+ return mSubLayer >= 0;
+ }
+
+ private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+ private static final String LOG_TAG = "SurfaceHolder";
@Override
public boolean isCreating() {
- return false;
+ return mIsCreating;
}
@Override
public void addCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ // This is a linear search, but in practice we'll
+ // have only a couple callbacks, so it doesn't matter.
+ if (mCallbacks.contains(callback) == false) {
+ mCallbacks.add(callback);
+ }
+ }
}
@Override
public void removeCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(callback);
+ }
}
@Override
public void setFixedSize(int width, int height) {
+ if (mRequestedWidth != width || mRequestedHeight != height) {
+ mRequestedWidth = width;
+ mRequestedHeight = height;
+ requestLayout();
+ }
}
@Override
public void setSizeFromLayout() {
+ if (mRequestedWidth != -1 || mRequestedHeight != -1) {
+ mRequestedWidth = mRequestedHeight = -1;
+ requestLayout();
+ }
}
@Override
public void setFormat(int format) {
+ // for backward compatibility reason, OPAQUE always
+ // means 565 for SurfaceView
+ if (format == PixelFormat.OPAQUE)
+ format = PixelFormat.RGB_565;
+
+ mRequestedFormat = format;
+ if (mSurfaceControl != null) {
+ updateSurface();
+ }
}
+ /**
+ * @deprecated setType is now ignored.
+ */
@Override
- public void setType(int type) {
- }
+ @Deprecated
+ public void setType(int type) { }
@Override
public void setKeepScreenOn(boolean screenOn) {
+ runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn));
}
+ /**
+ * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
+ *
+ * After drawing into the provided {@link Canvas}, the caller must
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * The caller must redraw the entire surface.
+ * @return A canvas for drawing into the surface.
+ */
@Override
public Canvas lockCanvas() {
- return null;
+ return internalLockCanvas(null, false);
+ }
+
+ /**
+ * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
+ *
+ * After drawing into the provided {@link Canvas}, the caller must
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * @param inOutDirty A rectangle that represents the dirty region that the caller wants
+ * to redraw. This function may choose to expand the dirty rectangle if for example
+ * the surface has been resized or if the previous contents of the surface were
+ * not available. The caller must redraw the entire dirty region as represented
+ * by the contents of the inOutDirty rectangle upon return from this function.
+ * The caller may also pass
+ * Input: Nothing.
+ *
+ * Output: Nothing.
+ *
+ *
+ * If the underlying buffer lacks an alpha channel, it is as if setOpaque(true)
+ * were set automatically.
+ * @param opaque Whether the Surface is OPAQUE.
+ */
+ public Builder setOpaque(boolean opaque) {
+ if (opaque) {
+ mFlags |= OPAQUE;
+ } else {
+ mFlags &= ~OPAQUE;
+ }
+ return this;
+ }
+
+ /**
+ * Set a parent surface for our new SurfaceControl.
+ *
+ * Child surfaces are constrained to the onscreen region of their parent.
+ * Furthermore they stack relatively in Z order, and inherit the transformation
+ * of the parent.
+ *
+ * @param parent The parent control.
+ */
+ public Builder setParent(SurfaceControl parent) {
+ mParent = parent;
+ return this;
+ }
+
+ /**
+ * Set surface metadata.
+ *
+ * Currently these are window-types as per {@link WindowManager.LayoutParams} and
+ * owner UIDs. Child surfaces inherit their parents
+ * metadata so only the WindowManager needs to set this on root Surfaces.
+ *
+ * @param windowType A window-type
+ * @param ownerUid UID of the window owner.
+ */
+ public Builder setMetadata(int windowType, int ownerUid) {
+ if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) {
+ throw new UnsupportedOperationException(
+ "It only makes sense to set Surface metadata from the WindowManager");
+ }
+ mWindowType = windowType;
+ mOwnerUid = ownerUid;
+ return this;
+ }
+
+ /**
+ * Indicate whether a 'ColorLayer' is to be constructed.
+ *
+ * Color layers will not have an associated BufferQueue and will instead always render a
+ * solid color (that is, solid before plane alpha). Currently that color is black.
+ *
+ * @param isColorLayer Whether to create a color layer.
+ */
+ public Builder setColorLayer(boolean isColorLayer) {
+ if (isColorLayer) {
+ mFlags |= FX_SURFACE_DIM;
+ } else {
+ mFlags &= ~FX_SURFACE_DIM;
+ }
+ return this;
+ }
+
+ /**
+ * Set 'Surface creation flags' such as {@link HIDDEN}, {@link SECURE}.
+ *
+ * TODO: Finish conversion to individual builder methods?
+ * @param flags The combined flags
+ */
+ public Builder setFlags(int flags) {
+ mFlags = flags;
+ return this;
+ }
+ }
+
/**
* Create a surface with a name.
*
+ *
+ *
+ * null
instead, in the case where the
+ * entire surface should be redrawn.
+ * @return A canvas for drawing into the surface.
+ */
+ @Override
+ public Canvas lockCanvas(Rect inOutDirty) {
+ return internalLockCanvas(inOutDirty, false);
}
@Override
- public Canvas lockCanvas(Rect dirty) {
+ public Canvas lockHardwareCanvas() {
+ return internalLockCanvas(null, true);
+ }
+
+ private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
+ mSurfaceLock.lock();
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
+ + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);
+
+ Canvas c = null;
+ if (!mDrawingStopped && mSurfaceControl != null) {
+ try {
+ if (hardware) {
+ c = mSurface.lockHardwareCanvas();
+ } else {
+ c = mSurface.lockCanvas(dirty);
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Exception locking surface", e);
+ }
+ }
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
+ if (c != null) {
+ mLastLockTime = SystemClock.uptimeMillis();
+ return c;
+ }
+
+ // If the Surface is not ready to be drawn, then return null,
+ // but throttle calls to this function so it isn't called more
+ // than every 100ms.
+ long now = SystemClock.uptimeMillis();
+ long nextTime = mLastLockTime + 100;
+ if (nextTime > now) {
+ try {
+ Thread.sleep(nextTime-now);
+ } catch (InterruptedException e) {
+ }
+ now = SystemClock.uptimeMillis();
+ }
+ mLastLockTime = now;
+ mSurfaceLock.unlock();
+
return null;
}
+ /**
+ * Posts the new contents of the {@link Canvas} to the surface and
+ * releases the {@link Canvas}.
+ *
+ * @param canvas The canvas previously obtained from {@link #lockCanvas}.
+ */
@Override
public void unlockCanvasAndPost(Canvas canvas) {
+ mSurface.unlockCanvasAndPost(canvas);
+ mSurfaceLock.unlock();
}
@Override
public Surface getSurface() {
- return null;
+ return mSurface;
}
@Override
public Rect getSurfaceFrame() {
- return null;
+ return mSurfaceFrame;
}
};
-}
+ class SurfaceControlWithBackground extends SurfaceControl {
+ private SurfaceControl mBackgroundControl;
+ private boolean mOpaque = true;
+ public boolean mVisible = false;
+
+ public SurfaceControlWithBackground(String name, boolean opaque, SurfaceControl.Builder b)
+ throws Exception {
+ super(b.setName(name).build());
+
+ mBackgroundControl = b.setName("Background for -" + name)
+ .setFormat(OPAQUE)
+ .setColorLayer(true)
+ .build();
+ mOpaque = opaque;
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ super.setAlpha(alpha);
+ mBackgroundControl.setAlpha(alpha);
+ }
+
+ @Override
+ public void setLayer(int zorder) {
+ super.setLayer(zorder);
+ // -3 is below all other child layers as SurfaceView never goes below -2
+ mBackgroundControl.setLayer(-3);
+ }
+
+ @Override
+ public void setPosition(float x, float y) {
+ super.setPosition(x, y);
+ mBackgroundControl.setPosition(x, y);
+ }
+
+ @Override
+ public void setSize(int w, int h) {
+ super.setSize(w, h);
+ mBackgroundControl.setSize(w, h);
+ }
+
+ @Override
+ public void setWindowCrop(Rect crop) {
+ super.setWindowCrop(crop);
+ mBackgroundControl.setWindowCrop(crop);
+ }
+
+ @Override
+ public void setFinalCrop(Rect crop) {
+ super.setFinalCrop(crop);
+ mBackgroundControl.setFinalCrop(crop);
+ }
+
+ @Override
+ public void setLayerStack(int layerStack) {
+ super.setLayerStack(layerStack);
+ mBackgroundControl.setLayerStack(layerStack);
+ }
+
+ @Override
+ public void setOpaque(boolean isOpaque) {
+ super.setOpaque(isOpaque);
+ mOpaque = isOpaque;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void setSecure(boolean isSecure) {
+ super.setSecure(isSecure);
+ }
+
+ @Override
+ public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+ super.setMatrix(dsdx, dtdx, dsdy, dtdy);
+ mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
+ }
+
+ @Override
+ public void hide() {
+ super.hide();
+ mVisible = false;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ mVisible = true;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ mBackgroundControl.destroy();
+ }
+
+ @Override
+ public void release() {
+ super.release();
+ mBackgroundControl.release();
+ }
+
+ @Override
+ public void setTransparentRegionHint(Region region) {
+ super.setTransparentRegionHint(region);
+ mBackgroundControl.setTransparentRegionHint(region);
+ }
+
+ @Override
+ public void deferTransactionUntil(IBinder handle, long frame) {
+ super.deferTransactionUntil(handle, frame);
+ mBackgroundControl.deferTransactionUntil(handle, frame);
+ }
+
+ @Override
+ public void deferTransactionUntil(Surface barrier, long frame) {
+ super.deferTransactionUntil(barrier, frame);
+ mBackgroundControl.deferTransactionUntil(barrier, frame);
+ }
+
+ void updateBackgroundVisibility() {
+ if (mOpaque && mVisible) {
+ mBackgroundControl.show();
+ } else {
+ mBackgroundControl.hide();
+ }
+ }
+ }
+}
diff --git a/android/view/TouchDelegate.java b/android/view/TouchDelegate.java
index cf36f436..dc50fa1d 100644
--- a/android/view/TouchDelegate.java
+++ b/android/view/TouchDelegate.java
@@ -44,7 +44,7 @@ public class TouchDelegate {
/**
* mBounds inflated to include some slop. This rect is to track whether the motion events
- * should be considered to be be within the delegate view.
+ * should be considered to be within the delegate view.
*/
private Rect mSlopBounds;
@@ -64,14 +64,12 @@ public class TouchDelegate {
public static final int BELOW = 2;
/**
- * The touchable region of the View extends to the left of its
- * actual extent.
+ * The touchable region of the View extends to the left of its actual extent.
*/
public static final int TO_LEFT = 4;
/**
- * The touchable region of the View extends to the right of its
- * actual extent.
+ * The touchable region of the View extends to the right of its actual extent.
*/
public static final int TO_RIGHT = 8;
@@ -108,28 +106,24 @@ public class TouchDelegate {
boolean handled = false;
switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Rect bounds = mBounds;
-
- if (bounds.contains(x, y)) {
- mDelegateTargeted = true;
- sendToDelegate = true;
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_MOVE:
- sendToDelegate = mDelegateTargeted;
- if (sendToDelegate) {
- Rect slopBounds = mSlopBounds;
- if (!slopBounds.contains(x, y)) {
- hit = false;
+ case MotionEvent.ACTION_DOWN:
+ mDelegateTargeted = mBounds.contains(x, y);
+ sendToDelegate = mDelegateTargeted;
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_MOVE:
+ sendToDelegate = mDelegateTargeted;
+ if (sendToDelegate) {
+ Rect slopBounds = mSlopBounds;
+ if (!slopBounds.contains(x, y)) {
+ hit = false;
+ }
}
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- sendToDelegate = mDelegateTargeted;
- mDelegateTargeted = false;
- break;
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ sendToDelegate = mDelegateTargeted;
+ mDelegateTargeted = false;
+ break;
}
if (sendToDelegate) {
final View delegateView = mDelegateView;
diff --git a/android/view/ViewGroup_Delegate.java b/android/view/ViewGroup_Delegate.java
index 4b760a7d..6daae200 100644
--- a/android/view/ViewGroup_Delegate.java
+++ b/android/view/ViewGroup_Delegate.java
@@ -67,12 +67,12 @@ public class ViewGroup_Delegate {
Outline outline) {
float elevation = getElevation(child, parent);
if(outline.mMode == Outline.MODE_ROUND_RECT && outline.mRect != null) {
- RectShadowPainter.paintShadow(outline, elevation, canvas);
+ RectShadowPainter.paintShadow(outline, elevation, canvas, child.getAlpha());
return;
}
BufferedImage shadow = null;
if (outline.mPath != null) {
- shadow = getPathShadow(outline, canvas, elevation);
+ shadow = getPathShadow(outline, canvas, elevation, child.getAlpha());
}
if (shadow == null) {
return;
@@ -91,7 +91,8 @@ public class ViewGroup_Delegate {
return child.getZ() - parent.getZ();
}
- private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation) {
+ private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation,
+ float alpha) {
Rect clipBounds = canvas.getClipBounds();
if (clipBounds.isEmpty()) {
return null;
@@ -101,7 +102,7 @@ public class ViewGroup_Delegate {
Graphics2D graphics = image.createGraphics();
graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape());
graphics.dispose();
- return ShadowPainter.createDropShadow(image, (int) elevation);
+ return ShadowPainter.createDropShadow(image, (int) elevation, alpha);
}
// Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
index 99438d87..37829f0b 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -2284,18 +2284,36 @@ public final class ViewRootImpl implements ViewParent,
}
}
- if (mFirst && sAlwaysAssignFocus) {
- // handle first focus request
- if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()="
- + mView.hasFocus());
- if (mView != null) {
- if (!mView.hasFocus()) {
- mView.restoreDefaultFocus();
- if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view="
- + mView.findFocus());
- } else {
- if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: existing focused view="
- + mView.findFocus());
+ if (mFirst) {
+ if (sAlwaysAssignFocus) {
+ // handle first focus request
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus());
+ }
+ if (mView != null) {
+ if (!mView.hasFocus()) {
+ mView.restoreDefaultFocus();
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(mTag, "First: requested focused view=" + mView.findFocus());
+ }
+ } else {
+ if (DEBUG_INPUT_RESIZE) {
+ Log.v(mTag, "First: existing focused view=" + mView.findFocus());
+ }
+ }
+ }
+ } else {
+ // Some views (like ScrollView) won't hand focus to descendants that aren't within
+ // their viewport. Before layout, there's a good change these views are size 0
+ // which means no children can get focus. After layout, this view now has size, but
+ // is not guaranteed to hand-off focus to a focusable child (specifically, the edge-
+ // case where the child has a size prior to layout and thus won't trigger
+ // focusableViewAvailable).
+ View focused = mView.findFocus();
+ if (focused instanceof ViewGroup
+ && ((ViewGroup) focused).getDescendantFocusability()
+ == ViewGroup.FOCUS_AFTER_DESCENDANTS) {
+ focused.restoreDefaultFocus();
}
}
}
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 11cb046a..0b9bc576 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -16,46 +16,152 @@
package android.view.accessibility;
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
+
+import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemService;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
import android.view.IWindow;
import android.view.View;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IntPair;
+
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
- * System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
- * Such events are generated when something notable happens in the user interface,
+ * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
+ * and provides facilities for querying the accessibility state of the system.
+ * Accessibility events are generated when something notable happens in the user interface,
* for example an {@link android.app.Activity} starts, the focus or selection of a
* {@link android.view.View} changes etc. Parties interested in handling accessibility
* events implement and register an accessibility service which extends
- * {@code android.accessibilityservice.AccessibilityService}.
+ * {@link android.accessibilityservice.AccessibilityService}.
*
* @see AccessibilityEvent
- * @see android.content.Context#getSystemService
+ * @see AccessibilityNodeInfo
+ * @see android.accessibilityservice.AccessibilityService
+ * @see Context#getSystemService
+ * @see Context#ACCESSIBILITY_SERVICE
*/
-@SuppressWarnings("UnusedDeclaration")
+@SystemService(Context.ACCESSIBILITY_SERVICE)
public final class AccessibilityManager {
+ private static final boolean DEBUG = false;
+
+ private static final String LOG_TAG = "AccessibilityManager";
+
+ /** @hide */
+ public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
+
+ /** @hide */
+ public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
+
+ /** @hide */
+ public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004;
+
+ /** @hide */
+ public static final int DALTONIZER_DISABLED = -1;
+
+ /** @hide */
+ public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
+
+ /** @hide */
+ public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
+
+ /** @hide */
+ public static final int AUTOCLICK_DELAY_DEFAULT = 600;
+
+ /**
+ * Activity action: Launch UI to manage which accessibility service or feature is assigned
+ * to the navigation bar Accessibility button.
+ * > mRequestPreparerLists;
/**
- * Listener for the accessibility state.
+ * Listener for the system accessibility state. To listen for changes to the
+ * accessibility state on the device, implement this interface and register
+ * it with the system by calling {@link #addAccessibilityStateChangeListener}.
*/
public interface AccessibilityStateChangeListener {
/**
- * Called back on change in the accessibility state.
+ * Called when the accessibility enabled state changes.
*
* @param enabled Whether accessibility is enabled.
*/
- public void onAccessibilityStateChanged(boolean enabled);
+ void onAccessibilityStateChanged(boolean enabled);
}
/**
@@ -71,7 +177,24 @@ public final class AccessibilityManager {
*
* @param enabled Whether touch exploration is enabled.
*/
- public void onTouchExplorationStateChanged(boolean enabled);
+ void onTouchExplorationStateChanged(boolean enabled);
+ }
+
+ /**
+ * Listener for changes to the state of accessibility services. Changes include services being
+ * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service.
+ * {@see #addAccessibilityServicesStateChangeListener}.
+ *
+ * @hide
+ */
+ public interface AccessibilityServicesStateChangeListener {
+
+ /**
+ * Called when the state of accessibility services changes.
+ *
+ * @param manager The manager that is calling back
+ */
+ void onAccessibilityServicesStateChanged(AccessibilityManager manager);
}
/**
@@ -79,6 +202,8 @@ public final class AccessibilityManager {
* the high text contrast state on the device, implement this interface and
* register it with the system by calling
* {@link #addHighTextContrastStateChangeListener}.
+ *
+ * @hide
*/
public interface HighTextContrastChangeListener {
@@ -87,26 +212,72 @@ public final class AccessibilityManager {
*
* @param enabled Whether high text contrast is enabled.
*/
- public void onHighTextContrastStateChanged(boolean enabled);
+ void onHighTextContrastStateChanged(boolean enabled);
}
private final IAccessibilityManagerClient.Stub mClient =
new IAccessibilityManagerClient.Stub() {
- public void setState(int state) {
- }
+ @Override
+ public void setState(int state) {
+ // We do not want to change this immediately as the application may
+ // have already checked that accessibility is on and fired an event,
+ // that is now propagating up the view tree, Hence, if accessibility
+ // is now off an exception will be thrown. We want to have the exception
+ // enforcement to guard against apps that fire unnecessary accessibility
+ // events when accessibility is off.
+ mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget();
+ }
- public void notifyServicesStateChanged() {
+ @Override
+ public void notifyServicesStateChanged() {
+ final ArrayMap
In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored, * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}). @@ -241,6 +242,16 @@ public final class AutofillManager { */ public static final int STATE_SHOWING_SAVE_UI = 3; + /** + * State where the autofill is disabled because the service cannot autofill the activity at all. + * + *
In this state, every call is ignored, even {@link #requestAutofill(View)} + * (and {@link #requestAutofill(View, int, Rect)}). + * + * @hide + */ + public static final int STATE_DISABLED_BY_SERVICE = 4; + /** * Makes an authentication id from a request id and a dataset id. * @@ -398,6 +409,11 @@ public final class AutofillManager { * Runs the specified action on the UI thread. */ void runOnUiThread(Runnable action); + + /** + * Gets the complete component name of this client. + */ + ComponentName getComponentName(); } /** @@ -506,7 +522,7 @@ public final class AutofillManager { * @return whether autofill is enabled for the current user. */ public boolean isEnabled() { - if (!hasAutofillFeature()) { + if (!hasAutofillFeature() || isDisabledByService()) { return false; } synchronized (mLock) { @@ -580,19 +596,31 @@ public final class AutofillManager { notifyViewEntered(view, 0); } + private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) { + if (isDisabledByService()) { + if (sVerbose) { + Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view + + ") on state " + getStateAsStringLocked()); + } + return true; + } + if (mState == STATE_FINISHED && (flags & FLAG_MANUAL_REQUEST) == 0) { + if (sVerbose) { + Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view + + ") on state " + getStateAsStringLocked()); + } + return true; + } + return false; + } + private void notifyViewEntered(@NonNull View view, int flags) { if (!hasAutofillFeature()) { return; } AutofillCallback callback = null; synchronized (mLock) { - if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) { - if (sVerbose) { - Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view - + "): ignored on state " + getStateAsStringLocked()); - } - return; - } + if (shouldIgnoreViewEnteredLocked(view, flags)) return; ensureServiceClientAddedIfNeededLocked(); @@ -717,14 +745,8 @@ public final class AutofillManager { } AutofillCallback callback = null; synchronized (mLock) { - if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) { - if (sVerbose) { - Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view - + ", virtualId=" + virtualId - + "): ignored on state " + getStateAsStringLocked()); - } - return; - } + if (shouldIgnoreViewEnteredLocked(view, flags)) return; + ensureServiceClientAddedIfNeededLocked(); if (!mEnabled) { @@ -1059,13 +1081,13 @@ public final class AutofillManager { return; } try { + final AutofillClient client = getClientLocked(); mSessionId = mService.startSession(mContext.getActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), - mCallback != null, flags, mContext.getOpPackageName()); + mCallback != null, flags, client.getComponentName()); if (mSessionId != NO_SESSION) { mState = STATE_ACTIVE; } - final AutofillClient client = getClientLocked(); if (client != null) { client.autofillCallbackResetableStateAvailable(); } @@ -1120,14 +1142,14 @@ public final class AutofillManager { try { if (restartIfNecessary) { + final AutofillClient client = getClientLocked(); final int newId = mService.updateOrRestartSession(mContext.getActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), - mCallback != null, flags, mContext.getOpPackageName(), mSessionId, action); + mCallback != null, flags, client.getComponentName(), mSessionId, action); if (newId != mSessionId) { if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId); mSessionId = newId; mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE; - final AutofillClient client = getClientLocked(); if (client != null) { client.autofillCallbackResetableStateAvailable(); } @@ -1437,7 +1459,9 @@ public final class AutofillManager { * Marks the state of the session as finished. * * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null} - * FillResponse) or {@link #STATE_UNKNOWN} (because the session was removed). + * FillResponse), {@link #STATE_UNKNOWN} (because the session was removed), or + * {@link #STATE_DISABLED_BY_SERVICE} (because the autofill service disabled further autofill + * requests for the activity). */ private void setSessionFinished(int newState) { synchronized (mLock) { @@ -1482,10 +1506,10 @@ public final class AutofillManager { } } - private void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) { + private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { if (sVerbose) { Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id - + ", finished=" + sessionFinished); + + ", sessionFinishedState=" + sessionFinishedState); } final View anchor = findView(id); if (anchor == null) { @@ -1508,9 +1532,9 @@ public final class AutofillManager { } } - if (sessionFinished) { + if (sessionFinishedState != 0) { // Callback call was "hijacked" to also update the session state. - setSessionFinished(STATE_FINISHED); + setSessionFinished(sessionFinishedState); } } @@ -1613,6 +1637,8 @@ public final class AutofillManager { return "STATE_FINISHED"; case STATE_SHOWING_SAVE_UI: return "STATE_SHOWING_SAVE_UI"; + case STATE_DISABLED_BY_SERVICE: + return "STATE_DISABLED_BY_SERVICE"; default: return "INVALID:" + mState; } @@ -1622,8 +1648,8 @@ public final class AutofillManager { return mState == STATE_ACTIVE; } - private boolean isFinishedLocked() { - return mState == STATE_FINISHED; + private boolean isDisabledByService() { + return mState == STATE_DISABLED_BY_SERVICE; } private void post(Runnable runnable) { @@ -1957,10 +1983,10 @@ public final class AutofillManager { } @Override - public void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) { + public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) { final AutofillManager afm = mAfm.get(); if (afm != null) { - afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinished)); + afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinishedState)); } } diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java index 8c3b8a2e..26d2141e 100644 --- a/android/view/textclassifier/TextClassification.java +++ b/android/view/textclassifier/TextClassification.java @@ -33,6 +33,52 @@ import java.util.List; /** * Information for generating a widget to handle classified text. + * + *
A TextClassification object contains icons, labels, onClickListeners and intents that may + * be used to build a widget that can be used to act on classified text. + * + *
e.g. building a view that, when clicked, shares the classified text with the preferred app: + * + *
{@code + * // Called preferably outside the UiThread. + * TextClassification classification = textClassifier.classifyText(allText, 10, 25, null); + * + * // Called on the UiThread. + * Button button = new Button(context); + * button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null); + * button.setText(classification.getLabel()); + * button.setOnClickListener(classification.getOnClickListener()); + * }+ * + *
e.g. starting an action mode with menu items that can handle the classified text: + * + *
{@code + * // Called preferably outside the UiThread. + * final TextClassification classification = textClassifier.classifyText(allText, 10, 25, null); + * + * // Called on the UiThread. + * view.startActionMode(new ActionMode.Callback() { + * + * public boolean onCreateActionMode(ActionMode mode, Menu menu) { + * for (int i = 0; i < classification.getActionCount(); i++) { + * if (thisAppHasPermissionToInvokeIntent(classification.getIntent(i))) { + * menu.add(Menu.NONE, i, 20, classification.getLabel(i)) + * .setIcon(classification.getIcon(i)) + * .setIntent(classification.getIntent(i)); + * } + * } + * return true; + * } + * + * public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + * context.startActivity(item.getIntent()); + * return true; + * } + * + * ... + * }); + * }+ * */ public final class TextClassification { diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java index c3601d9d..46dbd0e3 100644 --- a/android/view/textclassifier/TextClassifier.java +++ b/android/view/textclassifier/TextClassifier.java @@ -29,8 +29,8 @@ import java.lang.annotation.RetentionPolicy; /** * Interface for providing text classification related features. * - *
Unless otherwise stated, methods of this interface are blocking operations and you should - * avoid calling them on the UI thread. + *
Unless otherwise stated, methods of this interface are blocking operations. + * Avoid calling them on the UI thread. */ public interface TextClassifier { @@ -75,8 +75,8 @@ public interface TextClassifier { }; /** - * Returns suggested text selection indices, recognized types and their associated confidence - * scores. The selections are ordered from highest to lowest scoring. + * Returns suggested text selection start and end indices, recognized entity types, and their + * associated confidence scores. The entity types are ordered from highest to lowest scoring. * * @param text text providing context for the selected text (which is specified * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java index 8e1f2183..f368c74a 100644 --- a/android/view/textservice/TextServicesManager.java +++ b/android/view/textservice/TextServicesManager.java @@ -1,58 +1,213 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2011 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 + * 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 + * 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. + * 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. */ package android.view.textservice; +import android.annotation.SystemService; +import android.content.Context; import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; +import android.util.Log; import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener; +import com.android.internal.textservice.ITextServicesManager; + import java.util.Locale; /** - * A stub class of TextServicesManager for Layout-Lib. + * System API to the overall text services, which arbitrates interaction between applications + * and text services. + * + * The user can change the current text services in Settings. And also applications can specify + * the target text services. + * + *
There are three primary parties involved in the text services + * framework (TSF) architecture:
+ * + *