summaryrefslogtreecommitdiff
path: root/android/view
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-10-10 15:20:13 -0400
committerJustin Klaassen <justinklaassen@google.com>2017-10-10 15:20:13 -0400
commit93b7ee4fce01df52a6607f0b1965cbafdfeaf1a6 (patch)
tree49f76f879a89c256a4f65b674086be50760bdffb /android/view
parentbc81c7ada5aab3806dd0b17498f5c9672c9b33c4 (diff)
downloadandroid-28-93b7ee4fce01df52a6607f0b1965cbafdfeaf1a6.tar.gz
Import Android SDK Platform P [4386628]
/google/data/ro/projects/android/fetch_artifact \ --bid 4386628 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4386628.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: I9b8400ac92116cae4f033d173f7a5682b26ccba9
Diffstat (limited to 'android/view')
-rw-r--r--android/view/DragEvent.java5
-rw-r--r--android/view/Gravity.java53
-rw-r--r--android/view/MenuInflater_Delegate.java14
-rw-r--r--android/view/Surface.java2
-rw-r--r--android/view/SurfaceControl.java21
-rw-r--r--android/view/SurfaceView.java1135
-rw-r--r--android/view/View.java145
-rw-r--r--android/view/ViewDebug.java75
-rw-r--r--android/view/ViewRootImpl.java6
-rw-r--r--android/view/ViewStructure.java2
-rw-r--r--android/view/WindowManager.java395
-rw-r--r--android/view/WindowManagerPolicy.java67
-rw-r--r--android/view/accessibility/AccessibilityManager.java916
-rw-r--r--android/view/autofill/AutofillManager.java129
-rw-r--r--android/view/autofill/AutofillPopupWindow.java29
-rw-r--r--android/view/textclassifier/TextClassification.java188
-rw-r--r--android/view/textclassifier/TextClassifierImpl.java118
-rw-r--r--android/view/textclassifier/logging/SmartSelectionEventTracker.java1
-rw-r--r--android/view/textservice/TextServicesManager.java200
19 files changed, 3192 insertions, 309 deletions
diff --git a/android/view/DragEvent.java b/android/view/DragEvent.java
index 16f2d7d0..2c9f8712 100644
--- a/android/view/DragEvent.java
+++ b/android/view/DragEvent.java
@@ -103,7 +103,7 @@ import com.android.internal.view.IDragAndDropPermissions;
* <tr>
* <td>ACTION_DRAG_ENDED</td>
* <td style="text-align: center;">&nbsp;</td>
- * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">X</td>
* <td style="text-align: center;">&nbsp;</td>
* <td style="text-align: center;">&nbsp;</td>
* <td style="text-align: center;">&nbsp;</td>
@@ -112,6 +112,7 @@ import com.android.internal.view.IDragAndDropPermissions;
* </table>
* <p>
* The {@link android.view.DragEvent#getAction()},
+ * {@link android.view.DragEvent#getLocalState()}
* {@link android.view.DragEvent#describeContents()},
* {@link android.view.DragEvent#writeToParcel(Parcel,int)}, and
* {@link android.view.DragEvent#toString()} methods always return valid data.
@@ -397,7 +398,7 @@ public class DragEvent implements Parcelable {
* operation. In all other activities this method will return null
* </p>
* <p>
- * This method returns valid data for all event actions except for {@link #ACTION_DRAG_ENDED}.
+ * This method returns valid data for all event actions.
* </p>
* @return The local state object sent to the system by startDragAndDrop().
*/
diff --git a/android/view/Gravity.java b/android/view/Gravity.java
index 324a1ae3..232ff255 100644
--- a/android/view/Gravity.java
+++ b/android/view/Gravity.java
@@ -440,4 +440,57 @@ public class Gravity
}
return result;
}
+
+ /**
+ * @hide
+ */
+ public static String toString(int gravity) {
+ final StringBuilder result = new StringBuilder();
+ if ((gravity & FILL) != 0) {
+ result.append("FILL").append(' ');
+ } else {
+ if ((gravity & FILL_VERTICAL) != 0) {
+ result.append("FILL_VERTICAL").append(' ');
+ } else {
+ if ((gravity & TOP) != 0) {
+ result.append("TOP").append(' ');
+ }
+ if ((gravity & BOTTOM) != 0) {
+ result.append("BOTTOM").append(' ');
+ }
+ }
+ if ((gravity & FILL_HORIZONTAL) != 0) {
+ result.append("FILL_HORIZONTAL").append(' ');
+ } else {
+ if ((gravity & START) != 0) {
+ result.append("START").append(' ');
+ } else if ((gravity & LEFT) != 0) {
+ result.append("LEFT").append(' ');
+ }
+ if ((gravity & END) != 0) {
+ result.append("END").append(' ');
+ } else if ((gravity & RIGHT) != 0) {
+ result.append("RIGHT").append(' ');
+ }
+ }
+ }
+ if ((gravity & CENTER) != 0) {
+ result.append("CENTER").append(' ');
+ } else {
+ if ((gravity & CENTER_VERTICAL) != 0) {
+ result.append("CENTER_VERTICAL").append(' ');
+ }
+ if ((gravity & CENTER_HORIZONTAL) != 0) {
+ result.append("CENTER_HORIZONTAL").append(' ');
+ }
+ }
+ if ((gravity & DISPLAY_CLIP_VERTICAL) != 0) {
+ result.append("DISPLAY_CLIP_VERTICAL").append(' ');
+ }
+ if ((gravity & DISPLAY_CLIP_VERTICAL) != 0) {
+ result.append("DISPLAY_CLIP_VERTICAL").append(' ');
+ }
+ result.deleteCharAt(result.length() - 1);
+ return result.toString();
+ }
}
diff --git a/android/view/MenuInflater_Delegate.java b/android/view/MenuInflater_Delegate.java
index 08a97d64..977a2a72 100644
--- a/android/view/MenuInflater_Delegate.java
+++ b/android/view/MenuInflater_Delegate.java
@@ -42,7 +42,6 @@ import android.util.AttributeSet;
* ViewInfo}, we check the corresponding view key in the menu item for the view and add it
*/
public class MenuInflater_Delegate {
-
@LayoutlibDelegate
/*package*/ static void registerMenu(MenuInflater thisInflater, MenuItem menuItem,
AttributeSet attrs) {
@@ -56,10 +55,15 @@ public class MenuInflater_Delegate {
return;
}
}
- // This means that Bridge did not take over the instantiation of some object properly.
- // This is most likely a bug in the LayoutLib code.
- Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
- "Action Bar Menu rendering may be incorrect.", null);
+
+ if (menuItem == null || !menuItem.getClass().getName().startsWith("android.support.")) {
+ // This means that Bridge did not take over the instantiation of some object properly.
+ // This is most likely a bug in the LayoutLib code.
+ // We suppress this error for AppCompat menus since we do not support them in the menu
+ // editor yet.
+ Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
+ "Action Bar Menu rendering may be incorrect.", null);
+ }
}
diff --git a/android/view/Surface.java b/android/view/Surface.java
index 2c1f7346..ddced6cd 100644
--- a/android/view/Surface.java
+++ b/android/view/Surface.java
@@ -762,7 +762,7 @@ public class Surface implements Parcelable {
return "ROTATION_270";
}
default: {
- throw new IllegalArgumentException("Invalid rotation: " + rotation);
+ return Integer.toString(rotation);
}
}
}
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
index 91932dd4..31daefff 100644
--- a/android/view/SurfaceControl.java
+++ b/android/view/SurfaceControl.java
@@ -18,6 +18,7 @@ package android.view;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import android.annotation.Size;
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
import android.graphics.Rect;
@@ -65,6 +66,7 @@ public class SurfaceControl {
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 nativeSetFlags(long nativeObject, int flags, int mask);
@@ -105,8 +107,8 @@ public class SurfaceControl {
long surfaceObject, long frame);
private static native void nativeReparentChildren(long nativeObject,
IBinder handle);
- private static native void nativeReparentChild(long nativeObject,
- IBinder parentHandle, IBinder childHandle);
+ private static native void nativeReparent(long nativeObject,
+ IBinder parentHandle);
private static native void nativeSeverChildren(long nativeObject);
private static native void nativeSetOverrideScalingMode(long nativeObject,
int scalingMode);
@@ -455,9 +457,9 @@ public class SurfaceControl {
nativeReparentChildren(mNativeObject, newParentHandle);
}
- /** Re-parents a specific child layer to a new parent */
- public void reparentChild(IBinder newParentHandle, IBinder childHandle) {
- nativeReparentChild(mNativeObject, newParentHandle, childHandle);
+ /** Re-parents this layer to a new parent. */
+ public void reparent(IBinder newParentHandle) {
+ nativeReparent(mNativeObject, newParentHandle);
}
public void detachChildren() {
@@ -552,6 +554,15 @@ public class SurfaceControl {
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();
+ nativeSetColor(mNativeObject, color);
+ }
+
public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
checkNotReleased();
nativeSetMatrix(mNativeObject, dsdx, dtdx, dtdy, dsdy);
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 e5bd5ac0..b6be2961 100644
--- a/android/view/View.java
+++ b/android/view/View.java
@@ -127,6 +127,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -1078,6 +1079,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
* value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE}</code>).
*
+ * <p>When annotating a view with this hint, it's recommended to use a date autofill value to
+ * avoid ambiguity when the autofill service provides a value for it. To understand why a
+ * value can be ambiguous, consider "April of 2020", which could be represented as either of
+ * the following options:
+ *
+ * <ul>
+ * <li>{@code "04/2020"}
+ * <li>{@code "4/2020"}
+ * <li>{@code "2020/04"}
+ * <li>{@code "2020/4"}
+ * <li>{@code "April/2020"}
+ * <li>{@code "Apr/2020"}
+ * </ul>
+ *
+ * <p>You define a date autofill value for the view by overriding the following methods:
+ *
+ * <ol>
+ * <li>{@link #getAutofillType()} to return {@link #AUTOFILL_TYPE_DATE}.
+ * <li>{@link #getAutofillValue()} to return a
+ * {@link AutofillValue#forDate(long) date autofillvalue}.
+ * <li>{@link #autofill(AutofillValue)} to expect a data autofillvalue.
+ * </ol>
+ *
* <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
*/
public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE =
@@ -1090,6 +1114,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
* value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH}</code>).
*
+ * <p>When annotating a view with this hint, it's recommended to use a text autofill value
+ * whose value is the numerical representation of the month, starting on {@code 1} to avoid
+ * ambiguity when the autofill service provides a value for it. To understand why a
+ * value can be ambiguous, consider "January", which could be represented as either of
+ *
+ * <ul>
+ * <li>{@code "1"}: recommended way.
+ * <li>{@code "0"}: if following the {@link Calendar#MONTH} convention.
+ * <li>{@code "January"}: full name, in English.
+ * <li>{@code "jan"}: abbreviated name, in English.
+ * <li>{@code "Janeiro"}: full name, in another language.
+ * </ul>
+ *
+ * <p>Another recommended approach is to use a date autofill value - see
+ * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE} for more details.
+ *
* <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
*/
public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH =
@@ -3702,15 +3742,90 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
@ViewDebug.ExportedProperty(flagMapping = {
- @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LOW_PROFILE,
- equals = SYSTEM_UI_FLAG_LOW_PROFILE,
- name = "SYSTEM_UI_FLAG_LOW_PROFILE", outputIf = true),
- @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
- equals = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
- name = "SYSTEM_UI_FLAG_HIDE_NAVIGATION", outputIf = true),
- @ViewDebug.FlagToString(mask = PUBLIC_STATUS_BAR_VISIBILITY_MASK,
- equals = SYSTEM_UI_FLAG_VISIBLE,
- name = "SYSTEM_UI_FLAG_VISIBLE", outputIf = true)
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LOW_PROFILE,
+ equals = SYSTEM_UI_FLAG_LOW_PROFILE,
+ name = "LOW_PROFILE"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
+ equals = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
+ name = "HIDE_NAVIGATION"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_FULLSCREEN,
+ equals = SYSTEM_UI_FLAG_FULLSCREEN,
+ name = "FULLSCREEN"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_STABLE,
+ equals = SYSTEM_UI_FLAG_LAYOUT_STABLE,
+ name = "LAYOUT_STABLE"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,
+ equals = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,
+ name = "LAYOUT_HIDE_NAVIGATION"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+ equals = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+ name = "LAYOUT_FULLSCREEN"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_IMMERSIVE,
+ equals = SYSTEM_UI_FLAG_IMMERSIVE,
+ name = "IMMERSIVE"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_IMMERSIVE_STICKY,
+ equals = SYSTEM_UI_FLAG_IMMERSIVE_STICKY,
+ name = "IMMERSIVE_STICKY"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+ equals = SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+ name = "LIGHT_STATUS_BAR"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+ equals = SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+ name = "LIGHT_NAVIGATION_BAR"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_EXPAND,
+ equals = STATUS_BAR_DISABLE_EXPAND,
+ name = "STATUS_BAR_DISABLE_EXPAND"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_ICONS,
+ equals = STATUS_BAR_DISABLE_NOTIFICATION_ICONS,
+ name = "STATUS_BAR_DISABLE_NOTIFICATION_ICONS"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_ALERTS,
+ equals = STATUS_BAR_DISABLE_NOTIFICATION_ALERTS,
+ name = "STATUS_BAR_DISABLE_NOTIFICATION_ALERTS"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_TICKER,
+ equals = STATUS_BAR_DISABLE_NOTIFICATION_TICKER,
+ name = "STATUS_BAR_DISABLE_NOTIFICATION_TICKER"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_SYSTEM_INFO,
+ equals = STATUS_BAR_DISABLE_SYSTEM_INFO,
+ name = "STATUS_BAR_DISABLE_SYSTEM_INFO"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_HOME,
+ equals = STATUS_BAR_DISABLE_HOME,
+ name = "STATUS_BAR_DISABLE_HOME"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_BACK,
+ equals = STATUS_BAR_DISABLE_BACK,
+ name = "STATUS_BAR_DISABLE_BACK"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_CLOCK,
+ equals = STATUS_BAR_DISABLE_CLOCK,
+ name = "STATUS_BAR_DISABLE_CLOCK"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_RECENT,
+ equals = STATUS_BAR_DISABLE_RECENT,
+ name = "STATUS_BAR_DISABLE_RECENT"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_SEARCH,
+ equals = STATUS_BAR_DISABLE_SEARCH,
+ name = "STATUS_BAR_DISABLE_SEARCH"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSIENT,
+ equals = STATUS_BAR_TRANSIENT,
+ name = "STATUS_BAR_TRANSIENT"),
+ @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSIENT,
+ equals = NAVIGATION_BAR_TRANSIENT,
+ name = "NAVIGATION_BAR_TRANSIENT"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_UNHIDE,
+ equals = STATUS_BAR_UNHIDE,
+ name = "STATUS_BAR_UNHIDE"),
+ @ViewDebug.FlagToString(mask = NAVIGATION_BAR_UNHIDE,
+ equals = NAVIGATION_BAR_UNHIDE,
+ name = "NAVIGATION_BAR_UNHIDE"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSLUCENT,
+ equals = STATUS_BAR_TRANSLUCENT,
+ name = "STATUS_BAR_TRANSLUCENT"),
+ @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSLUCENT,
+ equals = NAVIGATION_BAR_TRANSLUCENT,
+ name = "NAVIGATION_BAR_TRANSLUCENT"),
+ @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSPARENT,
+ equals = NAVIGATION_BAR_TRANSPARENT,
+ name = "NAVIGATION_BAR_TRANSPARENT"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSPARENT,
+ equals = STATUS_BAR_TRANSPARENT,
+ name = "STATUS_BAR_TRANSPARENT")
}, formatToHexString = true)
int mSystemUiVisibility;
@@ -15414,7 +15529,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* {@code dirty}.
*
* @param dirty the rectangle representing the bounds of the dirty region
+ *
+ * @deprecated The switch to hardware accelerated rendering in API 14 reduced
+ * the importance of the dirty rectangle. In API 21 the given rectangle is
+ * ignored entirely in favor of an internally-calculated area instead.
+ * Because of this, clients are encouraged to just call {@link #invalidate()}.
*/
+ @Deprecated
public void invalidate(Rect dirty) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
@@ -15435,7 +15556,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param t the top position of the dirty region
* @param r the right position of the dirty region
* @param b the bottom position of the dirty region
+ *
+ * @deprecated The switch to hardware accelerated rendering in API 14 reduced
+ * the importance of the dirty rectangle. In API 21 the given rectangle is
+ * ignored entirely in favor of an internally-calculated area instead.
+ * Because of this, clients are encouraged to just call {@link #invalidate()}.
*/
+ @Deprecated
public void invalidate(int l, int t, int r, int b) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
diff --git a/android/view/ViewDebug.java b/android/view/ViewDebug.java
index 66c05785..3426485e 100644
--- a/android/view/ViewDebug.java
+++ b/android/view/ViewDebug.java
@@ -1375,6 +1375,81 @@ public class ViewDebug {
}
}
+ /**
+ * Converts an integer from a field that is mapped with {@link IntToString} to its string
+ * representation.
+ *
+ * @param clazz The class the field is defined on.
+ * @param field The field on which the {@link ExportedProperty} is defined on.
+ * @param integer The value to convert.
+ * @return The value converted into its string representation.
+ * @hide
+ */
+ public static String intToString(Class<?> clazz, String field, int integer) {
+ final IntToString[] mapping = getMapping(clazz, field);
+ if (mapping == null) {
+ return Integer.toString(integer);
+ }
+ final int count = mapping.length;
+ for (int j = 0; j < count; j++) {
+ final IntToString map = mapping[j];
+ if (map.from() == integer) {
+ return map.to();
+ }
+ }
+ return Integer.toString(integer);
+ }
+
+ /**
+ * Converts a set of flags from a field that is mapped with {@link FlagToString} to its string
+ * representation.
+ *
+ * @param clazz The class the field is defined on.
+ * @param field The field on which the {@link ExportedProperty} is defined on.
+ * @param flags The flags to convert.
+ * @return The flags converted into their string representations.
+ * @hide
+ */
+ public static String flagsToString(Class<?> clazz, String field, int flags) {
+ final FlagToString[] mapping = getFlagMapping(clazz, field);
+ if (mapping == null) {
+ return Integer.toHexString(flags);
+ }
+ final StringBuilder result = new StringBuilder();
+ final int count = mapping.length;
+ for (int j = 0; j < count; j++) {
+ final FlagToString flagMapping = mapping[j];
+ final boolean ifTrue = flagMapping.outputIf();
+ final int maskResult = flags & flagMapping.mask();
+ final boolean test = maskResult == flagMapping.equals();
+ if (test && ifTrue) {
+ final String name = flagMapping.name();
+ result.append(name).append(' ');
+ }
+ }
+ if (result.length() > 0) {
+ result.deleteCharAt(result.length() - 1);
+ }
+ return result.toString();
+ }
+
+ private static FlagToString[] getFlagMapping(Class<?> clazz, String field) {
+ try {
+ return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class)
+ .flagMapping();
+ } catch (NoSuchFieldException e) {
+ return null;
+ }
+ }
+
+ private static IntToString[] getMapping(Class<?> clazz, String field) {
+ try {
+ return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class).mapping();
+ } catch (NoSuchFieldException e) {
+ return null;
+ }
+ }
+
private static void exportUnrolledArray(Context context, BufferedWriter out,
ExportedProperty property, int[] array, String prefix, String suffix)
throws IOException {
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
index 415aad54..71106ada 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -366,7 +366,7 @@ public final class ViewRootImpl implements ViewParent,
// These can be accessed by any thread, must be protected with a lock.
// Surface can never be reassigned or cleared (use Surface.clear()).
- final Surface mSurface = new Surface();
+ public final Surface mSurface = new Surface();
boolean mAdded;
boolean mAddedTouchMode;
@@ -512,7 +512,7 @@ public final class ViewRootImpl implements ViewParent,
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
if (!sCompatibilityDone) {
- sAlwaysAssignFocus = true;
+ sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
sCompatibilityDone = true;
}
@@ -7714,7 +7714,7 @@ public final class ViewRootImpl implements ViewParent,
public void onAccessibilityStateChanged(boolean enabled) {
if (enabled) {
ensureConnection();
- if (mAttachInfo.mHasWindowFocus) {
+ if (mAttachInfo.mHasWindowFocus && (mView != null)) {
mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
View focusedView = mView.findFocus();
if (focusedView != null && focusedView != mView) {
diff --git a/android/view/ViewStructure.java b/android/view/ViewStructure.java
index 0ecd20da..f671c349 100644
--- a/android/view/ViewStructure.java
+++ b/android/view/ViewStructure.java
@@ -378,7 +378,7 @@ public abstract class ViewStructure {
*
* <p>Typically used when the view is a container for an HTML document.
*
- * @param domain URL representing the domain; only the host part will be used.
+ * @param domain RFC 2396-compliant URI representing the domain.
*/
public abstract void setWebDomain(@Nullable String domain);
diff --git a/android/view/WindowManager.java b/android/view/WindowManager.java
index e56a82ff..c29a1daf 100644
--- a/android/view/WindowManager.java
+++ b/android/view/WindowManager.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT;
+
import android.Manifest.permission;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -268,93 +270,93 @@ public interface WindowManager extends ViewManager {
*/
@ViewDebug.ExportedProperty(mapping = {
@ViewDebug.IntToString(from = TYPE_BASE_APPLICATION,
- to = "TYPE_BASE_APPLICATION"),
+ to = "BASE_APPLICATION"),
@ViewDebug.IntToString(from = TYPE_APPLICATION,
- to = "TYPE_APPLICATION"),
+ to = "APPLICATION"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_STARTING,
- to = "TYPE_APPLICATION_STARTING"),
+ to = "APPLICATION_STARTING"),
@ViewDebug.IntToString(from = TYPE_DRAWN_APPLICATION,
- to = "TYPE_DRAWN_APPLICATION"),
+ to = "DRAWN_APPLICATION"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_PANEL,
- to = "TYPE_APPLICATION_PANEL"),
+ to = "APPLICATION_PANEL"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA,
- to = "TYPE_APPLICATION_MEDIA"),
+ to = "APPLICATION_MEDIA"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_SUB_PANEL,
- to = "TYPE_APPLICATION_SUB_PANEL"),
+ to = "APPLICATION_SUB_PANEL"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_ABOVE_SUB_PANEL,
- to = "TYPE_APPLICATION_ABOVE_SUB_PANEL"),
+ to = "APPLICATION_ABOVE_SUB_PANEL"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_ATTACHED_DIALOG,
- to = "TYPE_APPLICATION_ATTACHED_DIALOG"),
+ to = "APPLICATION_ATTACHED_DIALOG"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA_OVERLAY,
- to = "TYPE_APPLICATION_MEDIA_OVERLAY"),
+ to = "APPLICATION_MEDIA_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_STATUS_BAR,
- to = "TYPE_STATUS_BAR"),
+ to = "STATUS_BAR"),
@ViewDebug.IntToString(from = TYPE_SEARCH_BAR,
- to = "TYPE_SEARCH_BAR"),
+ to = "SEARCH_BAR"),
@ViewDebug.IntToString(from = TYPE_PHONE,
- to = "TYPE_PHONE"),
+ to = "PHONE"),
@ViewDebug.IntToString(from = TYPE_SYSTEM_ALERT,
- to = "TYPE_SYSTEM_ALERT"),
+ to = "SYSTEM_ALERT"),
@ViewDebug.IntToString(from = TYPE_TOAST,
- to = "TYPE_TOAST"),
+ to = "TOAST"),
@ViewDebug.IntToString(from = TYPE_SYSTEM_OVERLAY,
- to = "TYPE_SYSTEM_OVERLAY"),
+ to = "SYSTEM_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_PRIORITY_PHONE,
- to = "TYPE_PRIORITY_PHONE"),
+ to = "PRIORITY_PHONE"),
@ViewDebug.IntToString(from = TYPE_SYSTEM_DIALOG,
- to = "TYPE_SYSTEM_DIALOG"),
+ to = "SYSTEM_DIALOG"),
@ViewDebug.IntToString(from = TYPE_KEYGUARD_DIALOG,
- to = "TYPE_KEYGUARD_DIALOG"),
+ to = "KEYGUARD_DIALOG"),
@ViewDebug.IntToString(from = TYPE_SYSTEM_ERROR,
- to = "TYPE_SYSTEM_ERROR"),
+ to = "SYSTEM_ERROR"),
@ViewDebug.IntToString(from = TYPE_INPUT_METHOD,
- to = "TYPE_INPUT_METHOD"),
+ to = "INPUT_METHOD"),
@ViewDebug.IntToString(from = TYPE_INPUT_METHOD_DIALOG,
- to = "TYPE_INPUT_METHOD_DIALOG"),
+ to = "INPUT_METHOD_DIALOG"),
@ViewDebug.IntToString(from = TYPE_WALLPAPER,
- to = "TYPE_WALLPAPER"),
+ to = "WALLPAPER"),
@ViewDebug.IntToString(from = TYPE_STATUS_BAR_PANEL,
- to = "TYPE_STATUS_BAR_PANEL"),
+ to = "STATUS_BAR_PANEL"),
@ViewDebug.IntToString(from = TYPE_SECURE_SYSTEM_OVERLAY,
- to = "TYPE_SECURE_SYSTEM_OVERLAY"),
+ to = "SECURE_SYSTEM_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_DRAG,
- to = "TYPE_DRAG"),
+ to = "DRAG"),
@ViewDebug.IntToString(from = TYPE_STATUS_BAR_SUB_PANEL,
- to = "TYPE_STATUS_BAR_SUB_PANEL"),
+ to = "STATUS_BAR_SUB_PANEL"),
@ViewDebug.IntToString(from = TYPE_POINTER,
- to = "TYPE_POINTER"),
+ to = "POINTER"),
@ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR,
- to = "TYPE_NAVIGATION_BAR"),
+ to = "NAVIGATION_BAR"),
@ViewDebug.IntToString(from = TYPE_VOLUME_OVERLAY,
- to = "TYPE_VOLUME_OVERLAY"),
+ to = "VOLUME_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_BOOT_PROGRESS,
- to = "TYPE_BOOT_PROGRESS"),
+ to = "BOOT_PROGRESS"),
@ViewDebug.IntToString(from = TYPE_INPUT_CONSUMER,
- to = "TYPE_INPUT_CONSUMER"),
+ to = "INPUT_CONSUMER"),
@ViewDebug.IntToString(from = TYPE_DREAM,
- to = "TYPE_DREAM"),
+ to = "DREAM"),
@ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL,
- to = "TYPE_NAVIGATION_BAR_PANEL"),
+ to = "NAVIGATION_BAR_PANEL"),
@ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY,
- to = "TYPE_DISPLAY_OVERLAY"),
+ to = "DISPLAY_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY,
- to = "TYPE_MAGNIFICATION_OVERLAY"),
+ to = "MAGNIFICATION_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_PRESENTATION,
- to = "TYPE_PRESENTATION"),
+ to = "PRESENTATION"),
@ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION,
- to = "TYPE_PRIVATE_PRESENTATION"),
+ to = "PRIVATE_PRESENTATION"),
@ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION,
- to = "TYPE_VOICE_INTERACTION"),
+ to = "VOICE_INTERACTION"),
@ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION_STARTING,
- to = "TYPE_VOICE_INTERACTION_STARTING"),
+ to = "VOICE_INTERACTION_STARTING"),
@ViewDebug.IntToString(from = TYPE_DOCK_DIVIDER,
- to = "TYPE_DOCK_DIVIDER"),
+ to = "DOCK_DIVIDER"),
@ViewDebug.IntToString(from = TYPE_QS_DIALOG,
- to = "TYPE_QS_DIALOG"),
+ to = "QS_DIALOG"),
@ViewDebug.IntToString(from = TYPE_SCREENSHOT,
- to = "TYPE_SCREENSHOT"),
+ to = "SCREENSHOT"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_OVERLAY,
- to = "TYPE_APPLICATION_OVERLAY")
+ to = "APPLICATION_OVERLAY")
})
public int type;
@@ -1198,63 +1200,69 @@ public interface WindowManager extends ViewManager {
*/
@ViewDebug.ExportedProperty(flagMapping = {
@ViewDebug.FlagToString(mask = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON, equals = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON,
- name = "FLAG_ALLOW_LOCK_WHILE_SCREEN_ON"),
+ name = "ALLOW_LOCK_WHILE_SCREEN_ON"),
@ViewDebug.FlagToString(mask = FLAG_DIM_BEHIND, equals = FLAG_DIM_BEHIND,
- name = "FLAG_DIM_BEHIND"),
+ name = "DIM_BEHIND"),
@ViewDebug.FlagToString(mask = FLAG_BLUR_BEHIND, equals = FLAG_BLUR_BEHIND,
- name = "FLAG_BLUR_BEHIND"),
+ name = "BLUR_BEHIND"),
@ViewDebug.FlagToString(mask = FLAG_NOT_FOCUSABLE, equals = FLAG_NOT_FOCUSABLE,
- name = "FLAG_NOT_FOCUSABLE"),
+ name = "NOT_FOCUSABLE"),
@ViewDebug.FlagToString(mask = FLAG_NOT_TOUCHABLE, equals = FLAG_NOT_TOUCHABLE,
- name = "FLAG_NOT_TOUCHABLE"),
+ name = "NOT_TOUCHABLE"),
@ViewDebug.FlagToString(mask = FLAG_NOT_TOUCH_MODAL, equals = FLAG_NOT_TOUCH_MODAL,
- name = "FLAG_NOT_TOUCH_MODAL"),
+ name = "NOT_TOUCH_MODAL"),
@ViewDebug.FlagToString(mask = FLAG_TOUCHABLE_WHEN_WAKING, equals = FLAG_TOUCHABLE_WHEN_WAKING,
- name = "FLAG_TOUCHABLE_WHEN_WAKING"),
+ name = "TOUCHABLE_WHEN_WAKING"),
@ViewDebug.FlagToString(mask = FLAG_KEEP_SCREEN_ON, equals = FLAG_KEEP_SCREEN_ON,
- name = "FLAG_KEEP_SCREEN_ON"),
+ name = "KEEP_SCREEN_ON"),
@ViewDebug.FlagToString(mask = FLAG_LAYOUT_IN_SCREEN, equals = FLAG_LAYOUT_IN_SCREEN,
- name = "FLAG_LAYOUT_IN_SCREEN"),
+ name = "LAYOUT_IN_SCREEN"),
@ViewDebug.FlagToString(mask = FLAG_LAYOUT_NO_LIMITS, equals = FLAG_LAYOUT_NO_LIMITS,
- name = "FLAG_LAYOUT_NO_LIMITS"),
+ name = "LAYOUT_NO_LIMITS"),
@ViewDebug.FlagToString(mask = FLAG_FULLSCREEN, equals = FLAG_FULLSCREEN,
- name = "FLAG_FULLSCREEN"),
+ name = "FULLSCREEN"),
@ViewDebug.FlagToString(mask = FLAG_FORCE_NOT_FULLSCREEN, equals = FLAG_FORCE_NOT_FULLSCREEN,
- name = "FLAG_FORCE_NOT_FULLSCREEN"),
+ name = "FORCE_NOT_FULLSCREEN"),
@ViewDebug.FlagToString(mask = FLAG_DITHER, equals = FLAG_DITHER,
- name = "FLAG_DITHER"),
+ name = "DITHER"),
@ViewDebug.FlagToString(mask = FLAG_SECURE, equals = FLAG_SECURE,
- name = "FLAG_SECURE"),
+ name = "SECURE"),
@ViewDebug.FlagToString(mask = FLAG_SCALED, equals = FLAG_SCALED,
- name = "FLAG_SCALED"),
+ name = "SCALED"),
@ViewDebug.FlagToString(mask = FLAG_IGNORE_CHEEK_PRESSES, equals = FLAG_IGNORE_CHEEK_PRESSES,
- name = "FLAG_IGNORE_CHEEK_PRESSES"),
+ name = "IGNORE_CHEEK_PRESSES"),
@ViewDebug.FlagToString(mask = FLAG_LAYOUT_INSET_DECOR, equals = FLAG_LAYOUT_INSET_DECOR,
- name = "FLAG_LAYOUT_INSET_DECOR"),
+ name = "LAYOUT_INSET_DECOR"),
@ViewDebug.FlagToString(mask = FLAG_ALT_FOCUSABLE_IM, equals = FLAG_ALT_FOCUSABLE_IM,
- name = "FLAG_ALT_FOCUSABLE_IM"),
+ name = "ALT_FOCUSABLE_IM"),
@ViewDebug.FlagToString(mask = FLAG_WATCH_OUTSIDE_TOUCH, equals = FLAG_WATCH_OUTSIDE_TOUCH,
- name = "FLAG_WATCH_OUTSIDE_TOUCH"),
+ name = "WATCH_OUTSIDE_TOUCH"),
@ViewDebug.FlagToString(mask = FLAG_SHOW_WHEN_LOCKED, equals = FLAG_SHOW_WHEN_LOCKED,
- name = "FLAG_SHOW_WHEN_LOCKED"),
+ name = "SHOW_WHEN_LOCKED"),
@ViewDebug.FlagToString(mask = FLAG_SHOW_WALLPAPER, equals = FLAG_SHOW_WALLPAPER,
- name = "FLAG_SHOW_WALLPAPER"),
+ name = "SHOW_WALLPAPER"),
@ViewDebug.FlagToString(mask = FLAG_TURN_SCREEN_ON, equals = FLAG_TURN_SCREEN_ON,
- name = "FLAG_TURN_SCREEN_ON"),
+ name = "TURN_SCREEN_ON"),
@ViewDebug.FlagToString(mask = FLAG_DISMISS_KEYGUARD, equals = FLAG_DISMISS_KEYGUARD,
- name = "FLAG_DISMISS_KEYGUARD"),
+ name = "DISMISS_KEYGUARD"),
@ViewDebug.FlagToString(mask = FLAG_SPLIT_TOUCH, equals = FLAG_SPLIT_TOUCH,
- name = "FLAG_SPLIT_TOUCH"),
+ name = "SPLIT_TOUCH"),
@ViewDebug.FlagToString(mask = FLAG_HARDWARE_ACCELERATED, equals = FLAG_HARDWARE_ACCELERATED,
- name = "FLAG_HARDWARE_ACCELERATED"),
- @ViewDebug.FlagToString(mask = FLAG_LOCAL_FOCUS_MODE, equals = FLAG_LOCAL_FOCUS_MODE,
- name = "FLAG_LOCAL_FOCUS_MODE"),
+ name = "HARDWARE_ACCELERATED"),
+ @ViewDebug.FlagToString(mask = FLAG_LAYOUT_IN_OVERSCAN, equals = FLAG_LAYOUT_IN_OVERSCAN,
+ name = "LOCAL_FOCUS_MODE"),
@ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_STATUS, equals = FLAG_TRANSLUCENT_STATUS,
- name = "FLAG_TRANSLUCENT_STATUS"),
+ name = "TRANSLUCENT_STATUS"),
@ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_NAVIGATION, equals = FLAG_TRANSLUCENT_NAVIGATION,
- name = "FLAG_TRANSLUCENT_NAVIGATION"),
+ name = "TRANSLUCENT_NAVIGATION"),
+ @ViewDebug.FlagToString(mask = FLAG_LOCAL_FOCUS_MODE, equals = FLAG_LOCAL_FOCUS_MODE,
+ name = "LOCAL_FOCUS_MODE"),
+ @ViewDebug.FlagToString(mask = FLAG_SLIPPERY, equals = FLAG_SLIPPERY,
+ name = "FLAG_SLIPPERY"),
+ @ViewDebug.FlagToString(mask = FLAG_LAYOUT_ATTACHED_IN_DECOR, equals = FLAG_LAYOUT_ATTACHED_IN_DECOR,
+ name = "FLAG_LAYOUT_ATTACHED_IN_DECOR"),
@ViewDebug.FlagToString(mask = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, equals = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
- name = "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS")
+ name = "DRAWS_SYSTEM_BAR_BACKGROUNDS")
}, formatToHexString = true)
public int flags;
@@ -1438,6 +1446,88 @@ public interface WindowManager extends ViewManager {
* Control flags that are private to the platform.
* @hide
*/
+ @ViewDebug.ExportedProperty(flagMapping = {
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
+ equals = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
+ name = "FAKE_HARDWARE_ACCELERATED"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
+ equals = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
+ name = "FORCE_HARDWARE_ACCELERATED"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
+ equals = PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
+ name = "WANTS_OFFSET_NOTIFICATIONS"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
+ equals = PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
+ name = "SHOW_FOR_ALL_USERS"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_NO_MOVE_ANIMATION,
+ equals = PRIVATE_FLAG_NO_MOVE_ANIMATION,
+ name = "NO_MOVE_ANIMATION"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_COMPATIBLE_WINDOW,
+ equals = PRIVATE_FLAG_COMPATIBLE_WINDOW,
+ name = "COMPATIBLE_WINDOW"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_SYSTEM_ERROR,
+ equals = PRIVATE_FLAG_SYSTEM_ERROR,
+ name = "SYSTEM_ERROR"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR,
+ equals = PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR,
+ name = "INHERIT_TRANSLUCENT_DECOR"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_KEYGUARD,
+ equals = PRIVATE_FLAG_KEYGUARD,
+ name = "KEYGUARD"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
+ equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
+ name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT,
+ equals = PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT,
+ name = "FORCE_STATUS_BAR_VISIBLE_TRANSPARENT"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_PRESERVE_GEOMETRY,
+ equals = PRIVATE_FLAG_PRESERVE_GEOMETRY,
+ name = "PRESERVE_GEOMETRY"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
+ equals = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
+ name = "FORCE_DECOR_VIEW_VISIBILITY"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
+ equals = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
+ name = "WILL_NOT_REPLACE_ON_RELAUNCH"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
+ equals = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
+ name = "LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND,
+ equals = PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND,
+ name = "FORCE_DRAW_STATUS_BAR_BACKGROUND"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
+ equals = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
+ name = "SUSTAINED_PERFORMANCE_MODE"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ equals = PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ name = "HIDE_NON_SYSTEM_OVERLAY_WINDOWS"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
+ equals = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
+ name = "IS_ROUNDED_CORNERS_OVERLAY"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
+ equals = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
+ name = "ACQUIRES_SLEEP_TOKEN")
+ })
@TestApi
public int privateFlags;
@@ -1977,7 +2067,7 @@ public interface WindowManager extends ViewManager {
* @hide
*/
@ActivityInfo.ColorMode
- private int mColorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+ private int mColorMode = COLOR_MODE_DEFAULT;
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
@@ -2442,9 +2532,15 @@ public interface WindowManager extends ViewManager {
@Override
public String toString() {
+ return toString("");
+ }
+
+ /**
+ * @hide
+ */
+ public String toString(String prefix) {
StringBuilder sb = new StringBuilder(256);
- sb.append("WM.LayoutParams{");
- sb.append("(");
+ sb.append("{(");
sb.append(x);
sb.append(',');
sb.append(y);
@@ -2464,26 +2560,19 @@ public interface WindowManager extends ViewManager {
sb.append(verticalMargin);
}
if (gravity != 0) {
- sb.append(" gr=#");
- sb.append(Integer.toHexString(gravity));
+ sb.append(" gr=");
+ sb.append(Gravity.toString(gravity));
}
if (softInputMode != 0) {
- sb.append(" sim=#");
- sb.append(Integer.toHexString(softInputMode));
+ sb.append(" sim={");
+ sb.append(softInputModeToString(softInputMode));
+ sb.append('}');
}
sb.append(" ty=");
- sb.append(type);
- sb.append(" fl=#");
- sb.append(Integer.toHexString(flags));
- if (privateFlags != 0) {
- if ((privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) {
- sb.append(" compatible=true");
- }
- sb.append(" pfl=0x").append(Integer.toHexString(privateFlags));
- }
+ sb.append(ViewDebug.intToString(LayoutParams.class, "type", type));
if (format != PixelFormat.OPAQUE) {
sb.append(" fmt=");
- sb.append(format);
+ sb.append(PixelFormat.formatToString(format));
}
if (windowAnimations != 0) {
sb.append(" wanim=0x");
@@ -2491,7 +2580,7 @@ public interface WindowManager extends ViewManager {
}
if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
sb.append(" or=");
- sb.append(screenOrientation);
+ sb.append(ActivityInfo.screenOrientationToString(screenOrientation));
}
if (alpha != 1.0f) {
sb.append(" alpha=");
@@ -2507,7 +2596,7 @@ public interface WindowManager extends ViewManager {
}
if (rotationAnimation != ROTATION_ANIMATION_ROTATE) {
sb.append(" rotAnim=");
- sb.append(rotationAnimation);
+ sb.append(rotationAnimationToString(rotationAnimation));
}
if (preferredRefreshRate != 0) {
sb.append(" preferredRefreshRate=");
@@ -2517,20 +2606,12 @@ public interface WindowManager extends ViewManager {
sb.append(" preferredDisplayMode=");
sb.append(preferredDisplayModeId);
}
- if (systemUiVisibility != 0) {
- sb.append(" sysui=0x");
- sb.append(Integer.toHexString(systemUiVisibility));
- }
- if (subtreeSystemUiVisibility != 0) {
- sb.append(" vsysui=0x");
- sb.append(Integer.toHexString(subtreeSystemUiVisibility));
- }
if (hasSystemUiListeners) {
sb.append(" sysuil=");
sb.append(hasSystemUiListeners);
}
if (inputFeatures != 0) {
- sb.append(" if=0x").append(Integer.toHexString(inputFeatures));
+ sb.append(" if=").append(inputFeatureToString(inputFeatures));
}
if (userActivityTimeout >= 0) {
sb.append(" userActivityTimeout=").append(userActivityTimeout);
@@ -2546,11 +2627,30 @@ public interface WindowManager extends ViewManager {
sb.append(" (!preservePreviousSurfaceInsets)");
}
}
- if (needsMenuKey != NEEDS_MENU_UNSET) {
- sb.append(" needsMenuKey=");
- sb.append(needsMenuKey);
+ if (needsMenuKey == NEEDS_MENU_SET_TRUE) {
+ sb.append(" needsMenuKey");
+ }
+ if (mColorMode != COLOR_MODE_DEFAULT) {
+ sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode));
+ }
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" fl=").append(
+ ViewDebug.flagsToString(LayoutParams.class, "flags", flags));
+ if (privateFlags != 0) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" pfl=").append(ViewDebug.flagsToString(
+ LayoutParams.class, "privateFlags", privateFlags));
+ }
+ if (systemUiVisibility != 0) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" sysui=").append(ViewDebug.flagsToString(
+ View.class, "mSystemUiVisibility", systemUiVisibility));
+ }
+ if (subtreeSystemUiVisibility != 0) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" vsysui=").append(ViewDebug.flagsToString(
+ View.class, "mSystemUiVisibility", subtreeSystemUiVisibility));
}
- sb.append(" colorMode=").append(mColorMode);
sb.append('}');
return sb.toString();
}
@@ -2634,5 +2734,88 @@ public interface WindowManager extends ViewManager {
&& width == WindowManager.LayoutParams.MATCH_PARENT
&& height == WindowManager.LayoutParams.MATCH_PARENT;
}
+
+ private static String softInputModeToString(@SoftInputModeFlags int softInputMode) {
+ final StringBuilder result = new StringBuilder();
+ final int state = softInputMode & SOFT_INPUT_MASK_STATE;
+ if (state != 0) {
+ result.append("state=");
+ switch (state) {
+ case SOFT_INPUT_STATE_UNCHANGED:
+ result.append("unchanged");
+ break;
+ case SOFT_INPUT_STATE_HIDDEN:
+ result.append("hidden");
+ break;
+ case SOFT_INPUT_STATE_ALWAYS_HIDDEN:
+ result.append("always_hidden");
+ break;
+ case SOFT_INPUT_STATE_VISIBLE:
+ result.append("visible");
+ break;
+ case SOFT_INPUT_STATE_ALWAYS_VISIBLE:
+ result.append("always_visible");
+ break;
+ default:
+ result.append(state);
+ break;
+ }
+ result.append(' ');
+ }
+ final int adjust = softInputMode & SOFT_INPUT_MASK_ADJUST;
+ if (adjust != 0) {
+ result.append("adjust=");
+ switch (adjust) {
+ case SOFT_INPUT_ADJUST_RESIZE:
+ result.append("resize");
+ break;
+ case SOFT_INPUT_ADJUST_PAN:
+ result.append("pan");
+ break;
+ case SOFT_INPUT_ADJUST_NOTHING:
+ result.append("nothing");
+ break;
+ default:
+ result.append(adjust);
+ break;
+ }
+ result.append(' ');
+ }
+ if ((softInputMode & SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
+ result.append("forwardNavigation").append(' ');
+ }
+ result.deleteCharAt(result.length() - 1);
+ return result.toString();
+ }
+
+ private static String rotationAnimationToString(int rotationAnimation) {
+ switch (rotationAnimation) {
+ case ROTATION_ANIMATION_UNSPECIFIED:
+ return "UNSPECIFIED";
+ case ROTATION_ANIMATION_ROTATE:
+ return "ROTATE";
+ case ROTATION_ANIMATION_CROSSFADE:
+ return "CROSSFADE";
+ case ROTATION_ANIMATION_JUMPCUT:
+ return "JUMPCUT";
+ case ROTATION_ANIMATION_SEAMLESS:
+ return "SEAMLESS";
+ default:
+ return Integer.toString(rotationAnimation);
+ }
+ }
+
+ private static String inputFeatureToString(int inputFeature) {
+ switch (inputFeature) {
+ case INPUT_FEATURE_DISABLE_POINTER_GESTURES:
+ return "DISABLE_POINTER_GESTURES";
+ case INPUT_FEATURE_NO_INPUT_CHANNEL:
+ return "NO_INPUT_CHANNEL";
+ case INPUT_FEATURE_DISABLE_USER_ACTIVITY:
+ return "DISABLE_USER_ACTIVITY";
+ default:
+ return Integer.toString(inputFeature);
+ }
+ }
}
}
diff --git a/android/view/WindowManagerPolicy.java b/android/view/WindowManagerPolicy.java
index 66506a18..da72535d 100644
--- a/android/view/WindowManagerPolicy.java
+++ b/android/view/WindowManagerPolicy.java
@@ -592,9 +592,10 @@ public interface WindowManagerPolicy {
int getDockedDividerInsetsLw();
/**
- * Retrieves the {@param outBounds} from the stack with id {@param stackId}.
+ * Retrieves the {@param outBounds} from the stack matching the {@param windowingMode} and
+ * {@param activityType}.
*/
- void getStackBounds(int stackId, Rect outBounds);
+ void getStackBounds(int windowingMode, int activityType, Rect outBounds);
/**
* Notifies window manager that {@link #isShowingDreamLw} has changed.
@@ -617,6 +618,38 @@ public interface WindowManagerPolicy {
* @param listener callback to call when display can be turned off
*/
void screenTurningOff(ScreenOffListener listener);
+
+ /**
+ * Convert the lid state to a human readable format.
+ */
+ static String lidStateToString(int lid) {
+ switch (lid) {
+ case LID_ABSENT:
+ return "LID_ABSENT";
+ case LID_CLOSED:
+ return "LID_CLOSED";
+ case LID_OPEN:
+ return "LID_OPEN";
+ default:
+ return Integer.toString(lid);
+ }
+ }
+
+ /**
+ * Convert the camera lens state to a human readable format.
+ */
+ static String cameraLensStateToString(int lens) {
+ switch (lens) {
+ case CAMERA_LENS_COVER_ABSENT:
+ return "CAMERA_LENS_COVER_ABSENT";
+ case CAMERA_LENS_UNCOVERED:
+ return "CAMERA_LENS_UNCOVERED";
+ case CAMERA_LENS_COVERED:
+ return "CAMERA_LENS_COVERED";
+ default:
+ return Integer.toString(lens);
+ }
+ }
}
public interface PointerEventListener {
@@ -1750,4 +1783,34 @@ public interface WindowManagerPolicy {
* @return true if ready; false otherwise.
*/
boolean canDismissBootAnimation();
+
+ /**
+ * Convert the user rotation mode to a human readable format.
+ */
+ static String userRotationModeToString(int mode) {
+ switch(mode) {
+ case USER_ROTATION_FREE:
+ return "USER_ROTATION_FREE";
+ case USER_ROTATION_LOCKED:
+ return "USER_ROTATION_LOCKED";
+ default:
+ return Integer.toString(mode);
+ }
+ }
+
+ /**
+ * Convert the off reason to a human readable format.
+ */
+ static String offReasonToString(int why) {
+ switch (why) {
+ case OFF_BECAUSE_OF_ADMIN:
+ return "OFF_BECAUSE_OF_ADMIN";
+ case OFF_BECAUSE_OF_USER:
+ return "OFF_BECAUSE_OF_USER";
+ case OFF_BECAUSE_OF_TIMEOUT:
+ return "OFF_BECAUSE_OF_TIMEOUT";
+ default:
+ return Integer.toString(why);
+ }
+ }
}
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 61cbce97..4fb2a99a 100644
--- a/android/view/autofill/AutofillManager.java
+++ b/android/view/autofill/AutofillManager.java
@@ -37,14 +37,13 @@ import android.service.autofill.AutofillService;
import android.service.autofill.FillEventHistory;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.DebugUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -202,9 +201,12 @@ public final class AutofillManager {
* Initial state of the autofill context, set when there is no session (i.e., when
* {@link #mSessionId} is {@link #NO_SESSION}).
*
+ * <p>In this state, app callbacks (such as {@link #notifyViewEntered(View)}) are notified to
+ * the server.
+ *
* @hide
*/
- public static final int STATE_UNKNOWN = 1;
+ public static final int STATE_UNKNOWN = 0;
/**
* State where the autofill context hasn't been {@link #commit() finished} nor
@@ -212,7 +214,18 @@ public final class AutofillManager {
*
* @hide
*/
- public static final int STATE_ACTIVE = 2;
+ public static final int STATE_ACTIVE = 1;
+
+ /**
+ * State where the autofill context was finished by the server because the autofill
+ * service could not autofill the page.
+ *
+ * <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored,
+ * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}).
+ *
+ * @hide
+ */
+ public static final int STATE_FINISHED = 2;
/**
* State where the autofill context has been {@link #commit() finished} but the server still has
@@ -220,7 +233,7 @@ public final class AutofillManager {
*
* @hide
*/
- public static final int STATE_SHOWING_SAVE_UI = 4;
+ public static final int STATE_SHOWING_SAVE_UI = 3;
/**
* Makes an authentication id from a request id and a dataset id.
@@ -559,6 +572,14 @@ public final class AutofillManager {
}
AutofillCallback callback = null;
synchronized (mLock) {
+ if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
+ if (sVerbose) {
+ Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view
+ + "): ignored on state " + getStateAsStringLocked());
+ }
+ return;
+ }
+
ensureServiceClientAddedIfNeededLocked();
if (!mEnabled) {
@@ -682,6 +703,14 @@ public final class AutofillManager {
}
AutofillCallback callback = null;
synchronized (mLock) {
+ if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
+ if (sVerbose) {
+ Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view
+ + ", virtualId=" + virtualId
+ + "): ignored on state " + getStateAsStringLocked());
+ }
+ return;
+ }
ensureServiceClientAddedIfNeededLocked();
if (!mEnabled) {
@@ -765,6 +794,10 @@ public final class AutofillManager {
}
if (!mEnabled || !isActiveLocked()) {
+ if (sVerbose && mEnabled) {
+ Log.v(TAG, "notifyValueChanged(" + view + "): ignoring on state "
+ + getStateAsStringLocked());
+ }
return;
}
@@ -904,10 +937,7 @@ public final class AutofillManager {
}
private AutofillClient getClientLocked() {
- if (mContext instanceof AutofillClient) {
- return (AutofillClient) mContext;
- }
- return null;
+ return mContext.getAutofillClient();
}
/** @hide */
@@ -950,10 +980,13 @@ public final class AutofillManager {
@NonNull AutofillValue value, int flags) {
if (sVerbose) {
Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
- + ", flags=" + flags + ", state=" + mState);
+ + ", flags=" + flags + ", state=" + getStateAsStringLocked());
}
- if (mState != STATE_UNKNOWN) {
- if (sDebug) Log.d(TAG, "not starting session for " + id + " on state " + mState);
+ if (mState != STATE_UNKNOWN && (flags & FLAG_MANUAL_REQUEST) == 0) {
+ if (sVerbose) {
+ Log.v(TAG, "not automatically starting session for " + id
+ + " on state " + getStateAsStringLocked());
+ }
return;
}
try {
@@ -973,7 +1006,7 @@ public final class AutofillManager {
}
private void finishSessionLocked() {
- if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + mState);
+ if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + getStateAsStringLocked());
if (!isActiveLocked()) return;
@@ -987,7 +1020,7 @@ public final class AutofillManager {
}
private void cancelSessionLocked() {
- if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + mState);
+ if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + getStateAsStringLocked());
if (!isActiveLocked()) return;
@@ -1245,10 +1278,10 @@ public final class AutofillManager {
}
}
- final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
- log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
- log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED,
- numApplied);
+ final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_DATASET_APPLIED)
+ .setPackageName(mContext.getPackageName())
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
mMetricsLogger.write(log);
}
}
@@ -1306,6 +1339,20 @@ public final class AutofillManager {
}
}
+ /**
+ * Marks the state of the session as finished.
+ *
+ * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null}
+ * FillResponse) or {@link #STATE_UNKNOWN} (because the session was removed).
+ */
+ private void setSessionFinished(int newState) {
+ synchronized (mLock) {
+ if (sVerbose) Log.v(TAG, "setSessionFinished(): from " + mState + " to " + newState);
+ resetSessionLocked();
+ mState = newState;
+ }
+ }
+
private void requestHideFillUi(AutofillId id) {
final View anchor = findView(id);
if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor);
@@ -1341,7 +1388,11 @@ public final class AutofillManager {
}
}
- private void notifyNoFillUi(int sessionId, AutofillId id) {
+ private void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
+ if (sVerbose) {
+ Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id
+ + ", finished=" + sessionFinished);
+ }
final View anchor = findView(id);
if (anchor == null) {
return;
@@ -1361,7 +1412,11 @@ public final class AutofillManager {
} else {
callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
}
+ }
+ if (sessionFinished) {
+ // Callback call was "hijacked" to also update the session state.
+ setSessionFinished(STATE_FINISHED);
}
}
@@ -1434,8 +1489,7 @@ public final class AutofillManager {
pw.print(outerPrefix); pw.println("AutofillManager:");
final String pfx = outerPrefix + " ";
pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
- pw.print(pfx); pw.print("state: "); pw.println(
- DebugUtils.flagsToString(AutofillManager.class, "STATE_", mState));
+ pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
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);
@@ -1452,10 +1506,29 @@ public final class AutofillManager {
pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
}
+ private String getStateAsStringLocked() {
+ switch (mState) {
+ case STATE_UNKNOWN:
+ return "STATE_UNKNOWN";
+ case STATE_ACTIVE:
+ return "STATE_ACTIVE";
+ case STATE_FINISHED:
+ return "STATE_FINISHED";
+ case STATE_SHOWING_SAVE_UI:
+ return "STATE_SHOWING_SAVE_UI";
+ default:
+ return "INVALID:" + mState;
+ }
+ }
+
private boolean isActiveLocked() {
return mState == STATE_ACTIVE;
}
+ private boolean isFinishedLocked() {
+ return mState == STATE_FINISHED;
+ }
+
private void post(Runnable runnable) {
final AutofillClient client = getClientLocked();
if (client == null) {
@@ -1787,10 +1860,10 @@ public final class AutofillManager {
}
@Override
- public void notifyNoFillUi(int sessionId, AutofillId id) {
+ public void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.post(() -> afm.notifyNoFillUi(sessionId, id));
+ afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinished));
}
}
@@ -1823,7 +1896,15 @@ public final class AutofillManager {
public void setSaveUiState(int sessionId, boolean shown) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.post(() ->afm.setSaveUiState(sessionId, shown));
+ afm.post(() -> afm.setSaveUiState(sessionId, shown));
+ }
+ }
+
+ @Override
+ public void setSessionFinished(int newState) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.setSessionFinished(newState));
}
}
}
diff --git a/android/view/autofill/AutofillPopupWindow.java b/android/view/autofill/AutofillPopupWindow.java
index 5f476380..b4688bb1 100644
--- a/android/view/autofill/AutofillPopupWindow.java
+++ b/android/view/autofill/AutofillPopupWindow.java
@@ -47,6 +47,19 @@ public class AutofillPopupWindow extends PopupWindow {
private final WindowPresenter mWindowPresenter;
private WindowManager.LayoutParams mWindowLayoutParams;
+ private final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ /* ignore - handled by the super class */
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ dismiss();
+ }
+ };
+
/**
* Creates a popup window with a presenter owning the window and responsible for
* showing/hiding/updating the backing window. This can be useful of the window is
@@ -208,7 +221,21 @@ public class AutofillPopupWindow extends PopupWindow {
p.packageName = anchor.getContext().getPackageName();
mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(),
anchor.getLayoutDirection());
- return;
+ }
+
+ @Override
+ protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
+ super.attachToAnchor(anchor, xoff, yoff, gravity);
+ anchor.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+ }
+
+ @Override
+ protected void detachFromAnchor() {
+ final View anchor = getAnchor();
+ if (anchor != null) {
+ anchor.removeOnAttachStateChangeListener(mOnAttachStateChangeListener);
+ }
+ super.detachFromAnchor();
}
@Override
diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java
index 1849368f..8c3b8a2e 100644
--- a/android/view/textclassifier/TextClassification.java
+++ b/android/view/textclassifier/TextClassification.java
@@ -28,6 +28,7 @@ import android.view.textclassifier.TextClassifier.EntityType;
import com.android.internal.util.Preconditions;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -41,10 +42,10 @@ public final class TextClassification {
static final TextClassification EMPTY = new TextClassification.Builder().build();
@NonNull private final String mText;
- @Nullable private final Drawable mIcon;
- @Nullable private final String mLabel;
- @Nullable private final Intent mIntent;
- @Nullable private final OnClickListener mOnClickListener;
+ @NonNull private final List<Drawable> mIcons;
+ @NonNull private final List<String> mLabels;
+ @NonNull private final List<Intent> mIntents;
+ @NonNull private final List<OnClickListener> mOnClickListeners;
@NonNull private final EntityConfidence<String> mEntityConfidence;
@NonNull private final List<String> mEntities;
private int mLogType;
@@ -52,18 +53,21 @@ public final class TextClassification {
private TextClassification(
@Nullable String text,
- @Nullable Drawable icon,
- @Nullable String label,
- @Nullable Intent intent,
- @Nullable OnClickListener onClickListener,
+ @NonNull List<Drawable> icons,
+ @NonNull List<String> labels,
+ @NonNull List<Intent> intents,
+ @NonNull List<OnClickListener> onClickListeners,
@NonNull EntityConfidence<String> entityConfidence,
int logType,
@NonNull String versionInfo) {
+ Preconditions.checkArgument(labels.size() == intents.size());
+ Preconditions.checkArgument(icons.size() == intents.size());
+ Preconditions.checkArgument(onClickListeners.size() == intents.size());
mText = text;
- mIcon = icon;
- mLabel = label;
- mIntent = intent;
- mOnClickListener = onClickListener;
+ mIcons = icons;
+ mLabels = labels;
+ mIntents = intents;
+ mOnClickListeners = onClickListeners;
mEntityConfidence = new EntityConfidence<>(entityConfidence);
mEntities = mEntityConfidence.getEntities();
mLogType = logType;
@@ -109,35 +113,106 @@ public final class TextClassification {
}
/**
- * Returns an icon that may be rendered on a widget used to act on the classified text.
+ * Returns the number of actions that are available to act on the classified text.
+ * @see #getIntent(int)
+ * @see #getLabel(int)
+ * @see #getIcon(int)
+ * @see #getOnClickListener(int)
+ */
+ @IntRange(from = 0)
+ public int getActionCount() {
+ return mIntents.size();
+ }
+
+ /**
+ * Returns one of the icons that maybe rendered on a widget used to act on the classified text.
+ * @param index Index of the action to get the icon for.
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getActionCount() for the number of entities available.
+ * @see #getIntent(int)
+ * @see #getLabel(int)
+ * @see #getOnClickListener(int)
+ */
+ @Nullable
+ public Drawable getIcon(int index) {
+ return mIcons.get(index);
+ }
+
+ /**
+ * Returns an icon for the default intent that may be rendered on a widget used to act on the
+ * classified text.
*/
@Nullable
public Drawable getIcon() {
- return mIcon;
+ return mIcons.isEmpty() ? null : mIcons.get(0);
}
/**
- * Returns a label that may be rendered on a widget used to act on the classified text.
+ * Returns one of the labels that may be rendered on a widget used to act on the classified
+ * text.
+ * @param index Index of the action to get the label for.
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getActionCount()
+ * @see #getIntent(int)
+ * @see #getIcon(int)
+ * @see #getOnClickListener(int)
+ */
+ @Nullable
+ public CharSequence getLabel(int index) {
+ return mLabels.get(index);
+ }
+
+ /**
+ * Returns a label for the default intent that may be rendered on a widget used to act on the
+ * classified text.
*/
@Nullable
public CharSequence getLabel() {
- return mLabel;
+ return mLabels.isEmpty() ? null : mLabels.get(0);
}
/**
- * Returns an intent that may be fired to act on the classified text.
+ * Returns one of the intents that may be fired to act on the classified text.
+ * @param index Index of the action to get the intent for.
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getActionCount()
+ * @see #getLabel(int)
+ * @see #getIcon(int)
+ * @see #getOnClickListener(int)
+ */
+ @Nullable
+ public Intent getIntent(int index) {
+ return mIntents.get(index);
+ }
+
+ /**
+ * Returns the default intent that may be fired to act on the classified text.
*/
@Nullable
public Intent getIntent() {
- return mIntent;
+ return mIntents.isEmpty() ? null : mIntents.get(0);
}
/**
- * Returns an OnClickListener that may be triggered to act on the classified text.
+ * Returns one of the OnClickListeners that may be triggered to act on the classified text.
+ * @param index Index of the action to get the click listener for.
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getActionCount()
+ * @see #getIntent(int)
+ * @see #getLabel(int)
+ * @see #getIcon(int)
+ */
+ @Nullable
+ public OnClickListener getOnClickListener(int index) {
+ return mOnClickListeners.get(index);
+ }
+
+ /**
+ * Returns the default OnClickListener that may be triggered to act on the classified text.
*/
@Nullable
public OnClickListener getOnClickListener() {
- return mOnClickListener;
+ return mOnClickListeners.isEmpty() ? null : mOnClickListeners.get(0);
}
/**
@@ -160,8 +235,8 @@ public final class TextClassification {
@Override
public String toString() {
return String.format("TextClassification {"
- + "text=%s, entities=%s, label=%s, intent=%s}",
- mText, mEntityConfidence, mLabel, mIntent);
+ + "text=%s, entities=%s, labels=%s, intents=%s}",
+ mText, mEntityConfidence, mLabels, mIntents);
}
/**
@@ -184,10 +259,10 @@ public final class TextClassification {
public static final class Builder {
@NonNull private String mText;
- @Nullable private Drawable mIcon;
- @Nullable private String mLabel;
- @Nullable private Intent mIntent;
- @Nullable private OnClickListener mOnClickListener;
+ @NonNull private final List<Drawable> mIcons = new ArrayList<>();
+ @NonNull private final List<String> mLabels = new ArrayList<>();
+ @NonNull private final List<Intent> mIntents = new ArrayList<>();
+ @NonNull private final List<OnClickListener> mOnClickListeners = new ArrayList<>();
@NonNull private final EntityConfidence<String> mEntityConfidence =
new EntityConfidence<>();
private int mLogType;
@@ -216,26 +291,57 @@ public final class TextClassification {
}
/**
- * Sets an icon that may be rendered on a widget used to act on the classified text.
+ * Adds an action that may be performed on the classified text. The label and icon are used
+ * for rendering of widgets that offer the intent. Actions should be added in order of
+ * priority and the first one will be treated as the default.
+ */
+ public Builder addAction(
+ Intent intent, @Nullable String label, @Nullable Drawable icon,
+ @Nullable OnClickListener onClickListener) {
+ mIntents.add(intent);
+ mLabels.add(label);
+ mIcons.add(icon);
+ mOnClickListeners.add(onClickListener);
+ return this;
+ }
+
+ /**
+ * Removes all actions.
+ */
+ public Builder clearActions() {
+ mIntents.clear();
+ mOnClickListeners.clear();
+ mLabels.clear();
+ mIcons.clear();
+ return this;
+ }
+
+ /**
+ * Sets the icon for the default action that may be rendered on a widget used to act on the
+ * classified text.
*/
public Builder setIcon(@Nullable Drawable icon) {
- mIcon = icon;
+ ensureDefaultActionAvailable();
+ mIcons.set(0, icon);
return this;
}
/**
- * Sets a label that may be rendered on a widget used to act on the classified text.
+ * Sets the label for the default action that may be rendered on a widget used to act on the
+ * classified text.
*/
public Builder setLabel(@Nullable String label) {
- mLabel = label;
+ ensureDefaultActionAvailable();
+ mLabels.set(0, label);
return this;
}
/**
- * Sets an intent that may be fired to act on the classified text.
+ * Sets the intent for the default action that may be fired to act on the classified text.
*/
public Builder setIntent(@Nullable Intent intent) {
- mIntent = intent;
+ ensureDefaultActionAvailable();
+ mIntents.set(0, intent);
return this;
}
@@ -249,10 +355,12 @@ public final class TextClassification {
}
/**
- * Sets an OnClickListener that may be triggered to act on the classified text.
+ * Sets the OnClickListener for the default action that may be triggered to act on the
+ * classified text.
*/
public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
- mOnClickListener = onClickListener;
+ ensureDefaultActionAvailable();
+ mOnClickListeners.set(0, onClickListener);
return this;
}
@@ -266,11 +374,21 @@ public final class TextClassification {
}
/**
+ * Ensures that we have at we have storage for the default action.
+ */
+ private void ensureDefaultActionAvailable() {
+ if (mIntents.isEmpty()) mIntents.add(null);
+ if (mLabels.isEmpty()) mLabels.add(null);
+ if (mIcons.isEmpty()) mIcons.add(null);
+ if (mOnClickListeners.isEmpty()) mOnClickListeners.add(null);
+ }
+
+ /**
* Builds and returns a {@link TextClassification} object.
*/
public TextClassification build() {
return new TextClassification(
- mText, mIcon, mLabel, mIntent, mOnClickListener, mEntityConfidence,
+ mText, mIcons, mLabels, mIntents, mOnClickListeners, mEntityConfidence,
mLogType, mVersionInfo);
}
}
diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java
index 7e93b78c..2aa81a2c 100644
--- a/android/view/textclassifier/TextClassifierImpl.java
+++ b/android/view/textclassifier/TextClassifierImpl.java
@@ -29,6 +29,7 @@ import android.net.Uri;
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.provider.Browser;
+import android.provider.ContactsContract;
import android.text.Spannable;
import android.text.TextUtils;
import android.text.method.WordIterator;
@@ -356,7 +357,16 @@ final class TextClassifierImpl implements TextClassifier {
final String type = getHighestScoringType(classifications);
builder.setLogType(IntentFactory.getLogType(type));
- final Intent intent = IntentFactory.create(mContext, type, text.toString());
+ final List<Intent> intents = IntentFactory.create(mContext, type, text.toString());
+ for (Intent intent : intents) {
+ extendClassificationWithIntent(intent, builder);
+ }
+
+ return builder.setVersionInfo(getVersionInfo()).build();
+ }
+
+ /** Extends the classification with the intent if it can be resolved. */
+ private void extendClassificationWithIntent(Intent intent, TextClassification.Builder builder) {
final PackageManager pm;
final ResolveInfo resolveInfo;
if (intent != null) {
@@ -367,30 +377,29 @@ final class TextClassifierImpl implements TextClassifier {
resolveInfo = null;
}
if (resolveInfo != null && resolveInfo.activityInfo != null) {
- builder.setIntent(intent)
- .setOnClickListener(TextClassification.createStartActivityOnClickListener(
- mContext, intent));
-
final String packageName = resolveInfo.activityInfo.packageName;
+ CharSequence label;
+ Drawable icon;
if ("android".equals(packageName)) {
// Requires the chooser to find an activity to handle the intent.
- builder.setLabel(IntentFactory.getLabel(mContext, type));
+ label = IntentFactory.getLabel(mContext, intent);
+ icon = null;
} else {
// A default activity will handle the intent.
intent.setComponent(new ComponentName(packageName, resolveInfo.activityInfo.name));
- Drawable icon = resolveInfo.activityInfo.loadIcon(pm);
+ icon = resolveInfo.activityInfo.loadIcon(pm);
if (icon == null) {
icon = resolveInfo.loadIcon(pm);
}
- builder.setIcon(icon);
- CharSequence label = resolveInfo.activityInfo.loadLabel(pm);
+ label = resolveInfo.activityInfo.loadLabel(pm);
if (label == null) {
label = resolveInfo.loadLabel(pm);
}
- builder.setLabel(label != null ? label.toString() : null);
}
+ builder.addAction(
+ intent, label != null ? label.toString() : null, icon,
+ TextClassification.createStartActivityOnClickListener(mContext, intent));
}
- return builder.setVersionInfo(getVersionInfo()).build();
}
private static int getHintFlags(CharSequence text, int start, int end) {
@@ -477,10 +486,11 @@ final class TextClassifierImpl implements TextClassifier {
if (results.length > 0) {
final String type = getHighestScoringType(results);
if (matches(type, linkMask)) {
- final Intent intent = IntentFactory.create(
+ // For links without disambiguation, we simply use the default intent.
+ final List<Intent> intents = IntentFactory.create(
context, type, text.substring(selectionStart, selectionEnd));
- if (hasActivityHandler(context, intent)) {
- final ClickableSpan span = createSpan(context, intent);
+ if (!intents.isEmpty() && hasActivityHandler(context, intents.get(0))) {
+ final ClickableSpan span = createSpan(context, intents.get(0));
spans.add(new SpanSpec(selectionStart, selectionEnd, span));
}
}
@@ -564,7 +574,7 @@ final class TextClassifierImpl implements TextClassifier {
};
}
- private static boolean hasActivityHandler(Context context, @Nullable Intent intent) {
+ private static boolean hasActivityHandler(Context context, Intent intent) {
if (intent == null) {
return false;
}
@@ -625,20 +635,32 @@ final class TextClassifierImpl implements TextClassifier {
private IntentFactory() {}
- @Nullable
- public static Intent create(Context context, String type, String text) {
+ @NonNull
+ public static List<Intent> create(Context context, String type, String text) {
+ final List<Intent> intents = new ArrayList<>();
type = type.trim().toLowerCase(Locale.ENGLISH);
text = text.trim();
switch (type) {
case TextClassifier.TYPE_EMAIL:
- return new Intent(Intent.ACTION_SENDTO)
- .setData(Uri.parse(String.format("mailto:%s", text)));
+ intents.add(new Intent(Intent.ACTION_SENDTO)
+ .setData(Uri.parse(String.format("mailto:%s", text))));
+ intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
+ .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+ .putExtra(ContactsContract.Intents.Insert.EMAIL, text));
+ break;
case TextClassifier.TYPE_PHONE:
- return new Intent(Intent.ACTION_DIAL)
- .setData(Uri.parse(String.format("tel:%s", text)));
+ intents.add(new Intent(Intent.ACTION_DIAL)
+ .setData(Uri.parse(String.format("tel:%s", text))));
+ intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
+ .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+ .putExtra(ContactsContract.Intents.Insert.PHONE, text));
+ intents.add(new Intent(Intent.ACTION_SENDTO)
+ .setData(Uri.parse(String.format("smsto:%s", text))));
+ break;
case TextClassifier.TYPE_ADDRESS:
- return new Intent(Intent.ACTION_VIEW)
- .setData(Uri.parse(String.format("geo:0,0?q=%s", text)));
+ intents.add(new Intent(Intent.ACTION_VIEW)
+ .setData(Uri.parse(String.format("geo:0,0?q=%s", text))));
+ break;
case TextClassifier.TYPE_URL:
final String httpPrefix = "http://";
final String httpsPrefix = "https://";
@@ -649,25 +671,47 @@ final class TextClassifierImpl implements TextClassifier {
} else {
text = httpPrefix + text;
}
- return new Intent(Intent.ACTION_VIEW, Uri.parse(text))
- .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
- default:
- return null;
+ intents.add(new Intent(Intent.ACTION_VIEW, Uri.parse(text))
+ .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()));
+ break;
}
+ return intents;
}
@Nullable
- public static String getLabel(Context context, String type) {
- type = type.trim().toLowerCase(Locale.ENGLISH);
- switch (type) {
- case TextClassifier.TYPE_EMAIL:
- return context.getString(com.android.internal.R.string.email);
- case TextClassifier.TYPE_PHONE:
+ public static String getLabel(Context context, @Nullable Intent intent) {
+ if (intent == null || intent.getAction() == null) {
+ return null;
+ }
+ switch (intent.getAction()) {
+ case Intent.ACTION_DIAL:
return context.getString(com.android.internal.R.string.dial);
- case TextClassifier.TYPE_ADDRESS:
- return context.getString(com.android.internal.R.string.map);
- case TextClassifier.TYPE_URL:
- return context.getString(com.android.internal.R.string.browse);
+ case Intent.ACTION_SENDTO:
+ switch (intent.getScheme()) {
+ case "mailto":
+ return context.getString(com.android.internal.R.string.email);
+ case "smsto":
+ return context.getString(com.android.internal.R.string.sms);
+ default:
+ return null;
+ }
+ case Intent.ACTION_INSERT_OR_EDIT:
+ switch (intent.getDataString()) {
+ case ContactsContract.Contacts.CONTENT_ITEM_TYPE:
+ return context.getString(com.android.internal.R.string.add_contact);
+ default:
+ return null;
+ }
+ case Intent.ACTION_VIEW:
+ switch (intent.getScheme()) {
+ case "geo":
+ return context.getString(com.android.internal.R.string.map);
+ case "http": // fall through
+ case "https":
+ return context.getString(com.android.internal.R.string.browse);
+ default:
+ return null;
+ }
default:
return null;
}
diff --git a/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
index 8d88ba60..83af19bb 100644
--- a/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -581,6 +581,7 @@ public final class SmartSelectionEventTracker {
case ActionType.SMART_SHARE: // fall through
case ActionType.DRAG: // fall through
case ActionType.ABANDON: // fall through
+ case ActionType.OTHER: // fall through
return true;
default:
return false;
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();
+ }
}
}