diff options
author | Jason Monk <jmonk@google.com> | 2017-10-19 18:17:25 +0000 |
---|---|---|
committer | Jason Monk <jmonk@google.com> | 2017-10-19 18:17:25 +0000 |
commit | 07f9f65561c2b81bcd189b895b31bb2ad0438d74 (patch) | |
tree | 49f76f879a89c256a4f65b674086be50760bdffb /android/view | |
parent | d439404c9988df6001e4ff8bce31537e2692660e (diff) | |
download | android-28-07f9f65561c2b81bcd189b895b31bb2ad0438d74.tar.gz |
Revert "Import Android SDK Platform P [4402356]"
This reverts commit d439404c9988df6001e4ff8bce31537e2692660e.
Change-Id: I825790bdf38523800388bc1bb531cecfcd7e60bd
Diffstat (limited to 'android/view')
-rw-r--r-- | android/view/SurfaceControl.java | 538 | ||||
-rw-r--r-- | android/view/SurfaceView.java | 1135 | ||||
-rw-r--r-- | android/view/View.java | 262 | ||||
-rw-r--r-- | android/view/ViewDebug.java | 167 | ||||
-rw-r--r-- | android/view/ViewGroup.java | 98 | ||||
-rw-r--r-- | android/view/ViewRootImpl.java | 758 | ||||
-rw-r--r-- | android/view/ViewStructure.java | 24 | ||||
-rw-r--r-- | android/view/WindowManagerInternal.java | 4 | ||||
-rw-r--r-- | android/view/WindowManagerPolicy.java | 7 | ||||
-rw-r--r-- | android/view/accessibility/AccessibilityCache.java | 2 | ||||
-rw-r--r-- | android/view/accessibility/AccessibilityManager.java | 916 | ||||
-rw-r--r-- | android/view/autofill/AutofillManager.java | 149 | ||||
-rw-r--r-- | android/view/textclassifier/TextClassifier.java | 8 | ||||
-rw-r--r-- | android/view/textclassifier/TextClassifierConstants.java | 90 | ||||
-rw-r--r-- | android/view/textclassifier/TextClassifierImpl.java | 14 | ||||
-rw-r--r-- | android/view/textservice/TextServicesManager.java | 200 |
16 files changed, 2765 insertions, 1607 deletions
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java index 54825895..31daefff 100644 --- a/android/view/SurfaceControl.java +++ b/android/view/SurfaceControl.java @@ -21,22 +21,15 @@ import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import android.annotation.Size; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; -import android.graphics.Point; -import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.os.Binder; -import android.os.Debug; import android.os.IBinder; import android.util.Log; import android.view.Surface.OutOfResourcesException; import dalvik.system.CloseGuard; -import java.io.Closeable; - -import libcore.util.NativeAllocationRegistry; - /** * SurfaceControl * @hide @@ -61,34 +54,25 @@ public class SurfaceControl { Rect sourceCrop, int width, int height, int minLayer, int maxLayer, boolean allLayers, boolean useIdentityTransform); - private static native long nativeCreateTransaction(); - private static native long nativeGetNativeTransactionFinalizer(); - private static native void nativeApplyTransaction(long transactionObj, boolean sync); - private static native void nativeSetAnimationTransaction(long transactionObj); - - private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder); - private static native void nativeSetRelativeLayer(long transactionObj, long nativeObject, - IBinder relativeTo, int zorder); - private static native void nativeSetPosition(long transactionObj, long nativeObject, - float x, float y); - private static native void nativeSetGeometryAppliesWithResize(long transactionObj, - long nativeObject); - private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h); - private static native void nativeSetTransparentRegionHint(long transactionObj, - long nativeObject, Region region); - private static native void nativeSetAlpha(long transactionObj, long nativeObject, float alpha); - private static native void nativeSetMatrix(long transactionObj, long nativeObject, - float dsdx, float dtdx, + private static native void nativeOpenTransaction(); + private static native void nativeCloseTransaction(boolean sync); + private static native void nativeSetAnimationTransaction(); + + private static native void nativeSetLayer(long nativeObject, int zorder); + private static native void nativeSetRelativeLayer(long nativeObject, IBinder relativeTo, + int zorder); + private static native void nativeSetPosition(long nativeObject, float x, float y); + private static native void nativeSetGeometryAppliesWithResize(long nativeObject); + private static native void nativeSetSize(long nativeObject, int w, int h); + private static native void nativeSetTransparentRegionHint(long nativeObject, Region region); + private static native void nativeSetAlpha(long nativeObject, float alpha); + private static native void nativeSetColor(long nativeObject, float[] color); + private static native void nativeSetMatrix(long nativeObject, float dsdx, float dtdx, float dtdy, float dsdy); - private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color); - private static native void nativeSetFlags(long transactionObj, long nativeObject, - int flags, int mask); - private static native void nativeSetWindowCrop(long transactionObj, long nativeObject, - int l, int t, int r, int b); - private static native void nativeSetFinalCrop(long transactionObj, long nativeObject, - int l, int t, int r, int b); - private static native void nativeSetLayerStack(long transactionObj, long nativeObject, - int layerStack); + private static native void nativeSetFlags(long nativeObject, int flags, int mask); + private static native void nativeSetWindowCrop(long nativeObject, int l, int t, int r, int b); + private static native void nativeSetFinalCrop(long nativeObject, int l, int t, int r, int b); + private static native void nativeSetLayerStack(long nativeObject, int layerStack); private static native boolean nativeClearContentFrameStats(long nativeObject); private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats); @@ -98,16 +82,15 @@ public class SurfaceControl { private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId); private static native IBinder nativeCreateDisplay(String name, boolean secure); private static native void nativeDestroyDisplay(IBinder displayToken); - private static native void nativeSetDisplaySurface(long transactionObj, + private static native void nativeSetDisplaySurface( IBinder displayToken, long nativeSurfaceObject); - private static native void nativeSetDisplayLayerStack(long transactionObj, + private static native void nativeSetDisplayLayerStack( IBinder displayToken, int layerStack); - private static native void nativeSetDisplayProjection(long transactionObj, + private static native void nativeSetDisplayProjection( IBinder displayToken, int orientation, int l, int t, int r, int b, int L, int T, int R, int B); - private static native void nativeSetDisplaySize(long transactionObj, IBinder displayToken, - int width, int height); + private static native void nativeSetDisplaySize(IBinder displayToken, int width, int height); private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs( IBinder displayToken); private static native int nativeGetActiveConfig(IBinder displayToken); @@ -118,17 +101,16 @@ public class SurfaceControl { int colorMode); private static native void nativeSetDisplayPowerMode( IBinder displayToken, int mode); - private static native void nativeDeferTransactionUntil(long transactionObj, long nativeObject, + private static native void nativeDeferTransactionUntil(long nativeObject, IBinder handle, long frame); - private static native void nativeDeferTransactionUntilSurface(long transactionObj, - long nativeObject, + private static native void nativeDeferTransactionUntilSurface(long nativeObject, long surfaceObject, long frame); - private static native void nativeReparentChildren(long transactionObj, long nativeObject, + private static native void nativeReparentChildren(long nativeObject, IBinder handle); - private static native void nativeReparent(long transactionObj, long nativeObject, + private static native void nativeReparent(long nativeObject, IBinder parentHandle); - private static native void nativeSeverChildren(long transactionObj, long nativeObject); - private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject, + private static native void nativeSeverChildren(long nativeObject); + private static native void nativeSetOverrideScalingMode(long nativeObject, int scalingMode); private static native IBinder nativeGetHandle(long nativeObject); private static native boolean nativeGetTransformToDisplayInverse(long nativeObject); @@ -140,9 +122,6 @@ public class SurfaceControl { private final String mName; long mNativeObject; // package visibility only for Surface.java access - static Transaction sGlobalTransaction; - static long sTransactionNestCount = 0; - /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */ /** @@ -398,6 +377,11 @@ public class SurfaceControl { } } + @Override + public String toString() { + return "Surface(name=" + mName + ")"; + } + /** * Release the local reference to the server-side surface. * Always call release() when you're done with a Surface. @@ -445,141 +429,102 @@ public class SurfaceControl { /** start a transaction */ public static void openTransaction() { - synchronized (SurfaceControl.class) { - if (sGlobalTransaction == null) { - sGlobalTransaction = new Transaction(); - } - synchronized(SurfaceControl.class) { - sTransactionNestCount++; - } - } - } - - private static void closeTransaction(boolean sync) { - synchronized(SurfaceControl.class) { - if (sTransactionNestCount == 0) { - Log.e(TAG, "Call to SurfaceControl.closeTransaction without matching openTransaction"); - } else if (--sTransactionNestCount > 0) { - return; - } - sGlobalTransaction.apply(sync); - } + nativeOpenTransaction(); } /** end a transaction */ public static void closeTransaction() { - closeTransaction(false); + nativeCloseTransaction(false); } public static void closeTransactionSync() { - closeTransaction(true); + nativeCloseTransaction(true); } public void deferTransactionUntil(IBinder handle, long frame) { if (frame > 0) { - synchronized(SurfaceControl.class) { - sGlobalTransaction.deferTransactionUntil(this, handle, frame); - } + nativeDeferTransactionUntil(mNativeObject, handle, frame); } } public void deferTransactionUntil(Surface barrier, long frame) { if (frame > 0) { - synchronized(SurfaceControl.class) { - sGlobalTransaction.deferTransactionUntilSurface(this, barrier, frame); - } + nativeDeferTransactionUntilSurface(mNativeObject, barrier.mNativeObject, frame); } } public void reparentChildren(IBinder newParentHandle) { - synchronized(SurfaceControl.class) { - sGlobalTransaction.reparentChildren(this, newParentHandle); - } + nativeReparentChildren(mNativeObject, newParentHandle); } + /** Re-parents this layer to a new parent. */ public void reparent(IBinder newParentHandle) { - synchronized(SurfaceControl.class) { - sGlobalTransaction.reparent(this, newParentHandle); - } + nativeReparent(mNativeObject, newParentHandle); } public void detachChildren() { - synchronized(SurfaceControl.class) { - sGlobalTransaction.detachChildren(this); - } + nativeSeverChildren(mNativeObject); } public void setOverrideScalingMode(int scalingMode) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setOverrideScalingMode(this, scalingMode); - } + nativeSetOverrideScalingMode(mNativeObject, scalingMode); } public IBinder getHandle() { return nativeGetHandle(mNativeObject); } + /** flag the transaction as an animation */ public static void setAnimationTransaction() { - synchronized (SurfaceControl.class) { - sGlobalTransaction.setAnimationTransaction(); - } + nativeSetAnimationTransaction(); } public void setLayer(int zorder) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setLayer(this, zorder); - } + nativeSetLayer(mNativeObject, zorder); } public void setRelativeLayer(IBinder relativeTo, int zorder) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setRelativeLayer(this, relativeTo, zorder); - } + nativeSetRelativeLayer(mNativeObject, relativeTo, zorder); } public void setPosition(float x, float y) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setPosition(this, x, y); - } + nativeSetPosition(mNativeObject, x, y); } + /** + * If the buffer size changes in this transaction, position and crop updates specified + * in this transaction will not complete until a buffer of the new size + * arrives. As transform matrix and size are already frozen in this fashion, + * this enables totally freezing the surface until the resize has completed + * (at which point the geometry influencing aspects of this transaction will then occur) + */ public void setGeometryAppliesWithResize() { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setGeometryAppliesWithResize(this); - } + nativeSetGeometryAppliesWithResize(mNativeObject); } public void setSize(int w, int h) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setSize(this, w, h); - } + nativeSetSize(mNativeObject, w, h); } public void hide() { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.hide(this); - } + nativeSetFlags(mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN); } public void show() { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.show(this); - } + nativeSetFlags(mNativeObject, 0, SURFACE_HIDDEN); } public void setTransparentRegionHint(Region region) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setTransparentRegionHint(this, region); - } + nativeSetTransparentRegionHint(mNativeObject, region); } public boolean clearContentFrameStats() { @@ -600,70 +545,80 @@ public class SurfaceControl { return nativeGetAnimationFrameStats(outStats); } + /** + * Sets an alpha value for the entire Surface. This value is combined with the + * per-pixel alpha. It may be used with opaque Surfaces. + */ public void setAlpha(float alpha) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setAlpha(this, alpha); - } + nativeSetAlpha(mNativeObject, alpha); } + /** + * Sets a color for the Surface. + * @param color A float array with three values to represent r, g, b in range [0..1] + */ public void setColor(@Size(3) float[] color) { checkNotReleased(); - synchronized (SurfaceControl.class) { - sGlobalTransaction.setColor(this, color); - } + nativeSetColor(mNativeObject, color); } public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setMatrix(this, dsdx, dtdx, dtdy, dsdy); - } + nativeSetMatrix(mNativeObject, dsdx, dtdx, dtdy, dsdy); } public void setWindowCrop(Rect crop) { checkNotReleased(); - synchronized (SurfaceControl.class) { - sGlobalTransaction.setWindowCrop(this, crop); + if (crop != null) { + nativeSetWindowCrop(mNativeObject, + crop.left, crop.top, crop.right, crop.bottom); + } else { + nativeSetWindowCrop(mNativeObject, 0, 0, 0, 0); } } public void setFinalCrop(Rect crop) { checkNotReleased(); - synchronized (SurfaceControl.class) { - sGlobalTransaction.setFinalCrop(this, crop); + if (crop != null) { + nativeSetFinalCrop(mNativeObject, + crop.left, crop.top, crop.right, crop.bottom); + } else { + nativeSetFinalCrop(mNativeObject, 0, 0, 0, 0); } } public void setLayerStack(int layerStack) { checkNotReleased(); - synchronized(SurfaceControl.class) { - sGlobalTransaction.setLayerStack(this, layerStack); - } + nativeSetLayerStack(mNativeObject, layerStack); } + /** + * Sets the opacity of the surface. Setting the flag is equivalent to creating the + * Surface with the {@link #OPAQUE} flag. + */ public void setOpaque(boolean isOpaque) { checkNotReleased(); - - synchronized (SurfaceControl.class) { - sGlobalTransaction.setOpaque(this, isOpaque); + if (isOpaque) { + nativeSetFlags(mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE); + } else { + nativeSetFlags(mNativeObject, 0, SURFACE_OPAQUE); } } + /** + * Sets the security of the surface. Setting the flag is equivalent to creating the + * Surface with the {@link #SECURE} flag. + */ public void setSecure(boolean isSecure) { checkNotReleased(); - - synchronized (SurfaceControl.class) { - sGlobalTransaction.setSecure(this, isSecure); + if (isSecure) { + nativeSetFlags(mNativeObject, SECURE, SECURE); + } else { + nativeSetFlags(mNativeObject, 0, SECURE); } } - @Override - public String toString() { - return "Surface(name=" + mName + ")/@0x" + - Integer.toHexString(System.identityHashCode(this)); - } - /* * set display parameters. * needs to be inside open/closeTransaction block @@ -786,28 +741,50 @@ public class SurfaceControl { public static void setDisplayProjection(IBinder displayToken, int orientation, Rect layerStackRect, Rect displayRect) { - synchronized (SurfaceControl.class) { - sGlobalTransaction.setDisplayProjection(displayToken, orientation, - layerStackRect, displayRect); + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + if (layerStackRect == null) { + throw new IllegalArgumentException("layerStackRect must not be null"); } + if (displayRect == null) { + throw new IllegalArgumentException("displayRect must not be null"); + } + nativeSetDisplayProjection(displayToken, orientation, + layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom, + displayRect.left, displayRect.top, displayRect.right, displayRect.bottom); } public static void setDisplayLayerStack(IBinder displayToken, int layerStack) { - synchronized (SurfaceControl.class) { - sGlobalTransaction.setDisplayLayerStack(displayToken, layerStack); + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); } + nativeSetDisplayLayerStack(displayToken, layerStack); } public static void setDisplaySurface(IBinder displayToken, Surface surface) { - synchronized (SurfaceControl.class) { - sGlobalTransaction.setDisplaySurface(displayToken, surface); + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + + if (surface != null) { + synchronized (surface.mLock) { + nativeSetDisplaySurface(displayToken, surface.mNativeObject); + } + } else { + nativeSetDisplaySurface(displayToken, 0); } } public static void setDisplaySize(IBinder displayToken, int width, int height) { - synchronized (SurfaceControl.class) { - sGlobalTransaction.setDisplaySize(displayToken, width, height); + if (displayToken == null) { + throw new IllegalArgumentException("displayToken must not be null"); + } + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("width and height must be positive"); } + + nativeSetDisplaySize(displayToken, width, height); } public static Display.HdrCapabilities getHdrCapabilities(IBinder displayToken) { @@ -969,261 +946,4 @@ public class SurfaceControl { nativeScreenshot(display, consumer, sourceCrop, width, height, minLayer, maxLayer, allLayers, useIdentityTransform); } - - public static class Transaction implements Closeable { - public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - Transaction.class.getClassLoader(), - nativeGetNativeTransactionFinalizer(), 512); - private long mNativeObject; - - Runnable mFreeNativeResources; - - public Transaction() { - mNativeObject = nativeCreateTransaction(); - mFreeNativeResources - = sRegistry.registerNativeAllocation(this, mNativeObject); - } - - /** - * Apply the transaction, clearing it's state, and making it usable - * as a new transaction. - */ - public void apply() { - apply(false); - } - - /** - * Close the transaction, if the transaction was not already applied this will cancel the - * transaction. - */ - @Override - public void close() { - mFreeNativeResources.run(); - mNativeObject = 0; - } - - /** - * Jankier version of apply. Avoid use (b/28068298). - */ - public void apply(boolean sync) { - nativeApplyTransaction(mNativeObject, sync); - } - - public Transaction show(SurfaceControl sc) { - nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN); - return this; - } - - public Transaction hide(SurfaceControl sc) { - nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN); - return this; - } - - public Transaction setPosition(SurfaceControl sc, float x, float y) { - nativeSetPosition(mNativeObject, sc.mNativeObject, x, y); - return this; - } - - public Transaction setSize(SurfaceControl sc, int w, int h) { - nativeSetSize(mNativeObject, sc.mNativeObject, - w, h); - return this; - } - - public Transaction setLayer(SurfaceControl sc, int z) { - nativeSetLayer(mNativeObject, sc.mNativeObject, z); - return this; - } - - public Transaction setRelativeLayer(SurfaceControl sc, IBinder relativeTo, int z) { - nativeSetRelativeLayer(mNativeObject, sc.mNativeObject, - relativeTo, z); - return this; - } - - public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) { - nativeSetTransparentRegionHint(mNativeObject, - sc.mNativeObject, transparentRegion); - return this; - } - - public Transaction setAlpha(SurfaceControl sc, float alpha) { - nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha); - return this; - } - - public Transaction setMatrix(SurfaceControl sc, - float dsdx, float dtdx, float dtdy, float dsdy) { - nativeSetMatrix(mNativeObject, sc.mNativeObject, - dsdx, dtdx, dtdy, dsdy); - return this; - } - - public Transaction setWindowCrop(SurfaceControl sc, Rect crop) { - if (crop != null) { - nativeSetWindowCrop(mNativeObject, sc.mNativeObject, - crop.left, crop.top, crop.right, crop.bottom); - } else { - nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0); - } - - return this; - } - - public Transaction setFinalCrop(SurfaceControl sc, Rect crop) { - if (crop != null) { - nativeSetFinalCrop(mNativeObject, sc.mNativeObject, - crop.left, crop.top, crop.right, crop.bottom); - } else { - nativeSetFinalCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0); - } - - return this; - } - - public Transaction setLayerStack(SurfaceControl sc, int layerStack) { - nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack); - return this; - } - - public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, long frameNumber) { - nativeDeferTransactionUntil(mNativeObject, sc.mNativeObject, handle, frameNumber); - return this; - } - - public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface, - long frameNumber) { - nativeDeferTransactionUntilSurface(mNativeObject, sc.mNativeObject, - barrierSurface.mNativeObject, frameNumber); - return this; - } - - public Transaction reparentChildren(SurfaceControl sc, IBinder newParentHandle) { - nativeReparentChildren(mNativeObject, sc.mNativeObject, newParentHandle); - return this; - } - - /** Re-parents a specific child layer to a new parent */ - public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) { - nativeReparent(mNativeObject, sc.mNativeObject, - newParentHandle); - return this; - } - - public Transaction detachChildren(SurfaceControl sc) { - nativeSeverChildren(mNativeObject, sc.mNativeObject); - return this; - } - - public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) { - nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject, - overrideScalingMode); - return this; - } - - /** - * Sets a color for the Surface. - * @param color A float array with three values to represent r, g, b in range [0..1] - */ - public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) { - nativeSetColor(mNativeObject, sc.mNativeObject, color); - return this; - } - - /** - * If the buffer size changes in this transaction, position and crop updates specified - * in this transaction will not complete until a buffer of the new size - * arrives. As transform matrix and size are already frozen in this fashion, - * this enables totally freezing the surface until the resize has completed - * (at which point the geometry influencing aspects of this transaction will then occur) - */ - public Transaction setGeometryAppliesWithResize(SurfaceControl sc) { - nativeSetGeometryAppliesWithResize(mNativeObject, sc.mNativeObject); - return this; - } - - /** - * Sets the security of the surface. Setting the flag is equivalent to creating the - * Surface with the {@link #SECURE} flag. - */ - Transaction setSecure(SurfaceControl sc, boolean isSecure) { - if (isSecure) { - nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE); - } else { - nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SECURE); - } - return this; - } - - /** - * Sets the opacity of the surface. Setting the flag is equivalent to creating the - * Surface with the {@link #OPAQUE} flag. - */ - public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) { - if (isOpaque) { - nativeSetFlags(mNativeObject, sc.mNativeObject, OPAQUE, OPAQUE); - } else { - nativeSetFlags(mNativeObject, sc.mNativeObject, 0, OPAQUE); - } - return this; - } - - public Transaction setDisplaySurface(IBinder displayToken, Surface surface) { - if (displayToken == null) { - throw new IllegalArgumentException("displayToken must not be null"); - } - - if (surface != null) { - synchronized (surface.mLock) { - nativeSetDisplaySurface(mNativeObject, displayToken, surface.mNativeObject); - } - } else { - nativeSetDisplaySurface(mNativeObject, displayToken, 0); - } - return this; - } - - public Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) { - if (displayToken == null) { - throw new IllegalArgumentException("displayToken must not be null"); - } - nativeSetDisplayLayerStack(mNativeObject, displayToken, layerStack); - return this; - } - - public Transaction setDisplayProjection(IBinder displayToken, - int orientation, Rect layerStackRect, Rect displayRect) { - if (displayToken == null) { - throw new IllegalArgumentException("displayToken must not be null"); - } - if (layerStackRect == null) { - throw new IllegalArgumentException("layerStackRect must not be null"); - } - if (displayRect == null) { - throw new IllegalArgumentException("displayRect must not be null"); - } - nativeSetDisplayProjection(mNativeObject, displayToken, orientation, - layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom, - displayRect.left, displayRect.top, displayRect.right, displayRect.bottom); - return this; - } - - public Transaction setDisplaySize(IBinder displayToken, int width, int height) { - if (displayToken == null) { - throw new IllegalArgumentException("displayToken must not be null"); - } - if (width <= 0 || height <= 0) { - throw new IllegalArgumentException("width and height must be positive"); - } - - nativeSetDisplaySize(mNativeObject, displayToken, width, height); - return this; - } - - /** flag the transaction as an animation */ - public Transaction setAnimationTransaction() { - nativeSetAnimationTransaction(mNativeObject); - return this; - } - } } diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java index ebb2af45..462dad3f 100644 --- a/android/view/SurfaceView.java +++ b/android/view/SurfaceView.java @@ -16,115 +16,1208 @@ 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 + * + * <p>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. + * + * <p> 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. + * <p>Access to the underlying surface is provided via the SurfaceHolder interface, + * which can be retrieved by calling {@link #getHolder}. * + * <p>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. + * + * <p>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: + * + * <ul> + * <li> All SurfaceView and + * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called + * from the thread running the SurfaceView's window (typically the main thread + * of the application). They thus need to correctly synchronize with any + * state that is also touched by the drawing thread. + * <li> You must ensure that the drawing thread only touches the underlying + * Surface while it is valid -- between + * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()} + * and + * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}. + * </ul> + * + * <p class="note"><strong>Note:</strong> 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.</p> */ -public class SurfaceView extends MockView { +public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback { + private static final String TAG = "SurfaceView"; + private static final boolean DEBUG = false; + + final ArrayList<SurfaceHolder.Callback> mCallbacks + = new ArrayList<SurfaceHolder.Callback>(); + + final int[] mLocation = new int[2]; + + final ReentrantLock mSurfaceLock = new ReentrantLock(); + final Surface mSurface = new Surface(); // Current surface in use + boolean mDrawingStopped = true; + // We use this to track if the application has produced a frame + // in to the Surface. Up until that point, we should be careful not to punch + // holes. + boolean mDrawFinished = false; + + final Rect mScreenRect = new Rect(); + SurfaceSession mSurfaceSession; + + SurfaceControl mSurfaceControl; + // In the case of format changes we switch out the surface in-place + // we need to preserve the old one until the new one has drawn. + SurfaceControl mDeferredDestroySurfaceControl; + final Rect mTmpRect = new Rect(); + final Configuration mConfiguration = new Configuration(); + + int mSubLayer = APPLICATION_MEDIA_SUBLAYER; + + boolean mIsCreating = false; + private volatile boolean mRtHandlingPositionUpdates = false; + + private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener + = new ViewTreeObserver.OnScrollChangedListener() { + @Override + public void onScrollChanged() { + updateSurface(); + } + }; + + private final ViewTreeObserver.OnPreDrawListener mDrawListener = + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + // reposition ourselves where the surface is + mHaveFrame = getWidth() > 0 && getHeight() > 0; + updateSurface(); + return true; + } + }; + + boolean mRequestedVisible = false; + boolean mWindowVisibility = false; + boolean mLastWindowVisibility = false; + boolean mViewVisibility = false; + boolean mWindowStopped = false; + + int mRequestedWidth = -1; + int mRequestedHeight = -1; + /* Set SurfaceView's format to 565 by default to maintain backward + * compatibility with applications assuming this format. + */ + int mRequestedFormat = PixelFormat.RGB_565; + + boolean mHaveFrame = false; + boolean mSurfaceCreated = false; + long mLastLockTime = 0; + + boolean mVisible = false; + int mWindowSpaceLeft = -1; + int mWindowSpaceTop = -1; + int mSurfaceWidth = -1; + int mSurfaceHeight = -1; + int mFormat = -1; + final Rect mSurfaceFrame = new Rect(); + int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; + private Translator mTranslator; + + private boolean mGlobalListenersAdded; + private boolean mAttachedToWindow; + + private int mSurfaceFlags = SurfaceControl.HIDDEN; + + private int mPendingReportDraws; public SurfaceView(Context context) { this(context, null); } public SurfaceView(Context context, AttributeSet attrs) { - this(context, attrs , 0); + this(context, attrs, 0); } - public SurfaceView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mRenderNode.requestPositionUpdates(this); + + setWillNotDraw(true); + } + + /** + * Return the SurfaceHolder providing access and control over this + * SurfaceView's underlying surface. + * + * @return SurfaceHolder The holder of the surface. + */ + public SurfaceHolder getHolder() { + return mSurfaceHolder; + } + + private void updateRequestedVisibility() { + mRequestedVisible = mViewVisibility && mWindowVisibility && !mWindowStopped; + } + + /** @hide */ + @Override + public void windowStopped(boolean stopped) { + mWindowStopped = stopped; + updateRequestedVisibility(); + updateSurface(); } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + getViewRootImpl().addWindowStoppedCallback(this); + mWindowStopped = false; + + mViewVisibility = getVisibility() == VISIBLE; + updateRequestedVisibility(); + + mAttachedToWindow = true; + mParent.requestTransparentRegion(SurfaceView.this); + if (!mGlobalListenersAdded) { + ViewTreeObserver observer = getViewTreeObserver(); + observer.addOnScrollChangedListener(mScrollChangedListener); + observer.addOnPreDrawListener(mDrawListener); + mGlobalListenersAdded = true; + } + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + mWindowVisibility = visibility == VISIBLE; + updateRequestedVisibility(); + updateSurface(); + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + mViewVisibility = visibility == VISIBLE; + boolean newRequestedVisible = mWindowVisibility && mViewVisibility && !mWindowStopped; + if (newRequestedVisible != mRequestedVisible) { + // our base class (View) invalidates the layout only when + // we go from/to the GONE state. However, SurfaceView needs + // to request a re-layout when the visibility changes at all. + // This is needed because the transparent region is computed + // as part of the layout phase, and it changes (obviously) when + // the visibility changes. + requestLayout(); + } + mRequestedVisible = newRequestedVisible; + updateSurface(); + } + + private void performDrawFinished() { + if (mPendingReportDraws > 0) { + mDrawFinished = true; + if (mAttachedToWindow) { + notifyDrawFinished(); + invalidate(); + } + } else { + Log.e(TAG, System.identityHashCode(this) + "finished drawing" + + " but no pending report draw (extra call" + + " to draw completion runnable?)"); + } + } + + void notifyDrawFinished() { + ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot != null) { + viewRoot.pendingDrawFinished(); + } + mPendingReportDraws--; + } + + @Override + protected void onDetachedFromWindow() { + ViewRootImpl viewRoot = getViewRootImpl(); + // It's possible to create a SurfaceView using the default constructor and never + // attach it to a view hierarchy, this is a common use case when dealing with + // OpenGL. A developer will probably create a new GLSurfaceView, and let it manage + // the lifecycle. Instead of attaching it to a view, he/she can just pass + // the SurfaceHolder forward, most live wallpapers do it. + if (viewRoot != null) { + viewRoot.removeWindowStoppedCallback(this); + } + + mAttachedToWindow = false; + if (mGlobalListenersAdded) { + ViewTreeObserver observer = getViewTreeObserver(); + observer.removeOnScrollChangedListener(mScrollChangedListener); + observer.removeOnPreDrawListener(mDrawListener); + mGlobalListenersAdded = false; + } + + while (mPendingReportDraws > 0) { + notifyDrawFinished(); + } + + mRequestedVisible = false; + + updateSurface(); + if (mSurfaceControl != null) { + mSurfaceControl.destroy(); + } + mSurfaceControl = null; + + mHaveFrame = false; + + super.onDetachedFromWindow(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = mRequestedWidth >= 0 + ? resolveSizeAndState(mRequestedWidth, widthMeasureSpec, 0) + : getDefaultSize(0, widthMeasureSpec); + int height = mRequestedHeight >= 0 + ? resolveSizeAndState(mRequestedHeight, heightMeasureSpec, 0) + : getDefaultSize(0, heightMeasureSpec); + setMeasuredDimension(width, height); + } + + /** @hide */ + @Override + protected boolean setFrame(int left, int top, int right, int bottom) { + boolean result = super.setFrame(left, top, right, bottom); + updateSurface(); + return result; + } + + @Override public boolean gatherTransparentRegion(Region region) { - return false; + if (isAboveParent() || !mDrawFinished) { + return super.gatherTransparentRegion(region); + } + + boolean opaque = true; + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) { + // this view draws, remove it from the transparent region + opaque = super.gatherTransparentRegion(region); + } else if (region != null) { + int w = getWidth(); + int h = getHeight(); + if (w>0 && h>0) { + getLocationInWindow(mLocation); + // otherwise, punch a hole in the whole hierarchy + int l = mLocation[0]; + int t = mLocation[1]; + region.op(l, t, l+w, t+h, Region.Op.UNION); + } + } + if (PixelFormat.formatHasAlpha(mRequestedFormat)) { + opaque = false; + } + return opaque; } + @Override + public void draw(Canvas canvas) { + if (mDrawFinished && !isAboveParent()) { + // draw() is not called when SKIP_DRAW is set + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) { + // punch a whole in the view-hierarchy below us + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } + } + super.draw(canvas); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + if (mDrawFinished && !isAboveParent()) { + // draw() is not called when SKIP_DRAW is set + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { + // punch a whole in the view-hierarchy below us + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } + } + super.dispatchDraw(canvas); + } + + /** + * Control whether the surface view's surface is placed on top of another + * regular surface view in the window (but still behind the window itself). + * This is typically used to place overlays on top of an underlying media + * surface view. + * + * <p>Note that this must be set before the surface view's containing + * window is attached to the window manager. + * + * <p>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. + * + * <p>Note that this must be set before the surface view's containing + * window is attached to the window manager. + * + * <p>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. + * + * <p>Note that this must be set before the surface view's containing + * window is attached to the window manager. + * + * <p>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(); + mSurfaceControl = new SurfaceControlWithBackground(mSurfaceSession, + "SurfaceView - " + viewRoot.getTitle().toString(), + mSurfaceWidth, mSurfaceHeight, mFormat, + 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 <code>null</code> instead, in the case where the + * entire surface should be redrawn. + * @return A canvas for drawing into the surface. + */ @Override - public Canvas lockCanvas(Rect dirty) { + public Canvas lockCanvas(Rect inOutDirty) { + return internalLockCanvas(inOutDirty, false); + } + + @Override + 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(SurfaceSession s, + String name, int w, int h, int format, int flags) + throws Exception { + super(s, name, w, h, format, flags); + mBackgroundControl = new SurfaceControl(s, "Background for - " + name, w, h, + PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM); + mOpaque = (flags & SurfaceControl.OPAQUE) != 0; + } + + @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/View.java b/android/view/View.java index c043dcac..b6be2961 100644 --- a/android/view/View.java +++ b/android/view/View.java @@ -1448,59 +1448,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * <p>Enables low quality mode for the drawing cache.</p> - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public static final int DRAWING_CACHE_QUALITY_LOW = 0x00080000; /** * <p>Enables high quality mode for the drawing cache.</p> - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public static final int DRAWING_CACHE_QUALITY_HIGH = 0x00100000; /** * <p>Enables automatic quality mode for the drawing cache.</p> - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public static final int DRAWING_CACHE_QUALITY_AUTO = 0x00000000; private static final int[] DRAWING_CACHE_QUALITY_FLAGS = { @@ -2342,9 +2300,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static final int PFLAG_HOVERED = 0x10000000; /** - * Flag set by {@link AutofillManager} if it needs to be notified when this view is clicked. + * no longer needed, should be reused */ - private static final int PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK = 0x20000000; + private static final int PFLAG_DOES_NOTHING_REUSE_PLEASE = 0x20000000; /** {@hide} */ static final int PFLAG_ACTIVATED = 0x40000000; @@ -6397,42 +6355,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return null; } - /** @hide */ - public void setNotifyAutofillManagerOnClick(boolean notify) { - if (notify) { - mPrivateFlags |= PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK; - } else { - mPrivateFlags &= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK; - } - } - - private void notifyAutofillManagerOnClick() { - if ((mPrivateFlags & PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK) != 0) { - try { - getAutofillManager().notifyViewClicked(this); - } finally { - // Set it to already called so it's not called twice when called by - // performClickInternal() - mPrivateFlags |= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK; - } - } - } - - /** - * Entry point for {@link #performClick()} - other methods on View should call it instead of - * {@code performClick()} directly to make sure the autofill manager is notified when - * necessary (as subclasses could extend {@code performClick()} without calling the parent's - * method). - */ - private boolean performClickInternal() { - // Must notify autofill manager before performing the click actions to avoid scenarios where - // the app has a click listener that changes the state of views the autofill service might - // be interested on. - notifyAutofillManagerOnClick(); - - return performClick(); - } - /** * Call this view's OnClickListener, if it is defined. Performs all normal * actions associated with clicking: reporting accessibility event, playing @@ -6441,14 +6363,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return True there was an assigned OnClickListener that was called, false * otherwise is returned. */ - // NOTE: other methods on View should not call this method directly, but performClickInternal() - // instead, to guarantee that the autofill manager is notified when necessary (as subclasses - // could extend this method without calling super.performClick()). public boolean performClick() { - // We still need to call this method to handle the cases where performClick() was called - // externally, instead of through performClickInternal() - notifyAutofillManagerOnClick(); - final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { @@ -8992,21 +8907,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #isDrawingCacheEnabled() * * @attr ref android.R.styleable#View_drawingCacheQuality - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated @DrawingCacheQuality public int getDrawingCacheQuality() { return mViewFlags & DRAWING_CACHE_QUALITY_MASK; @@ -9024,21 +8925,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #isDrawingCacheEnabled() * * @attr ref android.R.styleable#View_drawingCacheQuality - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public void setDrawingCacheQuality(@DrawingCacheQuality int quality) { setFlags(quality, DRAWING_CACHE_QUALITY_MASK); } @@ -11546,7 +11433,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: { if (isClickable()) { - performClickInternal(); + performClick(); return true; } } break; @@ -12658,7 +12545,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // This is a tap, so remove the longpress check removeLongPressCallback(); if (!event.isCanceled()) { - return performClickInternal(); + return performClick(); } } } @@ -13230,7 +13117,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { - performClickInternal(); + performClick(); } } } @@ -18216,21 +18103,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #getDrawingCache() * @see #buildDrawingCache() * @see #setLayerType(int, android.graphics.Paint) - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public void setDrawingCacheEnabled(boolean enabled) { mCachingFailed = false; setFlags(enabled ? DRAWING_CACHE_ENABLED : 0, DRAWING_CACHE_ENABLED); @@ -18243,21 +18116,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #setDrawingCacheEnabled(boolean) * @see #getDrawingCache() - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated @ViewDebug.ExportedProperty(category = "drawing") public boolean isDrawingCacheEnabled() { return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED; @@ -18271,11 +18130,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @SuppressWarnings({"UnusedDeclaration"}) public void outputDirtyFlags(String indent, boolean clear, int clearMask) { - Log.d(VIEW_LOG_TAG, indent + this + " DIRTY(" - + (mPrivateFlags & View.PFLAG_DIRTY_MASK) - + ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID(" - + (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID) - + ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")"); + Log.d("View", indent + this + " DIRTY(" + (mPrivateFlags & View.PFLAG_DIRTY_MASK) + + ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID(" + + (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID) + + ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")"); if (clear) { mPrivateFlags &= clearMask; } @@ -18399,21 +18257,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return A non-scaled bitmap representing this view or null if cache is disabled. * * @see #getDrawingCache(boolean) - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public Bitmap getDrawingCache() { return getDrawingCache(false); } @@ -18444,21 +18288,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #isDrawingCacheEnabled() * @see #buildDrawingCache(boolean) * @see #destroyDrawingCache() - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public Bitmap getDrawingCache(boolean autoScale) { if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) { return null; @@ -18478,21 +18308,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #setDrawingCacheEnabled(boolean) * @see #buildDrawingCache() * @see #getDrawingCache() - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public void destroyDrawingCache() { if (mDrawingCache != null) { mDrawingCache.recycle(); @@ -18514,21 +18330,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #setDrawingCacheEnabled(boolean) * @see #buildDrawingCache() * @see #getDrawingCache() - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public void setDrawingCacheBackgroundColor(@ColorInt int color) { if (color != mDrawingCacheBackgroundColor) { mDrawingCacheBackgroundColor = color; @@ -18540,21 +18342,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #setDrawingCacheBackgroundColor(int) * * @return The background color to used for the drawing cache's bitmap - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated @ColorInt public int getDrawingCacheBackgroundColor() { return mDrawingCacheBackgroundColor; @@ -18564,21 +18352,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <p>Calling this method is equivalent to calling <code>buildDrawingCache(false)</code>.</p> * * @see #buildDrawingCache(boolean) - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public void buildDrawingCache() { buildDrawingCache(false); } @@ -18605,21 +18379,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #getDrawingCache() * @see #destroyDrawingCache() - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public void buildDrawingCache(boolean autoScale) { if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ? mDrawingCache == null : mUnscaledDrawingCache == null)) { @@ -20052,7 +19812,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, boolean changed = false; if (DBG) { - Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + "," + Log.d("View", this + " View.setFrame(" + left + "," + top + "," + right + "," + bottom + ")"); } @@ -25098,7 +24858,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private final class PerformClick implements Runnable { @Override public void run() { - performClickInternal(); + performClick(); } } diff --git a/android/view/ViewDebug.java b/android/view/ViewDebug.java index afa94131..3426485e 100644 --- a/android/view/ViewDebug.java +++ b/android/view/ViewDebug.java @@ -528,23 +528,84 @@ public class ViewDebug { /** @hide */ public static void profileViewAndChildren(final View view, BufferedWriter out) throws IOException { - RenderNode node = RenderNode.create("ViewDebug", null); - profileViewAndChildren(view, node, out, true); - node.destroy(); + profileViewAndChildren(view, out, true); } - private static void profileViewAndChildren(View view, RenderNode node, BufferedWriter out, - boolean root) throws IOException { + private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root) + throws IOException { + long durationMeasure = (root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0) - ? profileViewMeasure(view) : 0; + ? profileViewOperation(view, new ViewOperation<Void>() { + public Void[] pre() { + forceLayout(view); + return null; + } + + private void forceLayout(View view) { + view.forceLayout(); + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + final int count = group.getChildCount(); + for (int i = 0; i < count; i++) { + forceLayout(group.getChildAt(i)); + } + } + } + + public void run(Void... data) { + view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec); + } + + public void post(Void... data) { + } + }) + : 0; long durationLayout = (root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0) - ? profileViewLayout(view) : 0; + ? profileViewOperation(view, new ViewOperation<Void>() { + public Void[] pre() { + return null; + } + + public void run(Void... data) { + view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom); + } + + public void post(Void... data) { + } + }) : 0; long durationDraw = (root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0) - ? profileViewDraw(view, node) : 0; + ? profileViewOperation(view, new ViewOperation<Object>() { + public Object[] pre() { + final DisplayMetrics metrics = + (view != null && view.getResources() != null) ? + view.getResources().getDisplayMetrics() : null; + final Bitmap bitmap = metrics != null ? + Bitmap.createBitmap(metrics, metrics.widthPixels, + metrics.heightPixels, Bitmap.Config.RGB_565) : null; + final Canvas canvas = bitmap != null ? new Canvas(bitmap) : null; + return new Object[] { + bitmap, canvas + }; + } + public void run(Object... data) { + if (data[1] != null) { + view.draw((Canvas) data[1]); + } + } + + public void post(Object... data) { + if (data[1] != null) { + ((Canvas) data[1]).setBitmap(null); + } + if (data[0] != null) { + ((Bitmap) data[0]).recycle(); + } + } + }) : 0; out.write(String.valueOf(durationMeasure)); out.write(' '); out.write(String.valueOf(durationLayout)); @@ -555,86 +616,34 @@ public class ViewDebug { ViewGroup group = (ViewGroup) view; final int count = group.getChildCount(); for (int i = 0; i < count; i++) { - profileViewAndChildren(group.getChildAt(i), node, out, false); - } - } - } - - private static long profileViewMeasure(final View view) { - return profileViewOperation(view, new ViewOperation() { - @Override - public void pre() { - forceLayout(view); - } - - private void forceLayout(View view) { - view.forceLayout(); - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - final int count = group.getChildCount(); - for (int i = 0; i < count; i++) { - forceLayout(group.getChildAt(i)); - } - } - } - - @Override - public void run() { - view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec); - } - }); - } - - private static long profileViewLayout(View view) { - return profileViewOperation(view, - () -> view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom)); - } - - private static long profileViewDraw(View view, RenderNode node) { - DisplayMetrics dm = view.getResources().getDisplayMetrics(); - if (dm == null) { - return 0; - } - - if (view.isHardwareAccelerated()) { - DisplayListCanvas canvas = node.start(dm.widthPixels, dm.heightPixels); - try { - return profileViewOperation(view, () -> view.draw(canvas)); - } finally { - node.end(canvas); - } - } else { - Bitmap bitmap = Bitmap.createBitmap( - dm, dm.widthPixels, dm.heightPixels, Bitmap.Config.RGB_565); - Canvas canvas = new Canvas(bitmap); - try { - return profileViewOperation(view, () -> view.draw(canvas)); - } finally { - canvas.setBitmap(null); - bitmap.recycle(); + profileViewAndChildren(group.getChildAt(i), out, false); } } } - interface ViewOperation { - default void pre() {} - - void run(); + interface ViewOperation<T> { + T[] pre(); + void run(T... data); + void post(T... data); } - private static long profileViewOperation(View view, final ViewOperation operation) { + private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) { final CountDownLatch latch = new CountDownLatch(1); final long[] duration = new long[1]; - view.post(() -> { - try { - operation.pre(); - long start = Debug.threadCpuTimeNanos(); - //noinspection unchecked - operation.run(); - duration[0] = Debug.threadCpuTimeNanos() - start; - } finally { - latch.countDown(); + view.post(new Runnable() { + public void run() { + try { + T[] data = operation.pre(); + long start = Debug.threadCpuTimeNanos(); + //noinspection unchecked + operation.run(data); + duration[0] = Debug.threadCpuTimeNanos() - start; + //noinspection unchecked + operation.post(data); + } finally { + latch.countDown(); + } } }); diff --git a/android/view/ViewGroup.java b/android/view/ViewGroup.java index 929beaea..b2e5a163 100644 --- a/android/view/ViewGroup.java +++ b/android/view/ViewGroup.java @@ -421,78 +421,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * Used to indicate that no drawing cache should be kept in memory. - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public static final int PERSISTENT_NO_CACHE = 0x0; /** * Used to indicate that the animation drawing cache should be kept in memory. - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public static final int PERSISTENT_ANIMATION_CACHE = 0x1; /** * Used to indicate that the scrolling drawing cache should be kept in memory. - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public static final int PERSISTENT_SCROLLING_CACHE = 0x2; /** * Used to indicate that all drawing caches should be kept in memory. - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public static final int PERSISTENT_ALL_CACHES = 0x3; // Layout Modes @@ -3825,21 +3769,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * Enables or disables the drawing cache for each child of this view group. * * @param enabled true to enable the cache, false to dispose of it - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated protected void setChildrenDrawingCacheEnabled(boolean enabled) { if (enabled || (mPersistentDrawingCache & PERSISTENT_ALL_CACHES) != PERSISTENT_ALL_CACHES) { final View[] children = mChildren; @@ -6401,21 +6331,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @return one or a combination of {@link #PERSISTENT_NO_CACHE}, * {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE} * and {@link #PERSISTENT_ALL_CACHES} - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated @ViewDebug.ExportedProperty(category = "drawing", mapping = { @ViewDebug.IntToString(from = PERSISTENT_NO_CACHE, to = "NONE"), @ViewDebug.IntToString(from = PERSISTENT_ANIMATION_CACHE, to = "ANIMATION"), @@ -6436,21 +6352,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @param drawingCacheToKeep one or a combination of {@link #PERSISTENT_NO_CACHE}, * {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE} * and {@link #PERSISTENT_ALL_CACHES} - * - * @deprecated The view drawing cache was largely made obsolete with the introduction of - * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache - * layers are largely unnecessary and can easily result in a net loss in performance due to the - * cost of creating and updating the layer. In the rare cases where caching layers are useful, - * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware - * rendering. For software-rendered snapshots of a small part of the View hierarchy or - * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or - * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these - * software-rendered usages are discouraged and have compatibility issues with hardware-only - * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE} - * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback - * reports or unit testing the {@link PixelCopy} API is recommended. */ - @Deprecated public void setPersistentDrawingCache(int drawingCacheToKeep) { mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES; } diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java index 99438d87..71106ada 100644 --- a/android/view/ViewRootImpl.java +++ b/android/view/ViewRootImpl.java @@ -72,7 +72,6 @@ import android.util.DisplayMetrics; import android.util.Log; import android.util.MergedConfiguration; import android.util.Slog; -import android.util.SparseArray; import android.util.TimeUtils; import android.util.TypedValue; import android.view.Surface.OutOfResourcesException; @@ -1669,6 +1668,8 @@ public final class ViewRootImpl implements ViewParent, host.dispatchAttachedToWindow(mAttachInfo, 0); mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); dispatchApplyInsets(host); + //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn); + } else { desiredWindowWidth = frame.width(); desiredWindowHeight = frame.height(); @@ -2826,7 +2827,7 @@ public final class ViewRootImpl implements ViewParent, try { mWindowDrawCountDown.await(); } catch (InterruptedException e) { - Log.e(mTag, "Window redraw count down interrupted!"); + Log.e(mTag, "Window redraw count down interruped!"); } mWindowDrawCountDown = null; } @@ -2896,6 +2897,8 @@ public final class ViewRootImpl implements ViewParent, final float appScale = mAttachInfo.mApplicationScale; final boolean scalingRequired = mAttachInfo.mScalingRequired; + int resizeAlpha = 0; + final Rect dirty = mDirty; if (mSurfaceHolder != null) { // The app owns the surface, we won't draw. @@ -3466,7 +3469,6 @@ public final class ViewRootImpl implements ViewParent, } void dispatchDetachedFromWindow() { - mFirstInputStage.onDetachedFromWindow(); if (mView != null && mView.mAttachInfo != null) { mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false); mView.dispatchDetachedFromWindow(); @@ -3729,273 +3731,266 @@ public final class ViewRootImpl implements ViewParent, @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_INVALIDATE: - ((View) msg.obj).invalidate(); - break; - case MSG_INVALIDATE_RECT: - final View.AttachInfo.InvalidateInfo info = - (View.AttachInfo.InvalidateInfo) msg.obj; - info.target.invalidate(info.left, info.top, info.right, info.bottom); - info.recycle(); - break; - case MSG_PROCESS_INPUT_EVENTS: - mProcessInputEventsScheduled = false; - doProcessInputEvents(); - break; - case MSG_DISPATCH_APP_VISIBILITY: - handleAppVisibility(msg.arg1 != 0); - break; - case MSG_DISPATCH_GET_NEW_SURFACE: - handleGetNewSurface(); + case MSG_INVALIDATE: + ((View) msg.obj).invalidate(); + break; + case MSG_INVALIDATE_RECT: + final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj; + info.target.invalidate(info.left, info.top, info.right, info.bottom); + info.recycle(); + break; + case MSG_PROCESS_INPUT_EVENTS: + mProcessInputEventsScheduled = false; + doProcessInputEvents(); + break; + case MSG_DISPATCH_APP_VISIBILITY: + handleAppVisibility(msg.arg1 != 0); + break; + case MSG_DISPATCH_GET_NEW_SURFACE: + handleGetNewSurface(); + break; + case MSG_RESIZED: { + // Recycled in the fall through... + SomeArgs args = (SomeArgs) msg.obj; + if (mWinFrame.equals(args.arg1) + && mPendingOverscanInsets.equals(args.arg5) + && mPendingContentInsets.equals(args.arg2) + && mPendingStableInsets.equals(args.arg6) + && mPendingVisibleInsets.equals(args.arg3) + && mPendingOutsets.equals(args.arg7) + && mPendingBackDropFrame.equals(args.arg8) + && args.arg4 == null + && args.argi1 == 0 + && mDisplay.getDisplayId() == args.argi3) { break; - case MSG_RESIZED: { - // Recycled in the fall through... + } + } // fall through... + case MSG_RESIZED_REPORT: + if (mAdded) { SomeArgs args = (SomeArgs) msg.obj; - if (mWinFrame.equals(args.arg1) - && mPendingOverscanInsets.equals(args.arg5) - && mPendingContentInsets.equals(args.arg2) - && mPendingStableInsets.equals(args.arg6) - && mPendingVisibleInsets.equals(args.arg3) - && mPendingOutsets.equals(args.arg7) - && mPendingBackDropFrame.equals(args.arg8) - && args.arg4 == null - && args.argi1 == 0 - && mDisplay.getDisplayId() == args.argi3) { - break; + + final int displayId = args.argi3; + MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4; + final boolean displayChanged = mDisplay.getDisplayId() != displayId; + + if (!mLastReportedMergedConfiguration.equals(mergedConfiguration)) { + // If configuration changed - notify about that and, maybe, about move to + // display. + performConfigurationChange(mergedConfiguration, false /* force */, + displayChanged ? displayId : INVALID_DISPLAY /* same display */); + } else if (displayChanged) { + // Moved to display without config change - report last applied one. + onMovedToDisplay(displayId, mLastConfigurationFromResources); } - } // fall through... - case MSG_RESIZED_REPORT: - if (mAdded) { - SomeArgs args = (SomeArgs) msg.obj; - - final int displayId = args.argi3; - MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4; - final boolean displayChanged = mDisplay.getDisplayId() != displayId; - - if (!mLastReportedMergedConfiguration.equals(mergedConfiguration)) { - // If configuration changed - notify about that and, maybe, - // about move to display. - performConfigurationChange(mergedConfiguration, false /* force */, - displayChanged - ? displayId : INVALID_DISPLAY /* same display */); - } else if (displayChanged) { - // Moved to display without config change - report last applied one. - onMovedToDisplay(displayId, mLastConfigurationFromResources); - } - final boolean framesChanged = !mWinFrame.equals(args.arg1) - || !mPendingOverscanInsets.equals(args.arg5) - || !mPendingContentInsets.equals(args.arg2) - || !mPendingStableInsets.equals(args.arg6) - || !mPendingVisibleInsets.equals(args.arg3) - || !mPendingOutsets.equals(args.arg7); - - mWinFrame.set((Rect) args.arg1); - mPendingOverscanInsets.set((Rect) args.arg5); - mPendingContentInsets.set((Rect) args.arg2); - mPendingStableInsets.set((Rect) args.arg6); - mPendingVisibleInsets.set((Rect) args.arg3); - mPendingOutsets.set((Rect) args.arg7); - mPendingBackDropFrame.set((Rect) args.arg8); - mForceNextWindowRelayout = args.argi1 != 0; - mPendingAlwaysConsumeNavBar = args.argi2 != 0; - - args.recycle(); - - if (msg.what == MSG_RESIZED_REPORT) { - reportNextDraw(); - } + final boolean framesChanged = !mWinFrame.equals(args.arg1) + || !mPendingOverscanInsets.equals(args.arg5) + || !mPendingContentInsets.equals(args.arg2) + || !mPendingStableInsets.equals(args.arg6) + || !mPendingVisibleInsets.equals(args.arg3) + || !mPendingOutsets.equals(args.arg7); + + mWinFrame.set((Rect) args.arg1); + mPendingOverscanInsets.set((Rect) args.arg5); + mPendingContentInsets.set((Rect) args.arg2); + mPendingStableInsets.set((Rect) args.arg6); + mPendingVisibleInsets.set((Rect) args.arg3); + mPendingOutsets.set((Rect) args.arg7); + mPendingBackDropFrame.set((Rect) args.arg8); + mForceNextWindowRelayout = args.argi1 != 0; + mPendingAlwaysConsumeNavBar = args.argi2 != 0; - if (mView != null && framesChanged) { - forceLayout(mView); - } - requestLayout(); + args.recycle(); + + if (msg.what == MSG_RESIZED_REPORT) { + reportNextDraw(); } - break; - case MSG_WINDOW_MOVED: - if (mAdded) { - final int w = mWinFrame.width(); - final int h = mWinFrame.height(); - final int l = msg.arg1; - final int t = msg.arg2; - mWinFrame.left = l; - mWinFrame.right = l + w; - mWinFrame.top = t; - mWinFrame.bottom = t + h; - - mPendingBackDropFrame.set(mWinFrame); - maybeHandleWindowMove(mWinFrame); + + if (mView != null && framesChanged) { + forceLayout(mView); } - break; - case MSG_WINDOW_FOCUS_CHANGED: { - final boolean hasWindowFocus = msg.arg1 != 0; - if (mAdded) { - mAttachInfo.mHasWindowFocus = hasWindowFocus; - - profileRendering(hasWindowFocus); - - if (hasWindowFocus) { - boolean inTouchMode = msg.arg2 != 0; - ensureTouchModeLocally(inTouchMode); - if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) { - mFullRedrawNeeded = true; + requestLayout(); + } + break; + case MSG_WINDOW_MOVED: + if (mAdded) { + final int w = mWinFrame.width(); + final int h = mWinFrame.height(); + final int l = msg.arg1; + final int t = msg.arg2; + mWinFrame.left = l; + mWinFrame.right = l + w; + mWinFrame.top = t; + mWinFrame.bottom = t + h; + + mPendingBackDropFrame.set(mWinFrame); + maybeHandleWindowMove(mWinFrame); + } + break; + case MSG_WINDOW_FOCUS_CHANGED: { + if (mAdded) { + boolean hasWindowFocus = msg.arg1 != 0; + mAttachInfo.mHasWindowFocus = hasWindowFocus; + + profileRendering(hasWindowFocus); + + if (hasWindowFocus) { + boolean inTouchMode = msg.arg2 != 0; + ensureTouchModeLocally(inTouchMode); + + if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()){ + mFullRedrawNeeded = true; + try { + final WindowManager.LayoutParams lp = mWindowAttributes; + final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null; + mAttachInfo.mThreadedRenderer.initializeIfNeeded( + mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets); + } catch (OutOfResourcesException e) { + Log.e(mTag, "OutOfResourcesException locking surface", e); try { - final WindowManager.LayoutParams lp = mWindowAttributes; - final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null; - mAttachInfo.mThreadedRenderer.initializeIfNeeded( - mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets); - } catch (OutOfResourcesException e) { - Log.e(mTag, "OutOfResourcesException locking surface", e); - try { - if (!mWindowSession.outOfMemory(mWindow)) { - Slog.w(mTag, "No processes killed for memory;" - + " killing self"); - Process.killProcess(Process.myPid()); - } - } catch (RemoteException ex) { + if (!mWindowSession.outOfMemory(mWindow)) { + Slog.w(mTag, "No processes killed for memory; killing self"); + Process.killProcess(Process.myPid()); } - // Retry in a bit. - sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), - 500); - return; + } catch (RemoteException ex) { } + // Retry in a bit. + sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), 500); + return; } } + } - mLastWasImTarget = WindowManager.LayoutParams - .mayUseInputMethod(mWindowAttributes.flags); + mLastWasImTarget = WindowManager.LayoutParams + .mayUseInputMethod(mWindowAttributes.flags); - InputMethodManager imm = InputMethodManager.peekInstance(); - if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { - imm.onPreWindowFocus(mView, hasWindowFocus); - } - if (mView != null) { - mAttachInfo.mKeyDispatchState.reset(); - mView.dispatchWindowFocusChanged(hasWindowFocus); - mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { + imm.onPreWindowFocus(mView, hasWindowFocus); + } + if (mView != null) { + mAttachInfo.mKeyDispatchState.reset(); + mView.dispatchWindowFocusChanged(hasWindowFocus); + mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); - if (mAttachInfo.mTooltipHost != null) { - mAttachInfo.mTooltipHost.hideTooltip(); - } + if (mAttachInfo.mTooltipHost != null) { + mAttachInfo.mTooltipHost.hideTooltip(); } + } - // Note: must be done after the focus change callbacks, - // so all of the view state is set up correctly. - if (hasWindowFocus) { - if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { - imm.onPostWindowFocus(mView, mView.findFocus(), - mWindowAttributes.softInputMode, - !mHasHadWindowFocus, mWindowAttributes.flags); - } - // Clear the forward bit. We can just do this directly, since - // the window manager doesn't care about it. - mWindowAttributes.softInputMode &= + // Note: must be done after the focus change callbacks, + // so all of the view state is set up correctly. + if (hasWindowFocus) { + if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { + imm.onPostWindowFocus(mView, mView.findFocus(), + mWindowAttributes.softInputMode, + !mHasHadWindowFocus, mWindowAttributes.flags); + } + // Clear the forward bit. We can just do this directly, since + // the window manager doesn't care about it. + mWindowAttributes.softInputMode &= + ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; + ((WindowManager.LayoutParams)mView.getLayoutParams()) + .softInputMode &= ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; - ((WindowManager.LayoutParams) mView.getLayoutParams()) - .softInputMode &= - ~WindowManager.LayoutParams - .SOFT_INPUT_IS_FORWARD_NAVIGATION; - mHasHadWindowFocus = true; - } else { - if (mPointerCapture) { - handlePointerCaptureChanged(false); - } + mHasHadWindowFocus = true; + } else { + if (mPointerCapture) { + handlePointerCaptureChanged(false); } } - mFirstInputStage.onWindowFocusChanged(hasWindowFocus); - } break; - case MSG_DIE: - doDie(); - break; - case MSG_DISPATCH_INPUT_EVENT: { - SomeArgs args = (SomeArgs) msg.obj; - InputEvent event = (InputEvent) args.arg1; - InputEventReceiver receiver = (InputEventReceiver) args.arg2; - enqueueInputEvent(event, receiver, 0, true); - args.recycle(); - } break; - case MSG_SYNTHESIZE_INPUT_EVENT: { - InputEvent event = (InputEvent) msg.obj; - enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true); - } break; - case MSG_DISPATCH_KEY_FROM_IME: { - if (LOCAL_LOGV) { - Log.v(TAG, "Dispatching key " + msg.obj + " from IME to " + mView); - } - KeyEvent event = (KeyEvent) msg.obj; - if ((event.getFlags() & KeyEvent.FLAG_FROM_SYSTEM) != 0) { - // The IME is trying to say this event is from the - // system! Bad bad bad! - //noinspection UnusedAssignment - event = KeyEvent.changeFlags(event, - event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM); - } - enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true); - } break; - case MSG_CHECK_FOCUS: { - InputMethodManager imm = InputMethodManager.peekInstance(); - if (imm != null) { - imm.checkFocus(); - } - } break; - case MSG_CLOSE_SYSTEM_DIALOGS: { - if (mView != null) { - mView.onCloseSystemDialogs((String) msg.obj); - } - } break; - case MSG_DISPATCH_DRAG_EVENT: { - } // fall through - case MSG_DISPATCH_DRAG_LOCATION_EVENT: { - DragEvent event = (DragEvent) msg.obj; - // only present when this app called startDrag() - event.mLocalState = mLocalDragState; - handleDragEvent(event); - } break; - case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: { - handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj); - } break; - case MSG_UPDATE_CONFIGURATION: { - Configuration config = (Configuration) msg.obj; - if (config.isOtherSeqNewer( - mLastReportedMergedConfiguration.getMergedConfiguration())) { - // If we already have a newer merged config applied - use its global part. - config = mLastReportedMergedConfiguration.getGlobalConfiguration(); - } + } + } break; + case MSG_DIE: + doDie(); + break; + case MSG_DISPATCH_INPUT_EVENT: { + SomeArgs args = (SomeArgs)msg.obj; + InputEvent event = (InputEvent)args.arg1; + InputEventReceiver receiver = (InputEventReceiver)args.arg2; + enqueueInputEvent(event, receiver, 0, true); + args.recycle(); + } break; + case MSG_SYNTHESIZE_INPUT_EVENT: { + InputEvent event = (InputEvent)msg.obj; + enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true); + } break; + case MSG_DISPATCH_KEY_FROM_IME: { + if (LOCAL_LOGV) Log.v( + TAG, "Dispatching key " + + msg.obj + " from IME to " + mView); + KeyEvent event = (KeyEvent)msg.obj; + if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) { + // The IME is trying to say this event is from the + // system! Bad bad bad! + //noinspection UnusedAssignment + event = KeyEvent.changeFlags(event, event.getFlags() & + ~KeyEvent.FLAG_FROM_SYSTEM); + } + enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true); + } break; + case MSG_CHECK_FOCUS: { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + imm.checkFocus(); + } + } break; + case MSG_CLOSE_SYSTEM_DIALOGS: { + if (mView != null) { + mView.onCloseSystemDialogs((String)msg.obj); + } + } break; + case MSG_DISPATCH_DRAG_EVENT: + case MSG_DISPATCH_DRAG_LOCATION_EVENT: { + DragEvent event = (DragEvent)msg.obj; + event.mLocalState = mLocalDragState; // only present when this app called startDrag() + handleDragEvent(event); + } break; + case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: { + handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj); + } break; + case MSG_UPDATE_CONFIGURATION: { + Configuration config = (Configuration) msg.obj; + if (config.isOtherSeqNewer( + mLastReportedMergedConfiguration.getMergedConfiguration())) { + // If we already have a newer merged config applied - use its global part. + config = mLastReportedMergedConfiguration.getGlobalConfiguration(); + } - // Use the newer global config and last reported override config. - mPendingMergedConfiguration.setConfiguration(config, - mLastReportedMergedConfiguration.getOverrideConfiguration()); + // Use the newer global config and last reported override config. + mPendingMergedConfiguration.setConfiguration(config, + mLastReportedMergedConfiguration.getOverrideConfiguration()); - performConfigurationChange(mPendingMergedConfiguration, false /* force */, - INVALID_DISPLAY /* same display */); - } break; - case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: { - setAccessibilityFocus(null, null); - } break; - case MSG_INVALIDATE_WORLD: { - if (mView != null) { - invalidateWorld(mView); - } - } break; - case MSG_DISPATCH_WINDOW_SHOWN: { - handleDispatchWindowShown(); - } break; - case MSG_REQUEST_KEYBOARD_SHORTCUTS: { - final IResultReceiver receiver = (IResultReceiver) msg.obj; - final int deviceId = msg.arg1; - handleRequestKeyboardShortcuts(receiver, deviceId); - } break; - case MSG_UPDATE_POINTER_ICON: { - MotionEvent event = (MotionEvent) msg.obj; - resetPointerIcon(event); - } break; - case MSG_POINTER_CAPTURE_CHANGED: { - final boolean hasCapture = msg.arg1 != 0; - handlePointerCaptureChanged(hasCapture); - } break; - case MSG_DRAW_FINISHED: { - pendingDrawFinished(); - } break; + performConfigurationChange(mPendingMergedConfiguration, false /* force */, + INVALID_DISPLAY /* same display */); + } break; + case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: { + setAccessibilityFocus(null, null); + } break; + case MSG_INVALIDATE_WORLD: { + if (mView != null) { + invalidateWorld(mView); + } + } break; + case MSG_DISPATCH_WINDOW_SHOWN: { + handleDispatchWindowShown(); + } break; + case MSG_REQUEST_KEYBOARD_SHORTCUTS: { + final IResultReceiver receiver = (IResultReceiver) msg.obj; + final int deviceId = msg.arg1; + handleRequestKeyboardShortcuts(receiver, deviceId); + } break; + case MSG_UPDATE_POINTER_ICON: { + MotionEvent event = (MotionEvent) msg.obj; + resetPointerIcon(event); + } break; + case MSG_POINTER_CAPTURE_CHANGED: { + final boolean hasCapture = msg.arg1 != 0; + handlePointerCaptureChanged(hasCapture); + } break; + case MSG_DRAW_FINISHED: { + pendingDrawFinished(); + } break; } } } @@ -4208,18 +4203,6 @@ public final class ViewRootImpl implements ViewParent, } } - protected void onWindowFocusChanged(boolean hasWindowFocus) { - if (mNext != null) { - mNext.onWindowFocusChanged(hasWindowFocus); - } - } - - protected void onDetachedFromWindow() { - if (mNext != null) { - mNext.onDetachedFromWindow(); - } - } - protected boolean shouldDropInputEvent(QueuedInputEvent q) { if (mView == null || !mAdded) { Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent); @@ -4973,9 +4956,9 @@ public final class ViewRootImpl implements ViewParent, final MotionEvent event = (MotionEvent)q.mEvent; final int source = event.getSource(); if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { - mTrackball.cancel(); + mTrackball.cancel(event); } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { - mJoystick.cancel(); + mJoystick.cancel(event); } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION) == InputDevice.SOURCE_TOUCH_NAVIGATION) { mTouchNavigation.cancel(event); @@ -4984,18 +4967,6 @@ public final class ViewRootImpl implements ViewParent, } super.onDeliverToNext(q); } - - @Override - protected void onWindowFocusChanged(boolean hasWindowFocus) { - if (!hasWindowFocus) { - mJoystick.cancel(); - } - } - - @Override - protected void onDetachedFromWindow() { - mJoystick.cancel(); - } } /** @@ -5108,7 +5079,7 @@ public final class ViewRootImpl implements ViewParent, } } - public void cancel() { + public void cancel(MotionEvent event) { mLastTime = Integer.MIN_VALUE; // If we reach this, we consumed a trackball event. @@ -5292,11 +5263,14 @@ public final class ViewRootImpl implements ViewParent, * Creates dpad events from unhandled joystick movements. */ final class SyntheticJoystickHandler extends Handler { + private final static String TAG = "SyntheticJoystickHandler"; private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 1; private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 2; - private final JoystickAxesState mJoystickAxesState = new JoystickAxesState(); - private final SparseArray<KeyEvent> mDeviceKeyEvents = new SparseArray<>(); + private int mLastXDirection; + private int mLastYDirection; + private int mLastXKeyCode; + private int mLastYKeyCode; public SyntheticJoystickHandler() { super(true); @@ -5307,10 +5281,11 @@ public final class ViewRootImpl implements ViewParent, switch (msg.what) { case MSG_ENQUEUE_X_AXIS_KEY_REPEAT: case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: { + KeyEvent oldEvent = (KeyEvent)msg.obj; + KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent, + SystemClock.uptimeMillis(), + oldEvent.getRepeatCount() + 1); if (mAttachInfo.mHasWindowFocus) { - KeyEvent oldEvent = (KeyEvent) msg.obj; - KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent, - SystemClock.uptimeMillis(), oldEvent.getRepeatCount() + 1); enqueueInputEvent(e); Message m = obtainMessage(msg.what, e); m.setAsynchronous(true); @@ -5322,176 +5297,97 @@ public final class ViewRootImpl implements ViewParent, public void process(MotionEvent event) { switch(event.getActionMasked()) { - case MotionEvent.ACTION_CANCEL: - cancel(); - break; - case MotionEvent.ACTION_MOVE: - update(event); - break; - default: - Log.w(mTag, "Unexpected action: " + event.getActionMasked()); + case MotionEvent.ACTION_CANCEL: + cancel(event); + break; + case MotionEvent.ACTION_MOVE: + update(event, true); + break; + default: + Log.w(mTag, "Unexpected action: " + event.getActionMasked()); } } - private void cancel() { + private void cancel(MotionEvent event) { removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT); removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT); - for (int i = 0; i < mDeviceKeyEvents.size(); i++) { - final KeyEvent keyEvent = mDeviceKeyEvents.valueAt(i); - if (keyEvent != null) { - enqueueInputEvent(KeyEvent.changeTimeRepeat(keyEvent, - SystemClock.uptimeMillis(), 0)); - } - } - mDeviceKeyEvents.clear(); - mJoystickAxesState.resetState(); - } - - private void update(MotionEvent event) { - final int historySize = event.getHistorySize(); - for (int h = 0; h < historySize; h++) { - final long time = event.getHistoricalEventTime(h); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_X, - event.getHistoricalAxisValue(MotionEvent.AXIS_X, 0, h)); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_Y, - event.getHistoricalAxisValue(MotionEvent.AXIS_Y, 0, h)); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_X, - event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, 0, h)); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y, - event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, 0, h)); - } - final long time = event.getEventTime(); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_X, - event.getAxisValue(MotionEvent.AXIS_X)); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_Y, - event.getAxisValue(MotionEvent.AXIS_Y)); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_X, - event.getAxisValue(MotionEvent.AXIS_HAT_X)); - mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y, - event.getAxisValue(MotionEvent.AXIS_HAT_Y)); + update(event, false); } - final class JoystickAxesState { - // State machine: from neutral state (no button press) can go into - // button STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state, emitting an ACTION_DOWN event. - // From STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state can go into neutral state, - // emitting an ACTION_UP event. - private static final int STATE_UP_OR_LEFT = -1; - private static final int STATE_NEUTRAL = 0; - private static final int STATE_DOWN_OR_RIGHT = 1; - - final int[] mAxisStatesHat = {STATE_NEUTRAL, STATE_NEUTRAL}; // {AXIS_HAT_X, AXIS_HAT_Y} - final int[] mAxisStatesStick = {STATE_NEUTRAL, STATE_NEUTRAL}; // {AXIS_X, AXIS_Y} - - void resetState() { - mAxisStatesHat[0] = STATE_NEUTRAL; - mAxisStatesHat[1] = STATE_NEUTRAL; - mAxisStatesStick[0] = STATE_NEUTRAL; - mAxisStatesStick[1] = STATE_NEUTRAL; - } - - void updateStateForAxis(MotionEvent event, long time, int axis, float value) { - // Emit KeyEvent if necessary - // axis can be AXIS_X, AXIS_Y, AXIS_HAT_X, AXIS_HAT_Y - final int axisStateIndex; - final int repeatMessage; - if (isXAxis(axis)) { - axisStateIndex = 0; - repeatMessage = MSG_ENQUEUE_X_AXIS_KEY_REPEAT; - } else if (isYAxis(axis)) { - axisStateIndex = 1; - repeatMessage = MSG_ENQUEUE_Y_AXIS_KEY_REPEAT; - } else { - Log.e(mTag, "Unexpected axis " + axis + " in updateStateForAxis!"); - return; - } - final int newState = joystickAxisValueToState(value); + private void update(MotionEvent event, boolean synthesizeNewKeys) { + final long time = event.getEventTime(); + final int metaState = event.getMetaState(); + final int deviceId = event.getDeviceId(); + final int source = event.getSource(); - final int currentState; - if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) { - currentState = mAxisStatesStick[axisStateIndex]; - } else { - currentState = mAxisStatesHat[axisStateIndex]; - } + int xDirection = joystickAxisValueToDirection( + event.getAxisValue(MotionEvent.AXIS_HAT_X)); + if (xDirection == 0) { + xDirection = joystickAxisValueToDirection(event.getX()); + } - if (currentState == newState) { - return; + int yDirection = joystickAxisValueToDirection( + event.getAxisValue(MotionEvent.AXIS_HAT_Y)); + if (yDirection == 0) { + yDirection = joystickAxisValueToDirection(event.getY()); + } + + if (xDirection != mLastXDirection) { + if (mLastXKeyCode != 0) { + removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT); + enqueueInputEvent(new KeyEvent(time, time, + KeyEvent.ACTION_UP, mLastXKeyCode, 0, metaState, + deviceId, 0, KeyEvent.FLAG_FALLBACK, source)); + mLastXKeyCode = 0; } - final int metaState = event.getMetaState(); - final int deviceId = event.getDeviceId(); - final int source = event.getSource(); + mLastXDirection = xDirection; - if (currentState == STATE_DOWN_OR_RIGHT || currentState == STATE_UP_OR_LEFT) { - // send a button release event - final int keyCode = joystickAxisAndStateToKeycode(axis, currentState); - if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { - enqueueInputEvent(new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode, - 0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source)); - // remove the corresponding pending UP event if focus lost/view detached - mDeviceKeyEvents.put(deviceId, null); - } - removeMessages(repeatMessage); + if (xDirection != 0 && synthesizeNewKeys) { + mLastXKeyCode = xDirection > 0 + ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT; + final KeyEvent e = new KeyEvent(time, time, + KeyEvent.ACTION_DOWN, mLastXKeyCode, 0, metaState, + deviceId, 0, KeyEvent.FLAG_FALLBACK, source); + enqueueInputEvent(e); + Message m = obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e); + m.setAsynchronous(true); + sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout()); } + } - if (newState == STATE_DOWN_OR_RIGHT || newState == STATE_UP_OR_LEFT) { - // send a button down event - final int keyCode = joystickAxisAndStateToKeycode(axis, newState); - if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { - KeyEvent keyEvent = new KeyEvent(time, time, KeyEvent.ACTION_DOWN, keyCode, - 0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source); - enqueueInputEvent(keyEvent); - Message m = obtainMessage(repeatMessage, keyEvent); - m.setAsynchronous(true); - sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout()); - // store the corresponding ACTION_UP event so that it can be sent - // if focus is lost or root view is removed - mDeviceKeyEvents.put(deviceId, - new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode, - 0, metaState, deviceId, 0, - KeyEvent.FLAG_FALLBACK | KeyEvent.FLAG_CANCELED, - source)); - } - } - if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) { - mAxisStatesStick[axisStateIndex] = newState; - } else { - mAxisStatesHat[axisStateIndex] = newState; + if (yDirection != mLastYDirection) { + if (mLastYKeyCode != 0) { + removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT); + enqueueInputEvent(new KeyEvent(time, time, + KeyEvent.ACTION_UP, mLastYKeyCode, 0, metaState, + deviceId, 0, KeyEvent.FLAG_FALLBACK, source)); + mLastYKeyCode = 0; } - } - private boolean isXAxis(int axis) { - return axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_HAT_X; - } - private boolean isYAxis(int axis) { - return axis == MotionEvent.AXIS_Y || axis == MotionEvent.AXIS_HAT_Y; - } + mLastYDirection = yDirection; - private int joystickAxisAndStateToKeycode(int axis, int state) { - if (isXAxis(axis) && state == STATE_UP_OR_LEFT) { - return KeyEvent.KEYCODE_DPAD_LEFT; - } - if (isXAxis(axis) && state == STATE_DOWN_OR_RIGHT) { - return KeyEvent.KEYCODE_DPAD_RIGHT; - } - if (isYAxis(axis) && state == STATE_UP_OR_LEFT) { - return KeyEvent.KEYCODE_DPAD_UP; + if (yDirection != 0 && synthesizeNewKeys) { + mLastYKeyCode = yDirection > 0 + ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP; + final KeyEvent e = new KeyEvent(time, time, + KeyEvent.ACTION_DOWN, mLastYKeyCode, 0, metaState, + deviceId, 0, KeyEvent.FLAG_FALLBACK, source); + enqueueInputEvent(e); + Message m = obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e); + m.setAsynchronous(true); + sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout()); } - if (isYAxis(axis) && state == STATE_DOWN_OR_RIGHT) { - return KeyEvent.KEYCODE_DPAD_DOWN; - } - Log.e(mTag, "Unknown axis " + axis + " or direction " + state); - return KeyEvent.KEYCODE_UNKNOWN; // should never happen } + } - private int joystickAxisValueToState(float value) { - if (value >= 0.5f) { - return STATE_DOWN_OR_RIGHT; - } else if (value <= -0.5f) { - return STATE_UP_OR_LEFT; - } else { - return STATE_NEUTRAL; - } + private int joystickAxisValueToDirection(float value) { + if (value >= 0.5f) { + return 1; + } else if (value <= -0.5f) { + return -1; + } else { + return 0; } } } @@ -6212,6 +6108,7 @@ public final class ViewRootImpl implements ViewParent, if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params); } + //Log.d(mTag, ">>>>>> CALLING relayout"); if (params != null && mOrigWindowType != params.type) { // For compatibility with old apps, don't crash here. if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { @@ -6232,6 +6129,7 @@ public final class ViewRootImpl implements ViewParent, mPendingAlwaysConsumeNavBar = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR) != 0; + //Log.d(mTag, "<<<<<< BACK FROM relayout"); if (restore) { params.restore(); } diff --git a/android/view/ViewStructure.java b/android/view/ViewStructure.java index 309366c6..f671c349 100644 --- a/android/view/ViewStructure.java +++ b/android/view/ViewStructure.java @@ -365,30 +365,6 @@ public abstract class ViewStructure { public abstract void setDataIsSensitive(boolean sensitive); /** - * Sets the minimum width in ems of the text associated with this view, when supported. - * - * <p>Should only be set when the node is used for autofill purposes - it will be ignored - * when used for Assist. - */ - public abstract void setMinTextEms(int minEms); - - /** - * Sets the maximum width in ems of the text associated with this view, when supported. - * - * <p>Should only be set when the node is used for autofill purposes - it will be ignored - * when used for Assist. - */ - public abstract void setMaxTextEms(int maxEms); - - /** - * Sets the maximum length of the text associated with this view, when supported. - * - * <p>Should only be set when the node is used for autofill purposes - it will be ignored - * when used for Assist. - */ - public abstract void setMaxTextLength(int maxLength); - - /** * Call when done populating a {@link ViewStructure} returned by * {@link #asyncNewChild}. */ diff --git a/android/view/WindowManagerInternal.java b/android/view/WindowManagerInternal.java index 69cc1002..98f8dc8e 100644 --- a/android/view/WindowManagerInternal.java +++ b/android/view/WindowManagerInternal.java @@ -335,8 +335,8 @@ public abstract class WindowManagerInternal { public abstract void setOnHardKeyboardStatusChangeListener( OnHardKeyboardStatusChangeListener listener); - /** Returns true if a stack in the windowing mode is currently visible. */ - public abstract boolean isStackVisible(int windowingMode); + /** Returns true if the stack with the input Id is currently visible. */ + public abstract boolean isStackVisible(int stackId); /** * @return True if and only if the docked divider is currently in resize mode. diff --git a/android/view/WindowManagerPolicy.java b/android/view/WindowManagerPolicy.java index 137e551d..da72535d 100644 --- a/android/view/WindowManagerPolicy.java +++ b/android/view/WindowManagerPolicy.java @@ -467,8 +467,11 @@ public interface WindowManagerPolicy { */ public boolean isDimming(); - /** @return the current windowing mode of this window. */ - int getWindowingMode(); + /** + * @return the stack id this windows belongs to, or {@link StackId#INVALID_STACK_ID} if + * not attached to any stack. + */ + int getStackId(); /** * Returns true if the window is current in multi-windowing mode. i.e. it shares the diff --git a/android/view/accessibility/AccessibilityCache.java b/android/view/accessibility/AccessibilityCache.java index d7851171..0f21c5c8 100644 --- a/android/view/accessibility/AccessibilityCache.java +++ b/android/view/accessibility/AccessibilityCache.java @@ -329,6 +329,8 @@ public final class AccessibilityCache { final long oldParentId = oldInfo.getParentNodeId(); if (info.getParentNodeId() != oldParentId) { clearSubTreeLocked(windowId, oldParentId); + } else { + oldInfo.recycle(); } } 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. + * <p> + * Input: Nothing. + * </p> + * <p> + * Output: Nothing. + * </p> + * + * @hide + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON = + "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON"; + + static final Object sInstanceSync = new Object(); + + private static AccessibilityManager sInstance; + + private final Object mLock = new Object(); + + private IAccessibilityManager mService; + + final int mUserId; + + final Handler mHandler; + + final Handler.Callback mCallback; + + boolean mIsEnabled; - private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0); + int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK; + boolean mIsTouchExplorationEnabled; + + boolean mIsHighTextContrastEnabled; + + private final ArrayMap<AccessibilityStateChangeListener, Handler> + mAccessibilityStateChangeListeners = new ArrayMap<>(); + + private final ArrayMap<TouchExplorationStateChangeListener, Handler> + mTouchExplorationStateChangeListeners = new ArrayMap<>(); + + private final ArrayMap<HighTextContrastChangeListener, Handler> + mHighTextContrastStateChangeListeners = new ArrayMap<>(); + + private final ArrayMap<AccessibilityServicesStateChangeListener, Handler> + mServicesStateChangeListeners = new ArrayMap<>(); + + /** + * Map from a view's accessibility id to the list of request preparers set for that view + */ + private SparseArray<List<AccessibilityRequestPreparer>> 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<AccessibilityServicesStateChangeListener, Handler> listeners; + synchronized (mLock) { + if (mServicesStateChangeListeners.isEmpty()) { + return; } + listeners = new ArrayMap<>(mServicesStateChangeListeners); + } - public void setRelevantEventTypes(int eventTypes) { - } - }; + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + final AccessibilityServicesStateChangeListener listener = + mServicesStateChangeListeners.keyAt(i); + mServicesStateChangeListeners.valueAt(i).post(() -> listener + .onAccessibilityServicesStateChanged(AccessibilityManager.this)); + } + } + + @Override + public void setRelevantEventTypes(int eventTypes) { + mRelevantEventTypes = eventTypes; + } + }; /** * Get an AccessibilityManager instance (create one if necessary). * + * @param context Context in which this manager operates. + * + * @hide */ public static AccessibilityManager getInstance(Context context) { + synchronized (sInstanceSync) { + if (sInstance == null) { + final int userId; + if (Binder.getCallingUid() == Process.SYSTEM_UID + || context.checkCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS) + == PackageManager.PERMISSION_GRANTED + || context.checkCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL) + == PackageManager.PERMISSION_GRANTED) { + userId = UserHandle.USER_CURRENT; + } else { + userId = UserHandle.myUserId(); + } + sInstance = new AccessibilityManager(context, null, userId); + } + } return sInstance; } @@ -114,21 +285,68 @@ public final class AccessibilityManager { * Create an instance. * * @param context A {@link Context}. + * @param service An interface to the backing service. + * @param userId User id under which to run. + * + * @hide */ public AccessibilityManager(Context context, IAccessibilityManager service, int userId) { + // Constructor can't be chained because we can't create an instance of an inner class + // before calling another constructor. + mCallback = new MyCallback(); + mHandler = new Handler(context.getMainLooper(), mCallback); + mUserId = userId; + synchronized (mLock) { + tryConnectToServiceLocked(service); + } + } + + /** + * Create an instance. + * + * @param handler The handler to use + * @param service An interface to the backing service. + * @param userId User id under which to run. + * + * @hide + */ + public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) { + mCallback = new MyCallback(); + mHandler = handler; + mUserId = userId; + synchronized (mLock) { + tryConnectToServiceLocked(service); + } } + /** + * @hide + */ public IAccessibilityManagerClient getClient() { return mClient; } /** - * Returns if the {@link AccessibilityManager} is enabled. + * @hide + */ + @VisibleForTesting + public Handler.Callback getCallback() { + return mCallback; + } + + /** + * Returns if the accessibility in the system is enabled. * - * @return True if this {@link AccessibilityManager} is enabled, false otherwise. + * @return True if accessibility is enabled, false otherwise. */ public boolean isEnabled() { - return false; + synchronized (mLock) { + IAccessibilityManager service = getServiceLocked(); + if (service == null) { + return false; + } + return mIsEnabled; + } } /** @@ -137,7 +355,13 @@ public final class AccessibilityManager { * @return True if touch exploration is enabled, false otherwise. */ public boolean isTouchExplorationEnabled() { - return true; + synchronized (mLock) { + IAccessibilityManager service = getServiceLocked(); + if (service == null) { + return false; + } + return mIsTouchExplorationEnabled; + } } /** @@ -147,35 +371,169 @@ public final class AccessibilityManager { * doing its own rendering and does not rely on the platform rendering pipeline. * </p> * + * @return True if high text contrast is enabled, false otherwise. + * + * @hide */ public boolean isHighTextContrastEnabled() { - return false; + synchronized (mLock) { + IAccessibilityManager service = getServiceLocked(); + if (service == null) { + return false; + } + return mIsHighTextContrastEnabled; + } } /** * Sends an {@link AccessibilityEvent}. + * + * @param event The event to send. + * + * @throws IllegalStateException if accessibility is not enabled. + * + * <strong>Note:</strong> The preferred mechanism for sending custom accessibility + * events is through calling + * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)} + * instead of this method to allow predecessors to augment/filter events sent by + * their descendants. */ public void sendAccessibilityEvent(AccessibilityEvent event) { + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + if (!mIsEnabled) { + Looper myLooper = Looper.myLooper(); + if (myLooper == Looper.getMainLooper()) { + throw new IllegalStateException( + "Accessibility off. Did you forget to check that?"); + } else { + // If we're not running on the thread with the main looper, it's possible for + // the state of accessibility to change between checking isEnabled and + // calling this method. So just log the error rather than throwing the + // exception. + Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled"); + return; + } + } + if ((event.getEventType() & mRelevantEventTypes) == 0) { + if (DEBUG) { + Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event + + " that is not among " + + AccessibilityEvent.eventTypeToString(mRelevantEventTypes)); + } + return; + } + userId = mUserId; + } + try { + event.setEventTime(SystemClock.uptimeMillis()); + // it is possible that this manager is in the same process as the service but + // client using it is called through Binder from another process. Example: MMS + // app adds a SMS notification and the NotificationManagerService calls this method + long identityToken = Binder.clearCallingIdentity(); + service.sendAccessibilityEvent(event, userId); + Binder.restoreCallingIdentity(identityToken); + if (DEBUG) { + Log.i(LOG_TAG, event + " sent"); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error during sending " + event + " ", re); + } finally { + event.recycle(); + } } /** - * Requests interruption of the accessibility feedback from all accessibility services. + * Requests feedback interruption from all accessibility services. */ public void interrupt() { + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + if (!mIsEnabled) { + Looper myLooper = Looper.myLooper(); + if (myLooper == Looper.getMainLooper()) { + throw new IllegalStateException( + "Accessibility off. Did you forget to check that?"); + } else { + // If we're not running on the thread with the main looper, it's possible for + // the state of accessibility to change between checking isEnabled and + // calling this method. So just log the error rather than throwing the + // exception. + Log.e(LOG_TAG, "Interrupt called with accessibility disabled"); + return; + } + } + userId = mUserId; + } + try { + service.interrupt(userId); + if (DEBUG) { + Log.i(LOG_TAG, "Requested interrupt from all services"); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re); + } } /** * Returns the {@link ServiceInfo}s of the installed accessibility services. * * @return An unmodifiable list with {@link ServiceInfo}s. + * + * @deprecated Use {@link #getInstalledAccessibilityServiceList()} */ @Deprecated public List<ServiceInfo> getAccessibilityServiceList() { - return Collections.emptyList(); + List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList(); + List<ServiceInfo> services = new ArrayList<>(); + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + AccessibilityServiceInfo info = infos.get(i); + services.add(info.getResolveInfo().serviceInfo); + } + return Collections.unmodifiableList(services); } + /** + * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services. + * + * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. + */ public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { - return Collections.emptyList(); + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return Collections.emptyList(); + } + userId = mUserId; + } + + List<AccessibilityServiceInfo> services = null; + try { + services = service.getInstalledAccessibilityServiceList(userId); + if (DEBUG) { + Log.i(LOG_TAG, "Installed AccessibilityServices " + services); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); + } + if (services != null) { + return Collections.unmodifiableList(services); + } else { + return Collections.emptyList(); + } } /** @@ -190,21 +548,48 @@ public final class AccessibilityManager { * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN * @see AccessibilityServiceInfo#FEEDBACK_VISUAL + * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE */ public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( int feedbackTypeFlags) { - return Collections.emptyList(); + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return Collections.emptyList(); + } + userId = mUserId; + } + + List<AccessibilityServiceInfo> services = null; + try { + services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId); + if (DEBUG) { + Log.i(LOG_TAG, "Installed AccessibilityServices " + services); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); + } + if (services != null) { + return Collections.unmodifiableList(services); + } else { + return Collections.emptyList(); + } } /** * Registers an {@link AccessibilityStateChangeListener} for changes in - * the global accessibility state of the system. + * the global accessibility state of the system. Equivalent to calling + * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)} + * with a null handler. * * @param listener The listener. - * @return True if successfully registered. + * @return Always returns {@code true}. */ public boolean addAccessibilityStateChangeListener( - AccessibilityStateChangeListener listener) { + @NonNull AccessibilityStateChangeListener listener) { + addAccessibilityStateChangeListener(listener, null); return true; } @@ -218,22 +603,40 @@ public final class AccessibilityManager { * for a callback on the process's main handler. */ public void addAccessibilityStateChangeListener( - @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {} + @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) { + synchronized (mLock) { + mAccessibilityStateChangeListeners + .put(listener, (handler == null) ? mHandler : handler); + } + } + /** + * Unregisters an {@link AccessibilityStateChangeListener}. + * + * @param listener The listener. + * @return True if the listener was previously registered. + */ public boolean removeAccessibilityStateChangeListener( - AccessibilityStateChangeListener listener) { - return true; + @NonNull AccessibilityStateChangeListener listener) { + synchronized (mLock) { + int index = mAccessibilityStateChangeListeners.indexOfKey(listener); + mAccessibilityStateChangeListeners.remove(listener); + return (index >= 0); + } } /** * Registers a {@link TouchExplorationStateChangeListener} for changes in - * the global touch exploration state of the system. + * the global touch exploration state of the system. Equivalent to calling + * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)} + * with a null handler. * * @param listener The listener. - * @return True if successfully registered. + * @return Always returns {@code true}. */ public boolean addTouchExplorationStateChangeListener( @NonNull TouchExplorationStateChangeListener listener) { + addTouchExplorationStateChangeListener(listener, null); return true; } @@ -247,17 +650,103 @@ public final class AccessibilityManager { * for a callback on the process's main handler. */ public void addTouchExplorationStateChangeListener( - @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {} + @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) { + synchronized (mLock) { + mTouchExplorationStateChangeListeners + .put(listener, (handler == null) ? mHandler : handler); + } + } /** * Unregisters a {@link TouchExplorationStateChangeListener}. * * @param listener The listener. - * @return True if successfully unregistered. + * @return True if listener was previously registered. */ public boolean removeTouchExplorationStateChangeListener( @NonNull TouchExplorationStateChangeListener listener) { - return true; + synchronized (mLock) { + int index = mTouchExplorationStateChangeListeners.indexOfKey(listener); + mTouchExplorationStateChangeListeners.remove(listener); + return (index >= 0); + } + } + + /** + * Registers a {@link AccessibilityServicesStateChangeListener}. + * + * @param listener The listener. + * @param handler The handler on which the listener should be called back, or {@code null} + * for a callback on the process's main handler. + * @hide + */ + public void addAccessibilityServicesStateChangeListener( + @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) { + synchronized (mLock) { + mServicesStateChangeListeners + .put(listener, (handler == null) ? mHandler : handler); + } + } + + /** + * Unregisters a {@link AccessibilityServicesStateChangeListener}. + * + * @param listener The listener. + * + * @hide + */ + public void removeAccessibilityServicesStateChangeListener( + @NonNull AccessibilityServicesStateChangeListener listener) { + // Final CopyOnWriteArrayList - no lock needed. + mServicesStateChangeListeners.remove(listener); + } + + /** + * Registers a {@link AccessibilityRequestPreparer}. + */ + public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) { + if (mRequestPreparerLists == null) { + mRequestPreparerLists = new SparseArray<>(1); + } + int id = preparer.getView().getAccessibilityViewId(); + List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id); + if (requestPreparerList == null) { + requestPreparerList = new ArrayList<>(1); + mRequestPreparerLists.put(id, requestPreparerList); + } + requestPreparerList.add(preparer); + } + + /** + * Unregisters a {@link AccessibilityRequestPreparer}. + */ + public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) { + if (mRequestPreparerLists == null) { + return; + } + int viewId = preparer.getView().getAccessibilityViewId(); + List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId); + if (requestPreparerList != null) { + requestPreparerList.remove(preparer); + if (requestPreparerList.isEmpty()) { + mRequestPreparerLists.remove(viewId); + } + } + } + + /** + * Get the preparers that are registered for an accessibility ID + * + * @param id The ID of interest + * @return The list of preparers, or {@code null} if there are none. + * + * @hide + */ + public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) { + if (mRequestPreparerLists == null) { + return null; + } + return mRequestPreparerLists.get(id); } /** @@ -269,7 +758,12 @@ public final class AccessibilityManager { * @hide */ public void addHighTextContrastStateChangeListener( - @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {} + @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) { + synchronized (mLock) { + mHighTextContrastStateChangeListeners + .put(listener, (handler == null) ? mHandler : handler); + } + } /** * Unregisters a {@link HighTextContrastChangeListener}. @@ -279,7 +773,51 @@ public final class AccessibilityManager { * @hide */ public void removeHighTextContrastStateChangeListener( - @NonNull HighTextContrastChangeListener listener) {} + @NonNull HighTextContrastChangeListener listener) { + synchronized (mLock) { + mHighTextContrastStateChangeListeners.remove(listener); + } + } + + /** + * Check if the accessibility volume stream is active. + * + * @return True if accessibility volume is active (i.e. some service has requested it). False + * otherwise. + * @hide + */ + public boolean isAccessibilityVolumeStreamActive() { + List<AccessibilityServiceInfo> serviceInfos = + getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK); + for (int i = 0; i < serviceInfos.size(); i++) { + if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) { + return true; + } + } + return false; + } + + /** + * Report a fingerprint gesture to accessibility. Only available for the system process. + * + * @param keyCode The key code of the gesture + * @return {@code true} if accessibility consumes the event. {@code false} if not. + * @hide + */ + public boolean sendFingerprintGesture(int keyCode) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return false; + } + } + try { + return service.sendFingerprintGesture(keyCode); + } catch (RemoteException e) { + return false; + } + } /** * Sets the current state and notifies listeners, if necessary. @@ -287,14 +825,314 @@ public final class AccessibilityManager { * @param stateFlags The state flags. */ private void setStateLocked(int stateFlags) { + final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; + final boolean touchExplorationEnabled = + (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0; + final boolean highTextContrastEnabled = + (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0; + + final boolean wasEnabled = mIsEnabled; + final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled; + final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled; + + // Ensure listeners get current state from isZzzEnabled() calls. + mIsEnabled = enabled; + mIsTouchExplorationEnabled = touchExplorationEnabled; + mIsHighTextContrastEnabled = highTextContrastEnabled; + + if (wasEnabled != enabled) { + notifyAccessibilityStateChanged(); + } + + if (wasTouchExplorationEnabled != touchExplorationEnabled) { + notifyTouchExplorationStateChanged(); + } + + if (wasHighTextContrastEnabled != highTextContrastEnabled) { + notifyHighTextContrastStateChanged(); + } } + /** + * Find an installed service with the specified {@link ComponentName}. + * + * @param componentName The name to match to the service. + * + * @return The info corresponding to the installed service, or {@code null} if no such service + * is installed. + * @hide + */ + public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName( + ComponentName componentName) { + final List<AccessibilityServiceInfo> installedServiceInfos = + getInstalledAccessibilityServiceList(); + if ((installedServiceInfos == null) || (componentName == null)) { + return null; + } + for (int i = 0; i < installedServiceInfos.size(); i++) { + if (componentName.equals(installedServiceInfos.get(i).getComponentName())) { + return installedServiceInfos.get(i); + } + } + return null; + } + + /** + * Adds an accessibility interaction connection interface for a given window. + * @param windowToken The window token to which a connection is added. + * @param connection The connection. + * + * @hide + */ public int addAccessibilityInteractionConnection(IWindow windowToken, IAccessibilityInteractionConnection connection) { + final IAccessibilityManager service; + final int userId; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return View.NO_ID; + } + userId = mUserId; + } + try { + return service.addAccessibilityInteractionConnection(windowToken, connection, userId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); + } return View.NO_ID; } + /** + * Removed an accessibility interaction connection interface for a given window. + * @param windowToken The window token to which a connection is removed. + * + * @hide + */ public void removeAccessibilityInteractionConnection(IWindow windowToken) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.removeAccessibilityInteractionConnection(windowToken); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re); + } + } + + /** + * Perform the accessibility shortcut if the caller has permission. + * + * @hide + */ + public void performAccessibilityShortcut() { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.performAccessibilityShortcut(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re); + } + } + + /** + * Notifies that the accessibility button in the system's navigation area has been clicked + * + * @hide + */ + public void notifyAccessibilityButtonClicked() { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.notifyAccessibilityButtonClicked(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while dispatching accessibility button click", re); + } + } + + /** + * Notifies that the visibility of the accessibility button in the system's navigation area + * has changed. + * + * @param shown {@code true} if the accessibility button is visible within the system + * navigation area, {@code false} otherwise + * @hide + */ + public void notifyAccessibilityButtonVisibilityChanged(boolean shown) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.notifyAccessibilityButtonVisibilityChanged(shown); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re); + } + } + + /** + * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture + * window. Intended for use by the System UI only. + * + * @param connection The connection to handle the actions. Set to {@code null} to avoid + * affecting the actions. + * + * @hide + */ + public void setPictureInPictureActionReplacingConnection( + @Nullable IAccessibilityInteractionConnection connection) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.setPictureInPictureActionReplacingConnection(connection); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error setting picture in picture action replacement", re); + } } + private IAccessibilityManager getServiceLocked() { + if (mService == null) { + tryConnectToServiceLocked(null); + } + return mService; + } + + private void tryConnectToServiceLocked(IAccessibilityManager service) { + if (service == null) { + IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); + if (iBinder == null) { + return; + } + service = IAccessibilityManager.Stub.asInterface(iBinder); + } + + try { + final long userStateAndRelevantEvents = service.addClient(mClient, mUserId); + setStateLocked(IntPair.first(userStateAndRelevantEvents)); + mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents); + mService = service; + } catch (RemoteException re) { + Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); + } + } + + /** + * Notifies the registered {@link AccessibilityStateChangeListener}s. + */ + private void notifyAccessibilityStateChanged() { + final boolean isEnabled; + final ArrayMap<AccessibilityStateChangeListener, Handler> listeners; + synchronized (mLock) { + if (mAccessibilityStateChangeListeners.isEmpty()) { + return; + } + isEnabled = mIsEnabled; + listeners = new ArrayMap<>(mAccessibilityStateChangeListeners); + } + + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + final AccessibilityStateChangeListener listener = + mAccessibilityStateChangeListeners.keyAt(i); + mAccessibilityStateChangeListeners.valueAt(i) + .post(() -> listener.onAccessibilityStateChanged(isEnabled)); + } + } + + /** + * Notifies the registered {@link TouchExplorationStateChangeListener}s. + */ + private void notifyTouchExplorationStateChanged() { + final boolean isTouchExplorationEnabled; + final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners; + synchronized (mLock) { + if (mTouchExplorationStateChangeListeners.isEmpty()) { + return; + } + isTouchExplorationEnabled = mIsTouchExplorationEnabled; + listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners); + } + + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + final TouchExplorationStateChangeListener listener = + mTouchExplorationStateChangeListeners.keyAt(i); + mTouchExplorationStateChangeListeners.valueAt(i) + .post(() -> listener.onTouchExplorationStateChanged(isTouchExplorationEnabled)); + } + } + + /** + * Notifies the registered {@link HighTextContrastChangeListener}s. + */ + private void notifyHighTextContrastStateChanged() { + final boolean isHighTextContrastEnabled; + final ArrayMap<HighTextContrastChangeListener, Handler> listeners; + synchronized (mLock) { + if (mHighTextContrastStateChangeListeners.isEmpty()) { + return; + } + isHighTextContrastEnabled = mIsHighTextContrastEnabled; + listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners); + } + + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; i++) { + final HighTextContrastChangeListener listener = + mHighTextContrastStateChangeListeners.keyAt(i); + mHighTextContrastStateChangeListeners.valueAt(i) + .post(() -> listener.onHighTextContrastStateChanged(isHighTextContrastEnabled)); + } + } + + /** + * Determines if the accessibility button within the system navigation area is supported. + * + * @return {@code true} if the accessibility button is supported on this device, + * {@code false} otherwise + */ + public static boolean isAccessibilityButtonSupported() { + final Resources res = Resources.getSystem(); + return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar); + } + + private final class MyCallback implements Handler.Callback { + public static final int MSG_SET_STATE = 1; + + @Override + public boolean handleMessage(Message message) { + switch (message.what) { + case MSG_SET_STATE: { + // See comment at mClient + final int state = message.arg1; + synchronized (mLock) { + setStateLocked(state); + } + } break; + } + return true; + } + } } diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java index e564fa34..4fb2a99a 100644 --- a/android/view/autofill/AutofillManager.java +++ b/android/view/autofill/AutofillManager.java @@ -91,10 +91,10 @@ import java.util.Objects; * </ul> * * <p>When the service returns datasets, the Android System displays an autofill dataset picker - * UI associated with the view, when the view is focused on and is part of a dataset. - * The application can be notified when the UI is shown by registering an + * UI affordance associated with the view, when the view is focused on and is part of a dataset. + * The application can be notified when the affordance is shown by registering an * {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user - * selects a dataset from the UI, all views present in the dataset are autofilled, through + * selects a dataset from the affordance, all views present in the dataset are autofilled, through * calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}. * * <p>When the service returns ids of savable views, the Android System keeps track of changes @@ -108,7 +108,7 @@ import java.util.Objects; * </ul> * * <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System - * shows an autofill save UI if the value of savable views have changed. If the user selects the + * shows a save UI affordance if the value of savable views have changed. If the user selects the * option to Save, the current value of the views is then sent to the autofill service. * * <p>It is safe to call into its methods from any thread. @@ -150,12 +150,6 @@ public final class AutofillManager { * service authentication will contain the Bundle set by * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra. * - * <p>On Android {@link android.os.Build.VERSION_CODES#P} and higher, the autofill service - * can also add this bundle to the {@link Intent} set as the - * {@link android.app.Activity#setResult(int, Intent) result} for an authentication request, - * so the bundle can be recovered later on - * {@link android.service.autofill.SaveRequest#getClientState()}. - * * <p> * Type: {@link android.os.Bundle} */ @@ -317,14 +311,6 @@ public final class AutofillManager { @GuardedBy("mLock") @Nullable private ArraySet<AutofillId> mFillableIds; - /** If set, session is commited when the field is clicked. */ - @GuardedBy("mLock") - @Nullable private AutofillId mSaveTriggerId; - - /** If set, session is commited when the activity is finished; otherwise session is canceled. */ - @GuardedBy("mLock") - private boolean mSaveOnFinish; - /** @hide */ public interface AutofillClient { /** @@ -848,46 +834,6 @@ public final class AutofillManager { } } - - /** - * Called when a {@link View} is clicked. Currently only used by views that should trigger save. - * - * @hide - */ - public void notifyViewClicked(View view) { - final AutofillId id = view.getAutofillId(); - - if (sVerbose) Log.v(TAG, "notifyViewClicked(): id=" + id + ", trigger=" + mSaveTriggerId); - - synchronized (mLock) { - if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) { - if (sDebug) Log.d(TAG, "triggering commit by click of " + id); - commitLocked(); - mMetricsLogger.action(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED, - mContext.getPackageName()); - } - } - } - - /** - * Called by {@link android.app.Activity} to commit or cancel the session on finish. - * - * @hide - */ - public void onActivityFinished() { - if (!hasAutofillFeature()) { - return; - } - synchronized (mLock) { - if (mSaveOnFinish) { - commitLocked(); - } else { - if (sDebug) Log.d(TAG, "Cancelling session on finish() as requested by service"); - cancelLocked(); - } - } - } - /** * Called to indicate the current autofill context should be commited. * @@ -904,15 +850,12 @@ public final class AutofillManager { return; } synchronized (mLock) { - commitLocked(); - } - } + if (!mEnabled && !isActiveLocked()) { + return; + } - private void commitLocked() { - if (!mEnabled && !isActiveLocked()) { - return; + finishSessionLocked(); } - finishSessionLocked(); } /** @@ -931,15 +874,12 @@ public final class AutofillManager { return; } synchronized (mLock) { - cancelLocked(); - } - } + if (!mEnabled && !isActiveLocked()) { + return; + } - private void cancelLocked() { - if (!mEnabled && !isActiveLocked()) { - return; + cancelSessionLocked(); } - cancelSessionLocked(); } /** @hide */ @@ -997,12 +937,7 @@ public final class AutofillManager { } private AutofillClient getClientLocked() { - final AutofillClient client = mContext.getAutofillClient(); - if (client == null && sDebug) { - Log.d(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context " - + mContext); - } - return client; + return mContext.getAutofillClient(); } /** @hide */ @@ -1024,10 +959,6 @@ public final class AutofillManager { final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT); final Bundle responseData = new Bundle(); responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result); - final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE); - if (newClientState != null) { - responseData.putBundle(EXTRA_CLIENT_STATE, newClientState); - } try { mService.setAuthenticationResult(responseData, mSessionId, authenticationId, mContext.getUserId()); @@ -1107,7 +1038,6 @@ public final class AutofillManager { mState = STATE_UNKNOWN; mTrackedViews = null; mFillableIds = null; - mSaveTriggerId = null; } private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action, @@ -1359,15 +1289,12 @@ public final class AutofillManager { /** * Set the tracked views. * - * @param trackedIds The views to be tracked. + * @param trackedIds The views to be tracked * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible. - * @param saveOnFinish Finish the session once the activity is finished. * @param fillableIds Views that might anchor FillUI. - * @param saveTriggerId View that when clicked triggers commit(). */ private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds, - boolean saveOnAllViewsInvisible, boolean saveOnFinish, - @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) { + boolean saveOnAllViewsInvisible, @Nullable AutofillId[] fillableIds) { synchronized (mLock) { if (mEnabled && mSessionId == sessionId) { if (saveOnAllViewsInvisible) { @@ -1375,7 +1302,6 @@ public final class AutofillManager { } else { mTrackedViews = null; } - mSaveOnFinish = saveOnFinish; if (fillableIds != null) { if (mFillableIds == null) { mFillableIds = new ArraySet<>(fillableIds.length); @@ -1388,30 +1314,10 @@ public final class AutofillManager { + ", mFillableIds" + mFillableIds); } } - - if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) { - // Turn off trigger on previous view id. - setNotifyOnClickLocked(mSaveTriggerId, false); - } - - if (saveTriggerId != null && !saveTriggerId.equals(mSaveTriggerId)) { - // Turn on trigger on new view id. - mSaveTriggerId = saveTriggerId; - setNotifyOnClickLocked(mSaveTriggerId, true); - } } } } - private void setNotifyOnClickLocked(@NonNull AutofillId id, boolean notify) { - final View view = findView(id); - if (view == null) { - Log.w(TAG, "setNotifyOnClick(): invalid id: " + id); - return; - } - view.setNotifyAutofillManagerOnClick(notify); - } - private void setSaveUiState(int sessionId, boolean shown) { if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown); synchronized (mLock) { @@ -1584,7 +1490,6 @@ public final class AutofillManager { final String pfx = outerPrefix + " "; pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId); pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked()); - pw.print(pfx); pw.print("context: "); pw.println(mContext); pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled); pw.print(pfx); pw.print("hasService: "); pw.println(mService != null); pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null); @@ -1599,8 +1504,6 @@ public final class AutofillManager { pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds); } pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds); - pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId); - pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish); } private String getStateAsStringLocked() { @@ -1849,7 +1752,7 @@ public final class AutofillManager { * Callback for autofill related events. * * <p>Typically used for applications that display their own "auto-complete" views, so they can - * enable / disable such views when the autofill UI is shown / hidden. + * enable / disable such views when the autofill UI affordance is shown / hidden. */ public abstract static class AutofillCallback { @@ -1859,26 +1762,26 @@ public final class AutofillManager { public @interface AutofillEventType {} /** - * The autofill input UI associated with the view was shown. + * The autofill input UI affordance associated with the view was shown. * - * <p>If the view provides its own auto-complete UI and its currently shown, it + * <p>If the view provides its own auto-complete UI affordance and its currently shown, it * should be hidden upon receiving this event. */ public static final int EVENT_INPUT_SHOWN = 1; /** - * The autofill input UI associated with the view was hidden. + * The autofill input UI affordance associated with the view was hidden. * - * <p>If the view provides its own auto-complete UI that was hidden upon a + * <p>If the view provides its own auto-complete UI affordance that was hidden upon a * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now. */ public static final int EVENT_INPUT_HIDDEN = 2; /** - * The autofill input UI associated with the view isn't shown because + * The autofill input UI affordance associated with the view isn't shown because * autofill is not available. * - * <p>If the view provides its own auto-complete UI but was not displaying it + * <p>If the view provides its own auto-complete UI affordance but was not displaying it * to avoid flickering, it could shown it upon receiving this event. */ public static final int EVENT_INPUT_UNAVAILABLE = 3; @@ -1980,12 +1883,12 @@ public final class AutofillManager { @Override public void setTrackedViews(int sessionId, AutofillId[] ids, - boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds, - AutofillId saveTriggerId) { + boolean saveOnAllViewsInvisible, AutofillId[] fillableIds) { final AutofillManager afm = mAfm.get(); if (afm != null) { - afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, - saveOnFinish, fillableIds, saveTriggerId)); + afm.post(() -> + afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, fillableIds) + ); } } diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java index c3601d9d..bb1e693f 100644 --- a/android/view/textclassifier/TextClassifier.java +++ b/android/view/textclassifier/TextClassifier.java @@ -152,12 +152,4 @@ public interface TextClassifier { */ @WorkerThread default void logEvent(String source, String event) {} - - /** - * Returns this TextClassifier's settings. - * @hide - */ - default TextClassifierConstants getSettings() { - return TextClassifierConstants.DEFAULT; - } } diff --git a/android/view/textclassifier/TextClassifierConstants.java b/android/view/textclassifier/TextClassifierConstants.java deleted file mode 100644 index 51e6168e..00000000 --- a/android/view/textclassifier/TextClassifierConstants.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view.textclassifier; - -import android.annotation.Nullable; -import android.util.KeyValueListParser; -import android.util.Slog; - -/** - * TextClassifier specific settings. - * This is encoded as a key=value list, separated by commas. Ex: - * - * <pre> - * smart_selection_dark_launch (boolean) - * smart_selection_enabled_for_edit_text (boolean) - * </pre> - * - * <p> - * Type: string - * see also android.provider.Settings.Global.TEXT_CLASSIFIER_CONSTANTS - * - * Example of setting the values for testing. - * adb shell settings put global text_classifier_constants smart_selection_dark_launch=true,smart_selection_enabled_for_edit_text=true - * @hide - */ -public final class TextClassifierConstants { - - private static final String LOG_TAG = "TextClassifierConstants"; - - private static final String SMART_SELECTION_DARK_LAUNCH = - "smart_selection_dark_launch"; - private static final String SMART_SELECTION_ENABLED_FOR_EDIT_TEXT = - "smart_selection_enabled_for_edit_text"; - - private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false; - private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true; - - /** Default settings. */ - static final TextClassifierConstants DEFAULT = new TextClassifierConstants(); - - private final boolean mDarkLaunch; - private final boolean mSuggestSelectionEnabledForEditableText; - - private TextClassifierConstants() { - mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT; - mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT; - } - - private TextClassifierConstants(@Nullable String settings) { - final KeyValueListParser parser = new KeyValueListParser(','); - try { - parser.setString(settings); - } catch (IllegalArgumentException e) { - // Failed to parse the settings string, log this and move on with defaults. - Slog.e(LOG_TAG, "Bad TextClassifier settings: " + settings); - } - mDarkLaunch = parser.getBoolean( - SMART_SELECTION_DARK_LAUNCH, - SMART_SELECTION_DARK_LAUNCH_DEFAULT); - mSuggestSelectionEnabledForEditableText = parser.getBoolean( - SMART_SELECTION_ENABLED_FOR_EDIT_TEXT, - SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT); - } - - static TextClassifierConstants loadFromString(String settings) { - return new TextClassifierConstants(settings); - } - - public boolean isDarkLaunch() { - return mDarkLaunch; - } - - public boolean isSuggestSelectionEnabledForEditableText() { - return mSuggestSelectionEnabledForEditableText; - } -} diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java index ef087472..2aa81a2c 100644 --- a/android/view/textclassifier/TextClassifierImpl.java +++ b/android/view/textclassifier/TextClassifierImpl.java @@ -24,12 +24,12 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; +import android.icu.text.BreakIterator; import android.net.Uri; import android.os.LocaleList; import android.os.ParcelFileDescriptor; import android.provider.Browser; import android.provider.ContactsContract; -import android.provider.Settings; import android.text.Spannable; import android.text.TextUtils; import android.text.method.WordIterator; @@ -47,7 +47,6 @@ import com.android.internal.util.Preconditions; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.text.BreakIterator; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -92,8 +91,6 @@ final class TextClassifierImpl implements TextClassifier { @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. private SmartSelection mSmartSelection; - private TextClassifierConstants mSettings; - TextClassifierImpl(Context context) { mContext = Preconditions.checkNotNull(context); } @@ -192,15 +189,6 @@ final class TextClassifierImpl implements TextClassifier { } } - @Override - public TextClassifierConstants getSettings() { - if (mSettings == null) { - mSettings = TextClassifierConstants.loadFromString(Settings.Global.getString( - mContext.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS)); - } - return mSettings; - } - private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException { synchronized (mSmartSelectionLock) { localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList; 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. + * + * <h3>Architecture Overview</h3> + * + * <p>There are three primary parties involved in the text services + * framework (TSF) architecture:</p> + * + * <ul> + * <li> The <strong>text services manager</strong> as expressed by this class + * is the central point of the system that manages interaction between all + * other parts. It is expressed as the client-side API here which exists + * in each application context and communicates with a global system service + * that manages the interaction across all processes. + * <li> A <strong>text service</strong> implements a particular + * interaction model allowing the client application to retrieve information of text. + * The system binds to the current text service that is in use, causing it to be created and run. + * <li> Multiple <strong>client applications</strong> arbitrate with the text service + * manager for connections to text services. + * </ul> + * + * <h3>Text services sessions</h3> + * <ul> + * <li>The <strong>spell checker session</strong> is one of the text services. + * {@link android.view.textservice.SpellCheckerSession}</li> + * </ul> + * */ +@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE) public final class TextServicesManager { - private static final TextServicesManager sInstance = new TextServicesManager(); - private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0]; + private static final String TAG = TextServicesManager.class.getSimpleName(); + private static final boolean DBG = false; + + private static TextServicesManager sInstance; + + private final ITextServicesManager mService; + + private TextServicesManager() throws ServiceNotFoundException { + mService = ITextServicesManager.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE)); + } /** * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist. * @hide */ public static TextServicesManager getInstance() { - return sInstance; + synchronized (TextServicesManager.class) { + if (sInstance == null) { + try { + sInstance = new TextServicesManager(); + } catch (ServiceNotFoundException e) { + throw new IllegalStateException(e); + } + } + return sInstance; + } + } + + /** + * Returns the language component of a given locale string. + */ + private static String parseLanguageFromLocaleString(String locale) { + final int idx = locale.indexOf('_'); + if (idx < 0) { + return locale; + } else { + return locale.substring(0, idx); + } } + /** + * Get a spell checker session for the specified spell checker + * @param locale the locale for the spell checker. If {@code locale} is null and + * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be + * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true, + * the locale specified in Settings will be returned only when it is same as {@code locale}. + * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is + * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be + * selected. + * @param listener a spell checker session lister for getting results from a spell checker. + * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled + * languages in settings will be returned. + * @return the spell checker session of the spell checker + */ public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale, SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) { - return null; + if (listener == null) { + throw new NullPointerException(); + } + if (!referToSpellCheckerLanguageSettings && locale == null) { + throw new IllegalArgumentException("Locale should not be null if you don't refer" + + " settings."); + } + + if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) { + return null; + } + + final SpellCheckerInfo sci; + try { + sci = mService.getCurrentSpellChecker(null); + } catch (RemoteException e) { + return null; + } + if (sci == null) { + return null; + } + SpellCheckerSubtype subtypeInUse = null; + if (referToSpellCheckerLanguageSettings) { + subtypeInUse = getCurrentSpellCheckerSubtype(true); + if (subtypeInUse == null) { + return null; + } + if (locale != null) { + final String subtypeLocale = subtypeInUse.getLocale(); + final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale); + if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) { + return null; + } + } + } else { + final String localeStr = locale.toString(); + for (int i = 0; i < sci.getSubtypeCount(); ++i) { + final SpellCheckerSubtype subtype = sci.getSubtypeAt(i); + final String tempSubtypeLocale = subtype.getLocale(); + final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale); + if (tempSubtypeLocale.equals(localeStr)) { + subtypeInUse = subtype; + break; + } else if (tempSubtypeLanguage.length() >= 2 && + locale.getLanguage().equals(tempSubtypeLanguage)) { + subtypeInUse = subtype; + } + } + } + if (subtypeInUse == null) { + return null; + } + final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener); + try { + mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(), + session.getTextServicesSessionListener(), + session.getSpellCheckerSessionListener(), bundle); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return session; } /** * @hide */ public SpellCheckerInfo[] getEnabledSpellCheckers() { - return EMPTY_SPELL_CHECKER_INFO; + try { + final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers(); + if (DBG) { + Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null")); + } + return retval; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** * @hide */ public SpellCheckerInfo getCurrentSpellChecker() { - return null; + try { + // Passing null as a locale for ICS + return mService.getCurrentSpellChecker(null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -60,13 +215,22 @@ public final class TextServicesManager { */ public SpellCheckerSubtype getCurrentSpellCheckerSubtype( boolean allowImplicitlySelectedSubtype) { - return null; + try { + // Passing null as a locale until we support multiple enabled spell checker subtypes. + return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** * @hide */ public boolean isSpellCheckerEnabled() { - return false; + try { + return mService.isSpellCheckerEnabled(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } } |