diff options
author | Jeff Davidson <jpd@google.com> | 2018-02-08 15:30:06 -0800 |
---|---|---|
committer | Jeff Davidson <jpd@google.com> | 2018-02-08 15:30:06 -0800 |
commit | a192cc2a132cb0ee8588e2df755563ec7008c179 (patch) | |
tree | 380e4db22df19c819bd37df34bf06e7568916a50 /android/view | |
parent | 98fe7819c6d14f4f464a5cac047f9e82dee5da58 (diff) | |
download | android-28-a192cc2a132cb0ee8588e2df755563ec7008c179.tar.gz |
Update fullsdk to 4575844
/google/data/ro/projects/android/fetch_artifact \
--bid 4575844 \
--target sdk_phone_x86_64-sdk \
sdk-repo-linux-sources-4575844.zip
Test: TreeHugger
Change-Id: I81e0eb157b4ac3b38408d0ef86f9d6286471f87a
Diffstat (limited to 'android/view')
46 files changed, 3570 insertions, 1028 deletions
diff --git a/android/view/Choreographer.java b/android/view/Choreographer.java index ba6b6cf6..1caea577 100644 --- a/android/view/Choreographer.java +++ b/android/view/Choreographer.java @@ -235,6 +235,8 @@ public final class Choreographer { for (int i = 0; i <= CALLBACK_LAST; i++) { mCallbackQueues[i] = new CallbackQueue(); } + // b/68769804: For low FPS experiments. + setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1)); } private static float getRefreshRate() { @@ -371,6 +373,7 @@ public final class Choreographer { * @see #removeCallbacks * @hide */ + @TestApi public void postCallback(int callbackType, Runnable action, Object token) { postCallbackDelayed(callbackType, action, token, 0); } @@ -389,6 +392,7 @@ public final class Choreographer { * @see #removeCallback * @hide */ + @TestApi public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis) { if (action == null) { @@ -438,6 +442,7 @@ public final class Choreographer { * @see #postCallbackDelayed * @hide */ + @TestApi public void removeCallbacks(int callbackType, Runnable action, Object token) { if (callbackType < 0 || callbackType > CALLBACK_LAST) { throw new IllegalArgumentException("callbackType is invalid"); @@ -605,6 +610,7 @@ public final class Choreographer { void setFPSDivisor(int divisor) { if (divisor <= 0) divisor = 1; mFPSDivisor = divisor; + ThreadedRenderer.setFPSDivisor(divisor); } void doFrame(long frameTimeNanos, int frame) { diff --git a/android/view/DisplayCutout.java b/android/view/DisplayCutout.java index e448f14c..a61c8c1d 100644 --- a/android/view/DisplayCutout.java +++ b/android/view/DisplayCutout.java @@ -16,11 +16,15 @@ package android.view; +import static android.view.DisplayCutoutProto.BOUNDS; +import static android.view.DisplayCutoutProto.INSETS; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import android.content.res.Resources; +import android.graphics.Matrix; import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; @@ -28,7 +32,12 @@ import android.graphics.RectF; import android.graphics.Region; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; +import android.util.PathParser; +import android.util.proto.ProtoOutputStream; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import java.util.List; @@ -40,6 +49,9 @@ import java.util.List; */ public final class DisplayCutout { + private static final String TAG = "DisplayCutout"; + private static final String DP_MARKER = "@dp"; + private static final Rect ZERO_RECT = new Rect(); private static final Region EMPTY_REGION = new Region(); @@ -150,11 +162,21 @@ public final class DisplayCutout { @Override public String toString() { return "DisplayCutout{insets=" + mSafeInsets - + " bounds=" + mBounds + + " boundingRect=" + getBoundingRect() + "}"; } /** + * @hide + */ + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + mSafeInsets.writeToProto(proto, INSETS); + mBounds.getBounds().writeToProto(proto, BOUNDS); + proto.end(token); + } + + /** * Insets the reference frame of the cutout in the given directions. * * @return a copy of this instance which has been inset @@ -266,9 +288,7 @@ public final class DisplayCutout { * @hide */ public static DisplayCutout fromBoundingPolygon(List<Point> points) { - Region bounds = Region.obtain(); Path path = new Path(); - path.reset(); for (int i = 0; i < points.size(); i++) { Point point = points.get(i); @@ -279,18 +299,62 @@ public final class DisplayCutout { } } path.close(); + return fromBounds(path); + } + /** + * Creates an instance from a bounding {@link Path}. + * + * @hide + */ + public static DisplayCutout fromBounds(Path path) { RectF clipRect = new RectF(); path.computeBounds(clipRect, false /* unused */); Region clipRegion = Region.obtain(); clipRegion.set((int) clipRect.left, (int) clipRect.top, (int) clipRect.right, (int) clipRect.bottom); + Region bounds = new Region(); bounds.setPath(path, clipRegion); + clipRegion.recycle(); return new DisplayCutout(ZERO_RECT, bounds); } /** + * Creates an instance according to @android:string/config_mainBuiltInDisplayCutout. + * + * @hide + */ + public static DisplayCutout fromResources(Resources res, int displayWidth) { + String spec = res.getString(R.string.config_mainBuiltInDisplayCutout); + if (TextUtils.isEmpty(spec)) { + return null; + } + spec = spec.trim(); + final boolean inDp = spec.endsWith(DP_MARKER); + if (inDp) { + spec = spec.substring(0, spec.length() - DP_MARKER.length()); + } + + Path p; + try { + p = PathParser.createPathFromPathData(spec); + } catch (Throwable e) { + Log.wtf(TAG, "Could not inflate cutout: ", e); + return null; + } + + final Matrix m = new Matrix(); + if (inDp) { + final float dpToPx = res.getDisplayMetrics().density; + m.postScale(dpToPx, dpToPx); + } + m.postTranslate(displayWidth / 2f, 0); + p.transform(m); + return fromBounds(p); + } + + /** * Helper class for passing {@link DisplayCutout} through binder. * * Needed, because {@code readFromParcel} cannot be used with immutable classes. @@ -316,12 +380,23 @@ public final class DisplayCutout { @Override public void writeToParcel(Parcel out, int flags) { - if (mInner == NO_CUTOUT) { + writeCutoutToParcel(mInner, out, flags); + } + + /** + * Writes a DisplayCutout to a {@link Parcel}. + * + * @see #readCutoutFromParcel(Parcel) + */ + public static void writeCutoutToParcel(DisplayCutout cutout, Parcel out, int flags) { + if (cutout == null) { + out.writeInt(-1); + } else if (cutout == NO_CUTOUT) { out.writeInt(0); } else { out.writeInt(1); - out.writeTypedObject(mInner.mSafeInsets, flags); - out.writeTypedObject(mInner.mBounds, flags); + out.writeTypedObject(cutout.mSafeInsets, flags); + out.writeTypedObject(cutout.mBounds, flags); } } @@ -332,13 +407,13 @@ public final class DisplayCutout { * Needed for AIDL out parameters. */ public void readFromParcel(Parcel in) { - mInner = readCutout(in); + mInner = readCutoutFromParcel(in); } public static final Creator<ParcelableWrapper> CREATOR = new Creator<ParcelableWrapper>() { @Override public ParcelableWrapper createFromParcel(Parcel in) { - return new ParcelableWrapper(readCutout(in)); + return new ParcelableWrapper(readCutoutFromParcel(in)); } @Override @@ -347,8 +422,17 @@ public final class DisplayCutout { } }; - private static DisplayCutout readCutout(Parcel in) { - if (in.readInt() == 0) { + /** + * Reads a DisplayCutout from a {@link Parcel}. + * + * @see #writeCutoutToParcel(DisplayCutout, Parcel, int) + */ + public static DisplayCutout readCutoutFromParcel(Parcel in) { + int variant = in.readInt(); + if (variant == -1) { + return null; + } + if (variant == 0) { return NO_CUTOUT; } diff --git a/android/view/DisplayInfo.java b/android/view/DisplayInfo.java index b813ddb6..37e9815c 100644 --- a/android/view/DisplayInfo.java +++ b/android/view/DisplayInfo.java @@ -149,6 +149,13 @@ public final class DisplayInfo implements Parcelable { public int overscanBottom; /** + * The {@link DisplayCutout} if present, otherwise {@code null}. + * + * @hide + */ + public DisplayCutout displayCutout; + + /** * The rotation of the display relative to its natural orientation. * May be one of {@link android.view.Surface#ROTATION_0}, * {@link android.view.Surface#ROTATION_90}, {@link android.view.Surface#ROTATION_180}, @@ -301,6 +308,7 @@ public final class DisplayInfo implements Parcelable { && overscanTop == other.overscanTop && overscanRight == other.overscanRight && overscanBottom == other.overscanBottom + && Objects.equal(displayCutout, other.displayCutout) && rotation == other.rotation && modeId == other.modeId && defaultModeId == other.defaultModeId @@ -342,6 +350,7 @@ public final class DisplayInfo implements Parcelable { overscanTop = other.overscanTop; overscanRight = other.overscanRight; overscanBottom = other.overscanBottom; + displayCutout = other.displayCutout; rotation = other.rotation; modeId = other.modeId; defaultModeId = other.defaultModeId; @@ -379,6 +388,7 @@ public final class DisplayInfo implements Parcelable { overscanTop = source.readInt(); overscanRight = source.readInt(); overscanBottom = source.readInt(); + displayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(source); rotation = source.readInt(); modeId = source.readInt(); defaultModeId = source.readInt(); @@ -425,6 +435,7 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(overscanTop); dest.writeInt(overscanRight); dest.writeInt(overscanBottom); + DisplayCutout.ParcelableWrapper.writeCutoutToParcel(displayCutout, dest, flags); dest.writeInt(rotation); dest.writeInt(modeId); dest.writeInt(defaultModeId); diff --git a/android/view/IWindowManagerImpl.java b/android/view/IWindowManagerImpl.java index 4d804c55..93e6c0b9 100644 --- a/android/view/IWindowManagerImpl.java +++ b/android/view/IWindowManagerImpl.java @@ -29,6 +29,7 @@ import android.os.IRemoteCallback; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.DisplayMetrics; +import android.view.RemoteAnimationAdapter; import com.android.internal.os.IResultReceiver; import com.android.internal.policy.IKeyguardDismissCallback; @@ -76,6 +77,11 @@ public class IWindowManagerImpl implements IWindowManager { // ---- unused implementation of IWindowManager ---- @Override + public int getNavBarPosition() throws RemoteException { + return 0; + } + + @Override public void addWindowToken(IBinder arg0, int arg1, int arg2) throws RemoteException { // TODO Auto-generated method stub @@ -237,6 +243,10 @@ public class IWindowManagerImpl implements IWindowManager { } @Override + public void overridePendingAppTransitionRemote(RemoteAnimationAdapter adapter) { + } + + @Override public void prepareAppTransition(int arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub @@ -416,7 +426,8 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void dismissKeyguard(IKeyguardDismissCallback callback) throws RemoteException { + public void dismissKeyguard(IKeyguardDismissCallback callback, CharSequence message) + throws RemoteException { } @Override @@ -537,4 +548,17 @@ public class IWindowManagerImpl implements IWindowManager { public void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener, int displayId) throws RemoteException { } + + @Override + public void startWindowTrace() throws RemoteException { + } + + @Override + public void stopWindowTrace() throws RemoteException { + } + + @Override + public boolean isWindowTraceEnabled() throws RemoteException { + return false; + } } diff --git a/android/view/KeyEvent.java b/android/view/KeyEvent.java index a2147b71..a5974056 100644 --- a/android/view/KeyEvent.java +++ b/android/view/KeyEvent.java @@ -804,11 +804,12 @@ public class KeyEvent extends InputEvent implements Parcelable { public static final int KEYCODE_SYSTEM_NAVIGATION_LEFT = 282; /** Key code constant: Consumed by the system for navigation right */ public static final int KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283; - /** Key code constant: Show all apps - * @hide */ + /** Key code constant: Show all apps */ public static final int KEYCODE_ALL_APPS = 284; + /** Key code constant: Refresh key. */ + public static final int KEYCODE_REFRESH = 285; - private static final int LAST_KEYCODE = KEYCODE_ALL_APPS; + private static final int LAST_KEYCODE = KEYCODE_REFRESH; // NOTE: If you add a new keycode here you must also add it to: // isSystem() diff --git a/android/view/MotionEvent.java b/android/view/MotionEvent.java index 04fa637b..1d7c1ded 100644 --- a/android/view/MotionEvent.java +++ b/android/view/MotionEvent.java @@ -26,6 +26,8 @@ import android.util.SparseArray; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; +import java.util.Objects; + /** * Object used to report movement (mouse, pen, finger, trackball) events. * Motion events may hold either absolute or relative movements and other data, @@ -173,6 +175,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { private static final long NS_PER_MS = 1000000; private static final String LABEL_PREFIX = "AXIS_"; + private static final boolean DEBUG_CONCISE_TOSTRING = false; + /** * An invalid pointer id. * @@ -3236,31 +3240,42 @@ public final class MotionEvent extends InputEvent implements Parcelable { public String toString() { StringBuilder msg = new StringBuilder(); msg.append("MotionEvent { action=").append(actionToString(getAction())); - msg.append(", actionButton=").append(buttonStateToString(getActionButton())); + appendUnless("0", msg, ", actionButton=", buttonStateToString(getActionButton())); final int pointerCount = getPointerCount(); for (int i = 0; i < pointerCount; i++) { - msg.append(", id[").append(i).append("]=").append(getPointerId(i)); - msg.append(", x[").append(i).append("]=").append(getX(i)); - msg.append(", y[").append(i).append("]=").append(getY(i)); - msg.append(", toolType[").append(i).append("]=").append( - toolTypeToString(getToolType(i))); + appendUnless(i, msg, ", id[" + i + "]=", getPointerId(i)); + float x = getX(i); + float y = getY(i); + if (!DEBUG_CONCISE_TOSTRING || x != 0f || y != 0f) { + msg.append(", x[").append(i).append("]=").append(x); + msg.append(", y[").append(i).append("]=").append(y); + } + appendUnless(TOOL_TYPE_SYMBOLIC_NAMES.get(TOOL_TYPE_FINGER), + msg, ", toolType[" + i + "]=", toolTypeToString(getToolType(i))); } - msg.append(", buttonState=").append(MotionEvent.buttonStateToString(getButtonState())); - msg.append(", metaState=").append(KeyEvent.metaStateToString(getMetaState())); - msg.append(", flags=0x").append(Integer.toHexString(getFlags())); - msg.append(", edgeFlags=0x").append(Integer.toHexString(getEdgeFlags())); - msg.append(", pointerCount=").append(pointerCount); - msg.append(", historySize=").append(getHistorySize()); + appendUnless("0", msg, ", buttonState=", MotionEvent.buttonStateToString(getButtonState())); + appendUnless("0", msg, ", metaState=", KeyEvent.metaStateToString(getMetaState())); + appendUnless("0", msg, ", flags=0x", Integer.toHexString(getFlags())); + appendUnless("0", msg, ", edgeFlags=0x", Integer.toHexString(getEdgeFlags())); + appendUnless(1, msg, ", pointerCount=", pointerCount); + appendUnless(0, msg, ", historySize=", getHistorySize()); msg.append(", eventTime=").append(getEventTime()); - msg.append(", downTime=").append(getDownTime()); - msg.append(", deviceId=").append(getDeviceId()); - msg.append(", source=0x").append(Integer.toHexString(getSource())); + if (!DEBUG_CONCISE_TOSTRING) { + msg.append(", downTime=").append(getDownTime()); + msg.append(", deviceId=").append(getDeviceId()); + msg.append(", source=0x").append(Integer.toHexString(getSource())); + } msg.append(" }"); return msg.toString(); } + private static <T> void appendUnless(T defValue, StringBuilder sb, String key, T value) { + if (DEBUG_CONCISE_TOSTRING && Objects.equals(defValue, value)) return; + sb.append(key).append(value); + } + /** * Returns a string that represents the symbolic name of the specified unmasked action * such as "ACTION_DOWN", "ACTION_POINTER_DOWN(3)" or an equivalent numeric constant diff --git a/android/view/NotificationHeaderView.java b/android/view/NotificationHeaderView.java index ab0b3eec..fbba8abf 100644 --- a/android/view/NotificationHeaderView.java +++ b/android/view/NotificationHeaderView.java @@ -47,6 +47,7 @@ public class NotificationHeaderView extends ViewGroup { private final int mGravity; private View mAppName; private View mHeaderText; + private View mSecondaryHeaderText; private OnClickListener mExpandClickListener; private HeaderTouchListener mTouchListener = new HeaderTouchListener(); private ImageView mExpandButton; @@ -58,7 +59,6 @@ public class NotificationHeaderView extends ViewGroup { private boolean mShowExpandButtonAtEnd; private boolean mShowWorkBadgeAtEnd; private Drawable mBackground; - private int mHeaderBackgroundHeight; private boolean mEntireHeaderClickable; private boolean mExpandOnlyOnButton; private boolean mAcceptAllTouches; @@ -68,7 +68,7 @@ public class NotificationHeaderView extends ViewGroup { @Override public void getOutline(View view, Outline outline) { if (mBackground != null) { - outline.setRect(0, 0, getWidth(), mHeaderBackgroundHeight); + outline.setRect(0, 0, getWidth(), getHeight()); outline.setAlpha(1f); } } @@ -91,8 +91,6 @@ public class NotificationHeaderView extends ViewGroup { Resources res = getResources(); mChildMinWidth = res.getDimensionPixelSize(R.dimen.notification_header_shrink_min_width); mContentEndMargin = res.getDimensionPixelSize(R.dimen.notification_content_margin_end); - mHeaderBackgroundHeight = res.getDimensionPixelSize( - R.dimen.notification_header_background_height); mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand); int[] attrIds = { android.R.attr.gravity }; @@ -106,6 +104,7 @@ public class NotificationHeaderView extends ViewGroup { super.onFinishInflate(); mAppName = findViewById(com.android.internal.R.id.app_name_text); mHeaderText = findViewById(com.android.internal.R.id.header_text); + mSecondaryHeaderText = findViewById(com.android.internal.R.id.header_text_secondary); mExpandButton = findViewById(com.android.internal.R.id.expand_button); mIcon = findViewById(com.android.internal.R.id.icon); mProfileBadge = findViewById(com.android.internal.R.id.profile_badge); @@ -137,26 +136,33 @@ public class NotificationHeaderView extends ViewGroup { if (totalWidth > givenWidth) { int overFlow = totalWidth - givenWidth; // We are overflowing, lets shrink the app name first - final int appWidth = mAppName.getMeasuredWidth(); - if (overFlow > 0 && mAppName.getVisibility() != GONE && appWidth > mChildMinWidth) { - int newSize = appWidth - Math.min(appWidth - mChildMinWidth, overFlow); - int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST); - mAppName.measure(childWidthSpec, wrapContentHeightSpec); - overFlow -= appWidth - newSize; - } - // still overflowing, finaly we shrink the header text - if (overFlow > 0 && mHeaderText.getVisibility() != GONE) { - // we're still too big - final int textWidth = mHeaderText.getMeasuredWidth(); - int newSize = Math.max(0, textWidth - overFlow); - int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST); - mHeaderText.measure(childWidthSpec, wrapContentHeightSpec); - } + overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mAppName, + mChildMinWidth); + + // still overflowing, we shrink the header text + overFlow = shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mHeaderText, 0); + + // still overflowing, finally we shrink the secondary header text + shrinkViewForOverflow(wrapContentHeightSpec, overFlow, mSecondaryHeaderText, + 0); } mTotalWidth = Math.min(totalWidth, givenWidth); setMeasuredDimension(givenWidth, givenHeight); } + private int shrinkViewForOverflow(int heightSpec, int overFlow, View targetView, + int minimumWidth) { + final int oldWidth = targetView.getMeasuredWidth(); + if (overFlow > 0 && targetView.getVisibility() != GONE && oldWidth > minimumWidth) { + // we're still too big + int newSize = Math.max(minimumWidth, oldWidth - overFlow); + int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST); + targetView.measure(childWidthSpec, heightSpec); + overFlow -= oldWidth - newSize; + } + return overFlow; + } + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int left = getPaddingStart(); @@ -228,7 +234,7 @@ public class NotificationHeaderView extends ViewGroup { @Override protected void onDraw(Canvas canvas) { if (mBackground != null) { - mBackground.setBounds(0, 0, getWidth(), mHeaderBackgroundHeight); + mBackground.setBounds(0, 0, getWidth(), getHeight()); mBackground.draw(canvas); } } diff --git a/android/view/PointerIcon.java b/android/view/PointerIcon.java index 998fd019..3fd46963 100644 --- a/android/view/PointerIcon.java +++ b/android/view/PointerIcon.java @@ -461,8 +461,10 @@ public final class PointerIcon implements Parcelable { + "refer to a bitmap drawable."); } + final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); + validateHotSpot(bitmap, hotSpotX, hotSpotY); // Set the properties now that we have successfully loaded the icon. - mBitmap = ((BitmapDrawable)drawable).getBitmap(); + mBitmap = bitmap; mHotSpotX = hotSpotX; mHotSpotY = hotSpotY; } diff --git a/android/view/RecordingCanvas.java b/android/view/RecordingCanvas.java index 5088cdc9..fbb862be 100644 --- a/android/view/RecordingCanvas.java +++ b/android/view/RecordingCanvas.java @@ -34,6 +34,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.TemporaryBuffer; import android.text.GraphicsOperations; +import android.text.MeasuredText; import android.text.SpannableString; import android.text.SpannedString; import android.text.TextUtils; @@ -473,7 +474,8 @@ public class RecordingCanvas extends Canvas { } nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount, - x, y, isRtl, paint.getNativeInstance()); + x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */, + 0 /* measured text offset */); } @Override @@ -503,8 +505,20 @@ public class RecordingCanvas extends Canvas { int len = end - start; char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); + long measuredTextPtr = 0; + int measuredTextOffset = 0; + if (text instanceof MeasuredText) { + MeasuredText mt = (MeasuredText) text; + int paraIndex = mt.findParaIndex(start); + if (end <= mt.getParagraphEnd(paraIndex)) { + // Only support if the target is in the same paragraph. + measuredTextPtr = mt.getMeasuredParagraph(paraIndex).getNativePtr(); + measuredTextOffset = start - mt.getParagraphStart(paraIndex); + } + } nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len, - 0, contextLen, x, y, isRtl, paint.getNativeInstance()); + 0, contextLen, x, y, isRtl, paint.getNativeInstance(), + measuredTextPtr, measuredTextOffset); TemporaryBuffer.recycle(buf); } } @@ -626,7 +640,8 @@ public class RecordingCanvas extends Canvas { @FastNative private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count, - int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint); + int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint, + long nativeMeasuredText, int measuredTextOffset); @FastNative private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count, diff --git a/android/view/RemoteAnimationAdapter.java b/android/view/RemoteAnimationAdapter.java new file mode 100644 index 00000000..d597e597 --- /dev/null +++ b/android/view/RemoteAnimationAdapter.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.view; + +import android.app.ActivityOptions; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Object that describes how to run a remote animation. + * <p> + * A remote animation lets another app control the entire app transition. It does so by + * <ul> + * <li>using {@link ActivityOptions#makeRemoteAnimation}</li> + * <li>using {@link IWindowManager#overridePendingAppTransitionRemote}</li> + * </ul> + * to register a {@link RemoteAnimationAdapter} that describes how the animation should be run: + * Along some meta-data, this object contains a callback that gets invoked from window manager when + * the transition is ready to be started. + * <p> + * Window manager supplies a list of {@link RemoteAnimationTarget}s into the callback. Each target + * contains information about the activity that is animating as well as + * {@link RemoteAnimationTarget#leash}. The controlling app can modify the leash like any other + * {@link SurfaceControl}, including the possibility to synchronize updating the leash's surface + * properties with a frame to be drawn using + * {@link SurfaceControl.Transaction#deferTransactionUntil}. + * <p> + * When the animation is done, the controlling app can invoke + * {@link IRemoteAnimationFinishedCallback} that gets supplied into + * {@link IRemoteAnimationRunner#onStartAnimation} + * + * @hide + */ +public class RemoteAnimationAdapter implements Parcelable { + + private final IRemoteAnimationRunner mRunner; + private final long mDuration; + private final long mStatusBarTransitionDelay; + + /** + * @param runner The interface that gets notified when we actually need to start the animation. + * @param duration The duration of the animation. + * @param statusBarTransitionDelay The desired delay for all visual animations in the + * status bar caused by this app animation in millis. + */ + public RemoteAnimationAdapter(IRemoteAnimationRunner runner, long duration, + long statusBarTransitionDelay) { + mRunner = runner; + mDuration = duration; + mStatusBarTransitionDelay = statusBarTransitionDelay; + } + + public RemoteAnimationAdapter(Parcel in) { + mRunner = IRemoteAnimationRunner.Stub.asInterface(in.readStrongBinder()); + mDuration = in.readLong(); + mStatusBarTransitionDelay = in.readLong(); + } + + public IRemoteAnimationRunner getRunner() { + return mRunner; + } + + public long getDuration() { + return mDuration; + } + + public long getStatusBarTransitionDelay() { + return mStatusBarTransitionDelay; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongInterface(mRunner); + dest.writeLong(mDuration); + dest.writeLong(mStatusBarTransitionDelay); + } + + public static final Creator<RemoteAnimationAdapter> CREATOR + = new Creator<RemoteAnimationAdapter>() { + public RemoteAnimationAdapter createFromParcel(Parcel in) { + return new RemoteAnimationAdapter(in); + } + + public RemoteAnimationAdapter[] newArray(int size) { + return new RemoteAnimationAdapter[size]; + } + }; +} diff --git a/android/view/RemoteAnimationDefinition.java b/android/view/RemoteAnimationDefinition.java new file mode 100644 index 00000000..381f6926 --- /dev/null +++ b/android/view/RemoteAnimationDefinition.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.view; + +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArrayMap; +import android.util.SparseArray; +import android.view.WindowManager.TransitionType; + +/** + * Defines which animation types should be overridden by which remote animation. + * + * @hide + */ +public class RemoteAnimationDefinition implements Parcelable { + + private final SparseArray<RemoteAnimationAdapter> mTransitionAnimationMap; + + public RemoteAnimationDefinition() { + mTransitionAnimationMap = new SparseArray<>(); + } + + /** + * Registers a remote animation for a specific transition. + * + * @param transition The transition type. Must be one of WindowManager.TRANSIT_* values. + * @param adapter The adapter that described how to run the remote animation. + */ + public void addRemoteAnimation(@TransitionType int transition, RemoteAnimationAdapter adapter) { + mTransitionAnimationMap.put(transition, adapter); + } + + /** + * Checks whether a remote animation for specific transition is defined. + * + * @param transition The transition type. Must be one of WindowManager.TRANSIT_* values. + * @return Whether this definition has defined a remote animation for the specified transition. + */ + public boolean hasTransition(@TransitionType int transition) { + return mTransitionAnimationMap.get(transition) != null; + } + + /** + * Retrieves the remote animation for a specific transition. + * + * @param transition The transition type. Must be one of WindowManager.TRANSIT_* values. + * @return The remote animation adapter for the specified transition. + */ + public @Nullable RemoteAnimationAdapter getAdapter(@TransitionType int transition) { + return mTransitionAnimationMap.get(transition); + } + + public RemoteAnimationDefinition(Parcel in) { + mTransitionAnimationMap = in.readSparseArray(null /* loader */); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeSparseArray((SparseArray) mTransitionAnimationMap); + } + + public static final Creator<RemoteAnimationDefinition> CREATOR = + new Creator<RemoteAnimationDefinition>() { + public RemoteAnimationDefinition createFromParcel(Parcel in) { + return new RemoteAnimationDefinition(in); + } + + public RemoteAnimationDefinition[] newArray(int size) { + return new RemoteAnimationDefinition[size]; + } + }; +} diff --git a/android/view/RemoteAnimationTarget.java b/android/view/RemoteAnimationTarget.java new file mode 100644 index 00000000..c28c3894 --- /dev/null +++ b/android/view/RemoteAnimationTarget.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.view; + +import android.annotation.IntDef; +import android.app.WindowConfiguration; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Describes an activity to be animated as part of a remote animation. + * + * @hide + */ +public class RemoteAnimationTarget implements Parcelable { + + /** + * The app is in the set of opening apps of this transition. + */ + public static final int MODE_OPENING = 0; + + /** + * The app is in the set of closing apps of this transition. + */ + public static final int MODE_CLOSING = 1; + + @IntDef(prefix = { "MODE_" }, value = { + MODE_OPENING, + MODE_CLOSING + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Mode {} + + /** + * The {@link Mode} to describe whether this app is opening or closing. + */ + public final @Mode int mode; + + /** + * The id of the task this app belongs to. + */ + public final int taskId; + + /** + * The {@link SurfaceControl} object to actually control the transform of the app. + */ + public final SurfaceControl leash; + + /** + * Whether the app is translucent and may reveal apps behind. + */ + public final boolean isTranslucent; + + /** + * The clip rect window manager applies when clipping the app's main surface in screen space + * coordinates. This is just a hint to the animation runner: If running a clip-rect animation, + * anything that extends beyond these bounds will not have any effect. This implies that any + * clip-rect animation should likely stop at these bounds. + */ + public final Rect clipRect; + + /** + * The index of the element in the tree in prefix order. This should be used for z-layering + * to preserve original z-layer order in the hierarchy tree assuming no "boosting" needs to + * happen. + */ + public final int prefixOrderIndex; + + /** + * The source position of the app, in screen spaces coordinates. If the position of the leash + * is modified from the controlling app, any animation transform needs to be offset by this + * amount. + */ + public final Point position; + + /** + * The bounds of the source container the app lives in, in screen space coordinates. If the crop + * of the leash is modified from the controlling app, it needs to take the source container + * bounds into account when calculating the crop. + */ + public final Rect sourceContainerBounds; + + /** + * The window configuration for the target. + */ + public final WindowConfiguration windowConfiguration; + + public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent, + Rect clipRect, int prefixOrderIndex, Point position, Rect sourceContainerBounds, + WindowConfiguration windowConfig) { + this.mode = mode; + this.taskId = taskId; + this.leash = leash; + this.isTranslucent = isTranslucent; + this.clipRect = new Rect(clipRect); + this.prefixOrderIndex = prefixOrderIndex; + this.position = new Point(position); + this.sourceContainerBounds = new Rect(sourceContainerBounds); + this.windowConfiguration = windowConfig; + } + + public RemoteAnimationTarget(Parcel in) { + taskId = in.readInt(); + mode = in.readInt(); + leash = in.readParcelable(null); + isTranslucent = in.readBoolean(); + clipRect = in.readParcelable(null); + prefixOrderIndex = in.readInt(); + position = in.readParcelable(null); + sourceContainerBounds = in.readParcelable(null); + windowConfiguration = in.readParcelable(null); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(taskId); + dest.writeInt(mode); + dest.writeParcelable(leash, 0 /* flags */); + dest.writeBoolean(isTranslucent); + dest.writeParcelable(clipRect, 0 /* flags */); + dest.writeInt(prefixOrderIndex); + dest.writeParcelable(position, 0 /* flags */); + dest.writeParcelable(sourceContainerBounds, 0 /* flags */); + dest.writeParcelable(windowConfiguration, 0 /* flags */); + } + + public static final Creator<RemoteAnimationTarget> CREATOR + = new Creator<RemoteAnimationTarget>() { + public RemoteAnimationTarget createFromParcel(Parcel in) { + return new RemoteAnimationTarget(in); + } + + public RemoteAnimationTarget[] newArray(int size) { + return new RemoteAnimationTarget[size]; + } + }; +} diff --git a/android/view/Surface.java b/android/view/Surface.java index a417a4a0..8830c90a 100644 --- a/android/view/Surface.java +++ b/android/view/Surface.java @@ -182,6 +182,11 @@ public class Surface implements Parcelable { * SurfaceTexture}, which can attach them to an OpenGL ES texture via {@link * SurfaceTexture#updateTexImage}. * + * Please note that holding onto the Surface created here is not enough to + * keep the provided SurfaceTexture from being reclaimed. In that sense, + * the Surface will act like a + * {@link java.lang.ref.WeakReference weak reference} to the SurfaceTexture. + * * @param surfaceTexture The {@link SurfaceTexture} that is updated by this * Surface. * @throws OutOfResourcesException if the surface could not be created. @@ -278,6 +283,7 @@ public class Surface implements Parcelable { */ public long getNextFrameNumber() { synchronized (mLock) { + checkNotReleasedLocked(); return nativeGetNextFrameNumber(mNativeObject); } } diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java index 268e460d..bd7f8e54 100644 --- a/android/view/SurfaceControl.java +++ b/android/view/SurfaceControl.java @@ -16,20 +16,22 @@ package android.view; -import static android.view.Surface.ROTATION_270; -import static android.view.Surface.ROTATION_90; import static android.graphics.Matrix.MSCALE_X; import static android.graphics.Matrix.MSCALE_Y; import static android.graphics.Matrix.MSKEW_X; import static android.graphics.Matrix.MSKEW_Y; import static android.graphics.Matrix.MTRANS_X; import static android.graphics.Matrix.MTRANS_Y; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; +import static android.view.SurfaceControlProto.HASH_CODE; +import static android.view.SurfaceControlProto.NAME; import android.annotation.Size; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; -import android.graphics.PixelFormat; import android.graphics.Matrix; +import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; @@ -40,11 +42,13 @@ import android.os.Process; import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; +import android.util.proto.ProtoOutputStream; import android.view.Surface.OutOfResourcesException; import com.android.internal.annotations.GuardedBy; import dalvik.system.CloseGuard; + import libcore.util.NativeAllocationRegistry; import java.io.Closeable; @@ -628,6 +632,21 @@ public class SurfaceControl implements Parcelable { nativeWriteToParcel(mNativeObject, dest); } + /** + * Write to a protocol buffer output stream. Protocol buffer message definition is at {@link + * android.view.SurfaceControlProto}. + * + * @param proto Stream to write the SurfaceControl object to. + * @param fieldId Field Id of the SurfaceControl as defined in the parent message. + * @hide + */ + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(HASH_CODE, System.identityHashCode(this)); + proto.write(NAME, mName); + proto.end(token); + } + public static final Creator<SurfaceControl> CREATOR = new Creator<SurfaceControl>() { public SurfaceControl createFromParcel(Parcel in) { diff --git a/android/view/ThreadedRenderer.java b/android/view/ThreadedRenderer.java index 6a8f8b12..8b730f28 100644 --- a/android/view/ThreadedRenderer.java +++ b/android/view/ThreadedRenderer.java @@ -969,8 +969,6 @@ public final class ThreadedRenderer { mInitialized = true; mAppContext = context.getApplicationContext(); - // b/68769804: For low FPS experiments. - setFPSDivisor(SystemProperties.getInt(DEBUG_FPS_DIVISOR, 1)); initSched(renderProxy); initGraphicsStats(); } @@ -1025,9 +1023,7 @@ public final class ThreadedRenderer { /** b/68769804: For low FPS experiments. */ public static void setFPSDivisor(int divisor) { - if (divisor <= 0) divisor = 1; - Choreographer.getInstance().setFPSDivisor(divisor); - nHackySetRTAnimationsEnabled(divisor == 1); + nHackySetRTAnimationsEnabled(divisor <= 1); } /** Not actually public - internal use only. This doc to make lint happy */ diff --git a/android/view/View.java b/android/view/View.java index cc63a62c..3d6a6fee 100644 --- a/android/view/View.java +++ b/android/view/View.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; + import static java.lang.Math.max; import android.animation.AnimatorInflater; @@ -3226,6 +3228,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static final int PFLAG3_SCREEN_READER_FOCUSABLE = 0x10000000; + /** + * The last aggregated visibility. Used to detect when it truly changes. + */ + private static final int PFLAG3_AGGREGATED_VISIBLE = 0x20000000; + /* End of masks for mPrivateFlags3 */ /** @@ -3387,6 +3394,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * decorations when they are shown. You can perform layout of your inner * UI elements to account for non-fullscreen system UI through the * {@link #fitSystemWindows(Rect)} method. + * + * <p>Note: on displays that have a {@link DisplayCutout}, the window may still be placed + * differently than if {@link #SYSTEM_UI_FLAG_FULLSCREEN} was set, if the + * window's {@link WindowManager.LayoutParams#layoutInDisplayCutoutMode + * layoutInDisplayCutoutMode} is + * {@link WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT + * LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT}. To avoid this, use either of the other modes. + * + * @see WindowManager.LayoutParams#layoutInDisplayCutoutMode + * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT + * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER */ public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400; @@ -4045,6 +4064,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private CharSequence mContentDescription; /** + * If this view represents a distinct part of the window, it can have a title that labels the + * area. + */ + private CharSequence mAccessibilityPaneTitle; + + /** * Specifies the id of a view for which this view serves as a label for * accessibility purposes. */ @@ -4182,6 +4207,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static boolean sUseDefaultFocusHighlight; + /** + * True if zero-sized views can be focused. + */ + private static boolean sCanFocusZeroSized; + + /** + * Always assign focus if a focusable View is available. + */ + private static boolean sAlwaysAssignFocus; + private String mTransitionName; static class TintInfo { @@ -4407,7 +4442,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private CheckForLongPress mPendingCheckForLongPress; private CheckForTap mPendingCheckForTap = null; private PerformClick mPerformClick; - private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent; private UnsetPressedState mUnsetPressedState; @@ -4798,6 +4832,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sThrowOnInvalidFloatProperties = targetSdkVersion >= Build.VERSION_CODES.P; + sCanFocusZeroSized = targetSdkVersion < Build.VERSION_CODES.P; + + sAlwaysAssignFocus = targetSdkVersion < Build.VERSION_CODES.P; + sCompatibilityDone = true; } } @@ -5402,6 +5440,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setScreenReaderFocusable(a.getBoolean(attr, false)); } break; + case R.styleable.View_accessibilityPaneTitle: + if (a.peekValue(attr) != null) { + setAccessibilityPaneTitle(a.getString(attr)); + } + break; } } @@ -6983,8 +7026,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Called when this view wants to give up focus. If focus is cleared * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} is called. * <p> - * <strong>Note:</strong> When a View clears focus the framework is trying - * to give focus to the first focusable View from the top. Hence, if this + * <strong>Note:</strong> When not in touch-mode, the framework will try to give focus + * to the first focusable View from the top after focus is cleared. Hence, if this * View is the first from the top that can take focus, then all callbacks * related to clearing focus will be invoked after which the framework will * give focus to this view. @@ -6995,7 +7038,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, System.out.println(this + " clearFocus()"); } - clearFocusInternal(null, true, true); + final boolean refocus = sAlwaysAssignFocus || !isInTouchMode(); + clearFocusInternal(null, true, refocus); } /** @@ -7010,6 +7054,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, void clearFocusInternal(View focused, boolean propagate, boolean refocus) { if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { mPrivateFlags &= ~PFLAG_FOCUSED; + clearParentsWantFocus(); if (propagate && mParent != null) { mParent.clearChildFocus(this); @@ -7156,7 +7201,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (gainFocus) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } else { - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -7189,20 +7234,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, notifyEnterOrExitForAutoFillIfNeeded(gainFocus); } - private void notifyEnterOrExitForAutoFillIfNeeded(boolean enter) { - if (isAutofillable() && isAttachedToWindow()) { + /** @hide */ + public void notifyEnterOrExitForAutoFillIfNeeded(boolean enter) { + if (canNotifyAutofillEnterExitEvent()) { AutofillManager afm = getAutofillManager(); if (afm != null) { - if (enter && hasWindowFocus() && isFocused()) { + if (enter && isFocused()) { // We have not been laid out yet, hence cannot evaluate // whether this view is visible to the user, we will do // the evaluation once layout is complete. if (!isLaidOut()) { mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; } else if (isVisibleToUser()) { + // TODO This is a potential problem that View gets focus before it's visible + // to User. Ideally View should handle the event when isVisibleToUser() + // becomes true where it should issue notifyViewEntered(). afm.notifyViewEntered(this); } - } else if (!hasWindowFocus() || !isFocused()) { + } else if (!isFocused()) { afm.notifyViewExited(this); } } @@ -7210,6 +7259,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * If this view is a visually distinct portion of a window, for example the content view of + * a fragment that is replaced, it is considered a pane for accessibility purposes. In order + * for accessibility services to understand the views role, and to announce its title as + * appropriate, such views should have pane titles. + * + * @param accessibilityPaneTitle The pane's title. + * + * {@see AccessibilityNodeInfo#setPaneTitle(CharSequence)} + */ + public void setAccessibilityPaneTitle(CharSequence accessibilityPaneTitle) { + if (!TextUtils.equals(accessibilityPaneTitle, mAccessibilityPaneTitle)) { + mAccessibilityPaneTitle = accessibilityPaneTitle; + notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_TITLE); + } + } + + /** + * Get the title of the pane for purposes of accessibility. + * + * @return The current pane title. + * + * {@see #setAccessibilityPaneTitle}. + */ + public CharSequence getAccessibilityPaneTitle() { + return mAccessibilityPaneTitle; + } + + /** * Sends an accessibility event of the given type. If accessibility is * not enabled this method has no effect. The default implementation calls * {@link #onInitializeAccessibilityEvent(AccessibilityEvent)} first @@ -7308,7 +7385,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) { - if (!isShown()) { + // Panes disappearing are relevant even if though the view is no longer visible. + boolean isWindowStateChanged = + (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + boolean isWindowDisappearedEvent = isWindowStateChanged && ((event.getContentChangeTypes() + & AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED) != 0); + if (!isShown() && !isWindowDisappearedEvent) { return; } onInitializeAccessibilityEvent(event); @@ -7416,6 +7498,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { + if ((event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) + && !TextUtils.isEmpty(getAccessibilityPaneTitle())) { + event.getText().add(getAccessibilityPaneTitle()); + } } /** @@ -8237,6 +8323,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, && getAutofillViewId() > LAST_APP_AUTOFILL_ID; } + /** @hide */ + public boolean canNotifyAutofillEnterExitEvent() { + return isAutofillable() && isAttachedToWindow(); + } + private void populateVirtualStructure(ViewStructure structure, AccessibilityNodeProvider provider, AccessibilityNodeInfo info) { structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()), @@ -8459,6 +8550,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, info.setLongClickable(isLongClickable()); info.setContextClickable(isContextClickable()); info.setLiveRegion(getAccessibilityLiveRegion()); + if ((mTooltipInfo != null) && (mTooltipInfo.mTooltipText != null)) { + info.setTooltipText(mTooltipInfo.mTooltipText); + info.addAction((mTooltipInfo.mTooltipPopup == null) + ? AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TOOLTIP + : AccessibilityNodeInfo.AccessibilityAction.ACTION_HIDE_TOOLTIP); + } // TODO: These make sense only if we are in an AdapterView but all // views can be selected. Maybe from accessibility perspective @@ -8506,6 +8603,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, info.addAction(AccessibilityAction.ACTION_SHOW_ON_SCREEN); populateAccessibilityNodeInfoDrawingOrderInParent(info); + info.setPaneTitle(mAccessibilityPaneTitle); } /** @@ -8826,9 +8924,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final boolean nonEmptyDesc = contentDescription != null && contentDescription.length() > 0; if (nonEmptyDesc && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } else { - notifyViewAccessibilityStateChangedIfNeeded( + notifyAccessibilityStateChanged( AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION); } } @@ -8861,8 +8959,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } mAccessibilityTraversalBeforeId = beforeId; - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); } /** @@ -8905,8 +9002,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } mAccessibilityTraversalAfterId = afterId; - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); } /** @@ -8948,8 +9044,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, && mID == View.NO_ID) { mID = generateViewId(); } - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); } /** @@ -10046,6 +10141,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * @return {@code true} if laid-out and not about to do another layout. + */ + boolean isLayoutValid() { + return isLaidOut() && ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == 0); + } + + /** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. @@ -10442,8 +10544,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (pflags3 != mPrivateFlags3) { mPrivateFlags3 = pflags3; - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -10817,7 +10918,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (views == null) { return; } - if (!isFocusable() || !isEnabled()) { + if (!canTakeFocus()) { return; } if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE @@ -11031,8 +11132,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * descendants. * * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns - * false), or if it is focusable and it is not focusable in touch mode - * ({@link #isFocusableInTouchMode}) while the device is in touch mode. + * false), or if it can't be focused due to other conditions (not focusable in touch mode + * ({@link #isFocusableInTouchMode}) while the device is in touch mode, not visible, not + * enabled, or has no size). * * See also {@link #focusSearch(int)}, which is what you call to say that you * have focus, and you want your parent to look for the next one. @@ -11139,9 +11241,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) { // need to be focusable - if ((mViewFlags & FOCUSABLE) != FOCUSABLE - || (mViewFlags & VISIBILITY_MASK) != VISIBLE - || (mViewFlags & ENABLED_MASK) != ENABLED) { + if (!canTakeFocus()) { return false; } @@ -11156,10 +11256,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return false; } + if (!isLayoutValid()) { + mPrivateFlags |= PFLAG_WANTS_FOCUS; + } else { + clearParentsWantFocus(); + } + handleFocusGainInternal(direction, previouslyFocusedRect); return true; } + void clearParentsWantFocus() { + if (mParent instanceof View) { + ((View) mParent).mPrivateFlags &= ~PFLAG_WANTS_FOCUS; + ((View) mParent).clearParentsWantFocus(); + } + } + /** * Call this to try to give focus to a specific view or to one of its descendants. This is a * special variant of {@link #requestFocus() } that will allow views that are not focusable in @@ -11261,8 +11374,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK; mPrivateFlags2 |= (mode << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT) & PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK; - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -11319,10 +11431,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT) & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK; if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility()) { - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } else { - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); } } } @@ -11380,6 +11491,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link #getAccessibilityLiveRegion()} is not * {@link #ACCESSIBILITY_LIVE_REGION_NONE}. * </ul> + * <li>Has an accessibility pane title, see {@link #setAccessibilityPaneTitle}</li> * </ol> * * @return Whether the view is exposed for accessibility. @@ -11406,7 +11518,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return mode == IMPORTANT_FOR_ACCESSIBILITY_YES || isActionableForAccessibility() || hasListenersForAccessibility() || getAccessibilityNodeProvider() != null - || getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE; + || getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE + || (mAccessibilityPaneTitle != null); } /** @@ -11496,25 +11609,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ - public void notifyViewAccessibilityStateChangedIfNeeded(int changeType) { - if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) { - return; - } - // If this is a live region, we should send a subtree change event - // from this view immediately. Otherwise, we can let it propagate up. - if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) { - final AccessibilityEvent event = AccessibilityEvent.obtain(); - event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - event.setContentChangeTypes(changeType); - sendAccessibilityEventUnchecked(event); - } else if (mParent != null) { - try { - mParent.notifySubtreeAccessibilityStateChanged(this, this, changeType); - } catch (AbstractMethodError e) { - Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + - " does not fully implement ViewParent", e); - } - } + public void notifyAccessibilityStateChanged(int changeType) { + notifyAccessibilityStateChanged(this, changeType); } /** @@ -11528,22 +11624,42 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ - public void notifySubtreeAccessibilityStateChangedIfNeeded() { + public void notifyAccessibilitySubtreeChanged() { + if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) { + mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; + notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); + } + } + + void notifyAccessibilityStateChanged(View source, int changeType) { if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) { return; } - if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) { - mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; + // Changes to views with a pane title count as window state changes, as the pane title + // marks them as significant parts of the UI. + if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) { + final AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + event.setContentChangeTypes(changeType); + onPopulateAccessibilityEvent(event); if (mParent != null) { try { - mParent.notifySubtreeAccessibilityStateChanged( - this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); + mParent.requestSendAccessibilityEvent(this, event); } catch (AbstractMethodError e) { - Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + - " does not fully implement ViewParent", e); + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); } } } + + if (mParent != null) { + try { + mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } } /** @@ -11563,8 +11679,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Reset the flag indicating the accessibility state of the subtree rooted * at this view changed. + * + * @hide */ - void resetSubtreeAccessibilityStateChanged() { + public void resetSubtreeAccessibilityStateChanged() { mPrivateFlags2 &= ~PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; } @@ -11725,8 +11843,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || getAccessibilitySelectionEnd() != end) && (start == end)) { setAccessibilitySelection(start, end); - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); return true; } } break; @@ -11743,6 +11860,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return true; } } break; + case R.id.accessibilityActionShowTooltip: { + if ((mTooltipInfo != null) && (mTooltipInfo.mTooltipPopup != null)) { + // Tooltip already showing + return false; + } + return showLongClickTooltip(0, 0); + } + case R.id.accessibilityActionHideTooltip: { + if ((mTooltipInfo == null) || (mTooltipInfo.mTooltipPopup == null)) { + // No tooltip showing + return false; + } + hideTooltip(); + return true; + } } return false; } @@ -12347,8 +12479,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, imm.focusIn(this); } - notifyEnterOrExitForAutoFillIfNeeded(hasWindowFocus); - refreshDrawableState(); } @@ -12467,6 +12597,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @CallSuper public void onVisibilityAggregated(boolean isVisible) { + // Update our internal visibility tracking so we can detect changes + boolean oldVisible = (mPrivateFlags3 & PFLAG3_AGGREGATED_VISIBLE) != 0; + mPrivateFlags3 = isVisible ? (mPrivateFlags3 | PFLAG3_AGGREGATED_VISIBLE) + : (mPrivateFlags3 & ~PFLAG3_AGGREGATED_VISIBLE); if (isVisible && mAttachInfo != null) { initialAwakenScrollBars(); } @@ -12507,6 +12641,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } } + if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) { + if (isVisible != oldVisible) { + notifyAccessibilityStateChanged(isVisible + ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED + : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED); + } + } } /** @@ -13531,6 +13672,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mUnbufferedDispatchRequested = true; } + private boolean canTakeFocus() { + return ((mViewFlags & VISIBILITY_MASK) == VISIBLE) + && ((mViewFlags & FOCUSABLE) == FOCUSABLE) + && ((mViewFlags & ENABLED_MASK) == ENABLED) + && (sCanFocusZeroSized || !isLayoutValid() || (mBottom > mTop) && (mRight > mLeft)); + } + /** * Set flags controlling behavior of this view. * @@ -13550,6 +13698,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } int privateFlags = mPrivateFlags; + boolean shouldNotifyFocusableAvailable = false; // If focusable is auto, update the FOCUSABLE bit. int focusableChangedByAuto = 0; @@ -13588,7 +13737,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || focusableChangedByAuto == 0 || viewRootImpl == null || viewRootImpl.mThread == Thread.currentThread()) { - mParent.focusableViewAvailable(this); + shouldNotifyFocusableAvailable = true; } } } @@ -13611,10 +13760,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // about in case nothing has focus. even if this specific view // isn't focusable, it may contain something that is, so let // the root view try to give this focus if nothing else does. - if ((mParent != null) && ((mViewFlags & ENABLED_MASK) == ENABLED) - && (mBottom > mTop) && (mRight > mLeft)) { - mParent.focusableViewAvailable(this); - } + shouldNotifyFocusableAvailable = true; } } @@ -13623,17 +13769,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // a view becoming enabled should notify the parent as long as the view is also // visible and the parent wasn't already notified by becoming visible during this // setFlags invocation. - if ((mViewFlags & VISIBILITY_MASK) == VISIBLE - && ((changed & VISIBILITY_MASK) == 0)) { - if ((mParent != null) && (mViewFlags & ENABLED_MASK) == ENABLED) { - mParent.focusableViewAvailable(this); - } - } + shouldNotifyFocusableAvailable = true; } else { if (hasFocus()) clearFocus(); } } + if (shouldNotifyFocusableAvailable) { + if (mParent != null && canTakeFocus()) { + mParent.focusableViewAvailable(this); + } + } + /* Check if the GONE bit has changed */ if ((changed & GONE) != 0) { needGlobalAttributesUpdate(false); @@ -13713,7 +13860,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ((!(mParent instanceof ViewGroup)) || ((ViewGroup) mParent).isShown())) { dispatchVisibilityAggregated(newVisibility == VISIBLE); } - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -13759,14 +13906,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0 || (changed & CONTEXT_CLICKABLE) != 0) { if (oldIncludeForAccessibility != includeForAccessibility()) { - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } else { - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); } } else if ((changed & ENABLED_MASK) != 0) { - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); } } } @@ -13800,10 +13945,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param oldt Previous vertical scroll origin. */ protected void onScrollChanged(int l, int t, int oldl, int oldt) { - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt); + ViewRootImpl root = getViewRootImpl(); + if (root != null) { + root.getAccessibilityState() + .getSendViewScrolledAccessibilityEvent() + .post(this, /* dx */ l - oldl, /* dy */ t - oldt); } mBackgroundSizeChanged = true; @@ -14199,7 +14347,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -14243,7 +14391,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -14287,7 +14435,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -14324,7 +14472,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -14361,7 +14509,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -14564,7 +14712,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mTransformationInfo.mAlpha != alpha) { // Report visibility changes, which can affect children, to accessibility if ((alpha == 0) ^ (mTransformationInfo.mAlpha == 0)) { - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } mTransformationInfo.mAlpha = alpha; if (onSetAlpha((int) (alpha * 255))) { @@ -15066,7 +15214,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -15100,7 +15248,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -15270,7 +15418,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void invalidateOutline() { rebuildOutline(); - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); invalidateViewProperty(false, false); } @@ -15465,7 +15613,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidateParentIfNeeded(); } - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -15513,7 +15661,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidateParentIfNeeded(); } - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } } @@ -16391,18 +16539,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Post a callback to send a {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. - * This event is sent at most once every - * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}. - */ - private void postSendViewScrolledAccessibilityEventCallback(int dx, int dy) { - if (mSendViewScrolledAccessibilityEvent == null) { - mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent(); - } - mSendViewScrolledAccessibilityEvent.post(dx, dy); - } - - /** * Called by a parent to request that a child update its values for mScrollX * and mScrollY if necessary. This will typically be done if the child is * animating a scroll using a {@link android.widget.Scroller Scroller} @@ -17657,7 +17793,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, removeUnsetPressCallback(); removeLongPressCallback(); removePerformClickCallback(); - cancel(mSendViewScrolledAccessibilityEvent); + if (mAttachInfo != null + && mAttachInfo.mViewRootImpl.mAccessibilityState != null + && mAttachInfo.mViewRootImpl.mAccessibilityState.isScrollEventSenderInitialized()) { + mAttachInfo.mViewRootImpl.mAccessibilityState + .getSendViewScrolledAccessibilityEvent() + .cancelIfPendingFor(this); + } stopNestedScroll(); // Anything that started animating right before detach should already @@ -17761,6 +17903,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Return the window this view is currently attached to. Used in + * {@link android.app.ActivityView} to communicate with WM. + * @hide + */ + protected IWindow getWindow() { + return mAttachInfo != null ? mAttachInfo.mWindow : null; + } + + /** * Return the visibility value of the least visible component passed. */ int combineVisibility(int vis1, int vis2) { @@ -18936,7 +19087,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ - public Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) { + public Bitmap createSnapshot(ViewDebug.CanvasProvider canvasProvider, boolean skipChildren) { int width = mRight - mLeft; int height = mBottom - mTop; @@ -18945,71 +19096,48 @@ public class View implements Drawable.Callback, KeyEvent.Callback, width = (int) ((width * scale) + 0.5f); height = (int) ((height * scale) + 0.5f); - Bitmap bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(), - width > 0 ? width : 1, height > 0 ? height : 1, quality); - if (bitmap == null) { - throw new OutOfMemoryError(); - } - - Resources resources = getResources(); - if (resources != null) { - bitmap.setDensity(resources.getDisplayMetrics().densityDpi); - } + Canvas oldCanvas = null; + try { + Canvas canvas = canvasProvider.getCanvas(this, + width > 0 ? width : 1, height > 0 ? height : 1); - Canvas canvas; - if (attachInfo != null) { - canvas = attachInfo.mCanvas; - if (canvas == null) { - canvas = new Canvas(); + if (attachInfo != null) { + oldCanvas = attachInfo.mCanvas; + // Temporarily clobber the cached Canvas in case one of our children + // is also using a drawing cache. Without this, the children would + // steal the canvas by attaching their own bitmap to it and bad, bad + // things would happen (invisible views, corrupted drawings, etc.) + attachInfo.mCanvas = null; } - canvas.setBitmap(bitmap); - // Temporarily clobber the cached Canvas in case one of our children - // is also using a drawing cache. Without this, the children would - // steal the canvas by attaching their own bitmap to it and bad, bad - // things would happen (invisible views, corrupted drawings, etc.) - attachInfo.mCanvas = null; - } else { - // This case should hopefully never or seldom happen - canvas = new Canvas(bitmap); - } - boolean enabledHwBitmapsInSwMode = canvas.isHwBitmapsInSwModeEnabled(); - canvas.setHwBitmapsInSwModeEnabled(true); - if ((backgroundColor & 0xff000000) != 0) { - bitmap.eraseColor(backgroundColor); - } - computeScroll(); - final int restoreCount = canvas.save(); - canvas.scale(scale, scale); - canvas.translate(-mScrollX, -mScrollY); + computeScroll(); + final int restoreCount = canvas.save(); + canvas.scale(scale, scale); + canvas.translate(-mScrollX, -mScrollY); - // Temporarily remove the dirty mask - int flags = mPrivateFlags; - mPrivateFlags &= ~PFLAG_DIRTY_MASK; + // Temporarily remove the dirty mask + int flags = mPrivateFlags; + mPrivateFlags &= ~PFLAG_DIRTY_MASK; - // Fast path for layouts with no backgrounds - if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { - dispatchDraw(canvas); - drawAutofilledHighlight(canvas); - if (mOverlay != null && !mOverlay.isEmpty()) { - mOverlay.getOverlayView().draw(canvas); + // Fast path for layouts with no backgrounds + if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { + dispatchDraw(canvas); + drawAutofilledHighlight(canvas); + if (mOverlay != null && !mOverlay.isEmpty()) { + mOverlay.getOverlayView().draw(canvas); + } + } else { + draw(canvas); } - } else { - draw(canvas); - } - - mPrivateFlags = flags; - canvas.restoreToCount(restoreCount); - canvas.setBitmap(null); - canvas.setHwBitmapsInSwModeEnabled(enabledHwBitmapsInSwMode); - - if (attachInfo != null) { - // Restore the cached Canvas for our siblings - attachInfo.mCanvas = canvas; + mPrivateFlags = flags; + canvas.restoreToCount(restoreCount); + return canvasProvider.createBitmap(); + } finally { + if (oldCanvas != null) { + attachInfo.mCanvas = oldCanvas; + } } - - return bitmap; } /** @@ -20160,15 +20288,58 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + final boolean wasLayoutValid = isLayoutValid(); + mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; + if (!wasLayoutValid && isFocused()) { + mPrivateFlags &= ~PFLAG_WANTS_FOCUS; + if (canTakeFocus()) { + // We have a robust focus, so parents should no longer be wanting focus. + clearParentsWantFocus(); + } else if (!getViewRootImpl().isInLayout()) { + // This is a weird case. Most-likely the user, rather than ViewRootImpl, called + // layout. In this case, there's no guarantee that parent layouts will be evaluated + // and thus the safest action is to clear focus here. + clearFocusInternal(null, /* propagate */ true, /* refocus */ false); + clearParentsWantFocus(); + } else if (!hasParentWantsFocus()) { + // original requestFocus was likely on this view directly, so just clear focus + clearFocusInternal(null, /* propagate */ true, /* refocus */ false); + } + // otherwise, we let parents handle re-assigning focus during their layout passes. + } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) { + mPrivateFlags &= ~PFLAG_WANTS_FOCUS; + View focused = findFocus(); + if (focused != null) { + // Try to restore focus as close as possible to our starting focus. + if (!restoreDefaultFocus() && !hasParentWantsFocus()) { + // Give up and clear focus once we've reached the top-most parent which wants + // focus. + focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false); + } + } + } + if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) { mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; notifyEnterOrExitForAutoFillIfNeeded(true); } } + private boolean hasParentWantsFocus() { + ViewParent parent = mParent; + while (parent instanceof ViewGroup) { + ViewGroup pv = (ViewGroup) parent; + if ((pv.mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) { + return true; + } + parent = pv.mParent; + } + return false; + } + /** * Called from layout when this view should * assign a size and position to each of its children. @@ -20256,7 +20427,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mForegroundInfo.mBoundsChanged = true; } - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } return changed; } @@ -20275,6 +20446,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mOverlay.getOverlayView().setRight(newWidth); mOverlay.getOverlayView().setBottom(newHeight); } + // If this isn't laid out yet, focus assignment will be handled during the "deferment/ + // backtracking" of requestFocus during layout, so don't touch focus here. + if (!sCanFocusZeroSized && isLayoutValid()) { + if (newWidth <= 0 || newHeight <= 0) { + if (hasFocus()) { + clearFocus(); + if (mParent instanceof ViewGroup) { + ((ViewGroup) mParent).clearFocusedInCluster(); + } + } + clearAccessibilityFocus(); + } else if (oldWidth <= 0 || oldHeight <= 0) { + if (mParent != null && canTakeFocus()) { + mParent.focusableViewAvailable(this); + } + } + } rebuildOutline(); } @@ -21683,8 +21871,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (selected) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); } else { - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); } } } @@ -22038,7 +22225,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @param id the ID to search for * @return a view with given ID if found, or {@code null} otherwise - * @see View#findViewById(int) + * @see View#requireViewById(int) */ @Nullable public final <T extends View> T findViewById(@IdRes int id) { @@ -22049,6 +22236,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Finds the first descendant view with the given ID, the view itself if the ID matches + * {@link #getId()}, or throws an IllegalArgumentException if the ID is invalid or there is no + * matching view in the hierarchy. + * <p> + * <strong>Note:</strong> In most cases -- depending on compiler support -- + * the resulting view is automatically cast to the target class type. If + * the target class type is unconstrained, an explicit cast may be + * necessary. + * + * @param id the ID to search for + * @return a view with given ID + * @see View#findViewById(int) + */ + @NonNull + public final <T extends View> T requireViewById(@IdRes int id) { + T view = findViewById(id); + if (view == null) { + throw new IllegalArgumentException("ID does not reference a View inside this View"); + } + return view; + } + + /** * Finds a view by its unuque and stable accessibility id. * * @param accessibilityId The searched accessibility id. @@ -23391,15 +23601,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0); } - boolean okay = false; - Point shadowSize = new Point(); Point shadowTouchPoint = new Point(); shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint); - if ((shadowSize.x < 0) || (shadowSize.y < 0) || - (shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) { - throw new IllegalStateException("Drag shadow dimensions must not be negative"); + if ((shadowSize.x <= 0) || (shadowSize.y <= 0) + || (shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) { + throw new IllegalStateException("Drag shadow dimensions must be positive"); } if (ViewDebug.DEBUG_DRAG) { @@ -23410,40 +23618,50 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mDragSurface.release(); } mAttachInfo.mDragSurface = new Surface(); + mAttachInfo.mDragToken = null; + + final ViewRootImpl root = mAttachInfo.mViewRootImpl; + final SurfaceSession session = new SurfaceSession(root.mSurface); + final SurfaceControl surface = new SurfaceControl.Builder(session) + .setName("drag surface") + .setSize(shadowSize.x, shadowSize.y) + .setFormat(PixelFormat.TRANSLUCENT) + .build(); try { - mAttachInfo.mDragToken = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow, - flags, shadowSize.x, shadowSize.y, mAttachInfo.mDragSurface); - if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" - + mAttachInfo.mDragToken + " surface=" + mAttachInfo.mDragSurface); - if (mAttachInfo.mDragToken != null) { - Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null); - try { - canvas.drawColor(0, PorterDuff.Mode.CLEAR); - shadowBuilder.onDrawShadow(canvas); - } finally { - mAttachInfo.mDragSurface.unlockCanvasAndPost(canvas); - } - - final ViewRootImpl root = getViewRootImpl(); + mAttachInfo.mDragSurface.copyFrom(surface); + final Canvas canvas = mAttachInfo.mDragSurface.lockCanvas(null); + try { + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + shadowBuilder.onDrawShadow(canvas); + } finally { + mAttachInfo.mDragSurface.unlockCanvasAndPost(canvas); + } - // Cache the local state object for delivery with DragEvents - root.setLocalDragState(myLocalState); + // Cache the local state object for delivery with DragEvents + root.setLocalDragState(myLocalState); - // repurpose 'shadowSize' for the last touch point - root.getLastTouchPoint(shadowSize); + // repurpose 'shadowSize' for the last touch point + root.getLastTouchPoint(shadowSize); - okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, mAttachInfo.mDragToken, - root.getLastTouchSource(), shadowSize.x, shadowSize.y, - shadowTouchPoint.x, shadowTouchPoint.y, data); - if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay); + mAttachInfo.mDragToken = mAttachInfo.mSession.performDrag( + mAttachInfo.mWindow, flags, surface, root.getLastTouchSource(), + shadowSize.x, shadowSize.y, shadowTouchPoint.x, shadowTouchPoint.y, data); + if (ViewDebug.DEBUG_DRAG) { + Log.d(VIEW_LOG_TAG, "performDrag returned " + mAttachInfo.mDragToken); } + + return mAttachInfo.mDragToken != null; } catch (Exception e) { Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e); - mAttachInfo.mDragSurface.destroy(); - mAttachInfo.mDragSurface = null; + return false; + } finally { + if (mAttachInfo.mDragToken == null) { + mAttachInfo.mDragSurface.destroy(); + mAttachInfo.mDragSurface = null; + root.setLocalDragState(null); + } + session.kill(); } - - return okay; } /** @@ -26240,53 +26458,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Resuable callback for sending - * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. - */ - private class SendViewScrolledAccessibilityEvent implements Runnable { - public volatile boolean mIsPending; - public int mDeltaX; - public int mDeltaY; - - public void post(int dx, int dy) { - mDeltaX += dx; - mDeltaY += dy; - if (!mIsPending) { - mIsPending = true; - postDelayed(this, ViewConfiguration.getSendRecurringAccessibilityEventsInterval()); - } - } - - @Override - public void run() { - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - AccessibilityEvent event = AccessibilityEvent.obtain( - AccessibilityEvent.TYPE_VIEW_SCROLLED); - event.setScrollDeltaX(mDeltaX); - event.setScrollDeltaY(mDeltaY); - sendAccessibilityEventUnchecked(event); - } - reset(); - } - - private void reset() { - mIsPending = false; - mDeltaX = 0; - mDeltaY = 0; - } - } - - /** - * Remove the pending callback for sending a - * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. - */ - private void cancel(@Nullable SendViewScrolledAccessibilityEvent callback) { - if (callback == null || !callback.mIsPending) return; - removeCallbacks(callback); - callback.reset(); - } - - /** * <p> * This class represents a delegate that can be registered in a {@link View} * to enhance accessibility support via composition rather via inheritance. @@ -26902,6 +27073,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final boolean fromTouch = (mPrivateFlags3 & PFLAG3_FINGER_DOWN) == PFLAG3_FINGER_DOWN; mTooltipInfo.mTooltipPopup.show(this, x, y, fromTouch, mTooltipInfo.mTooltipText); mAttachInfo.mTooltipHost = this; + // The available accessibility actions have changed + notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); return true; } @@ -26920,6 +27093,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mAttachInfo != null) { mAttachInfo.mTooltipHost = null; } + // The available accessibility actions have changed + notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); } private boolean showLongClickTooltip(int x, int y) { @@ -26928,8 +27103,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return showTooltip(x, y, true); } - private void showHoverTooltip() { - showTooltip(mTooltipInfo.mAnchorX, mTooltipInfo.mAnchorY, false); + private boolean showHoverTooltip() { + return showTooltip(mTooltipInfo.mAnchorX, mTooltipInfo.mAnchorY, false); } boolean dispatchTooltipHoverEvent(MotionEvent event) { diff --git a/android/view/ViewDebug.java b/android/view/ViewDebug.java index afa94131..b09934e3 100644 --- a/android/view/ViewDebug.java +++ b/android/view/ViewDebug.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Point; import android.graphics.Rect; import android.os.Debug; import android.os.Handler; @@ -773,16 +774,15 @@ public class ViewDebug { final CountDownLatch latch = new CountDownLatch(1); final Bitmap[] cache = new Bitmap[1]; - captureView.post(new Runnable() { - public void run() { - try { - cache[0] = captureView.createSnapshot( - Bitmap.Config.ARGB_8888, 0, skipChildren); - } catch (OutOfMemoryError e) { - Log.w("View", "Out of memory for bitmap"); - } finally { - latch.countDown(); - } + captureView.post(() -> { + try { + CanvasProvider provider = captureView.isHardwareAccelerated() + ? new HardwareCanvasProvider() : new SoftwareCanvasProvider(); + cache[0] = captureView.createSnapshot(provider, skipChildren); + } catch (OutOfMemoryError e) { + Log.w("View", "Out of memory for bitmap"); + } finally { + latch.countDown(); } }); @@ -1740,4 +1740,86 @@ public class ViewDebug { } }); } + + /** + * @hide + */ + public static class SoftwareCanvasProvider implements CanvasProvider { + + private Canvas mCanvas; + private Bitmap mBitmap; + private boolean mEnabledHwBitmapsInSwMode; + + @Override + public Canvas getCanvas(View view, int width, int height) { + mBitmap = Bitmap.createBitmap(view.getResources().getDisplayMetrics(), + width, height, Bitmap.Config.ARGB_8888); + if (mBitmap == null) { + throw new OutOfMemoryError(); + } + mBitmap.setDensity(view.getResources().getDisplayMetrics().densityDpi); + + if (view.mAttachInfo != null) { + mCanvas = view.mAttachInfo.mCanvas; + } + if (mCanvas == null) { + mCanvas = new Canvas(); + } + mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled(); + mCanvas.setBitmap(mBitmap); + return mCanvas; + } + + @Override + public Bitmap createBitmap() { + mCanvas.setBitmap(null); + mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode); + return mBitmap; + } + } + + /** + * @hide + */ + public static class HardwareCanvasProvider implements CanvasProvider { + + private View mView; + private Point mSize; + private RenderNode mNode; + private DisplayListCanvas mCanvas; + + @Override + public Canvas getCanvas(View view, int width, int height) { + mView = view; + mSize = new Point(width, height); + mNode = RenderNode.create("ViewDebug", mView); + mNode.setLeftTopRightBottom(0, 0, width, height); + mNode.setClipToBounds(false); + mCanvas = mNode.start(width, height); + return mCanvas; + } + + @Override + public Bitmap createBitmap() { + mNode.end(mCanvas); + return ThreadedRenderer.createHardwareBitmap(mNode, mSize.x, mSize.y); + } + } + + /** + * @hide + */ + public interface CanvasProvider { + + /** + * Returns a canvas which can be used to draw {@param view} + */ + Canvas getCanvas(View view, int width, int height); + + /** + * Creates a bitmap from previously returned canvas + * @return + */ + Bitmap createBitmap(); + } } diff --git a/android/view/ViewGroup.java b/android/view/ViewGroup.java index 122df934..4631261e 100644 --- a/android/view/ViewGroup.java +++ b/android/view/ViewGroup.java @@ -57,6 +57,7 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.LayoutAnimationController; import android.view.animation.Transformation; +import android.view.autofill.Helper; import com.android.internal.R; @@ -3215,22 +3216,31 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } int descendantFocusability = getDescendantFocusability(); + boolean result; switch (descendantFocusability) { case FOCUS_BLOCK_DESCENDANTS: - return super.requestFocus(direction, previouslyFocusedRect); + result = super.requestFocus(direction, previouslyFocusedRect); + break; case FOCUS_BEFORE_DESCENDANTS: { final boolean took = super.requestFocus(direction, previouslyFocusedRect); - return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect); + result = took ? took : onRequestFocusInDescendants(direction, + previouslyFocusedRect); + break; } case FOCUS_AFTER_DESCENDANTS: { final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect); - return took ? took : super.requestFocus(direction, previouslyFocusedRect); + result = took ? took : super.requestFocus(direction, previouslyFocusedRect); + break; } default: throw new IllegalStateException("descendant focusability must be " + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS " + "but is " + descendantFocusability); } + if (result && !isLayoutValid() && ((mPrivateFlags & PFLAG_WANTS_FOCUS) == 0)) { + mPrivateFlags |= PFLAG_WANTS_FOCUS; + } + return result; } /** @@ -3465,8 +3475,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (!isLaidOut()) { - Log.v(VIEW_LOG_TAG, "dispatchProvideStructure(): not laid out, ignoring " - + childrenCount + " children of " + getAccessibilityViewId()); + if (Helper.sVerbose) { + Log.v(VIEW_LOG_TAG, "dispatchProvideStructure(): not laid out, ignoring " + + childrenCount + " children of " + getAccessibilityViewId()); + } return; } @@ -3637,44 +3649,34 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return ViewGroup.class.getName(); } - @Override - public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) { - // If this is a live region, we should send a subtree change event - // from this view. Otherwise, we can let it propagate up. - if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) { - notifyViewAccessibilityStateChangedIfNeeded( - AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); - } else if (mParent != null) { - try { - mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType); - } catch (AbstractMethodError e) { - Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + - " does not fully implement ViewParent", e); - } - } - } - /** @hide */ @Override - public void notifySubtreeAccessibilityStateChangedIfNeeded() { + public void notifyAccessibilitySubtreeChanged() { if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) { return; } // If something important for a11y is happening in this subtree, make sure it's dispatched // from a view that is important for a11y so it doesn't get lost. - if ((getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) - && !isImportantForAccessibility() && (getChildCount() > 0)) { + if (getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + && !isImportantForAccessibility() + && getChildCount() > 0) { ViewParent a11yParent = getParentForAccessibility(); if (a11yParent instanceof View) { - ((View) a11yParent).notifySubtreeAccessibilityStateChangedIfNeeded(); + ((View) a11yParent).notifyAccessibilitySubtreeChanged(); return; } } - super.notifySubtreeAccessibilityStateChangedIfNeeded(); + super.notifyAccessibilitySubtreeChanged(); + } + + @Override + public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) { + notifyAccessibilityStateChanged(source, changeType); } + /** @hide */ @Override - void resetSubtreeAccessibilityStateChanged() { + public void resetSubtreeAccessibilityStateChanged() { super.resetSubtreeAccessibilityStateChanged(); View[] children = mChildren; final int childCount = mChildrenCount; @@ -3854,7 +3856,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ @Override - public Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) { + public Bitmap createSnapshot(ViewDebug.CanvasProvider canvasProvider, boolean skipChildren) { int count = mChildrenCount; int[] visibilities = null; @@ -3870,17 +3872,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } - Bitmap b = super.createSnapshot(quality, backgroundColor, skipChildren); - - if (skipChildren) { - for (int i = 0; i < count; i++) { - View child = getChildAt(i); - child.mViewFlags = (child.mViewFlags & ~View.VISIBILITY_MASK) - | (visibilities[i] & View.VISIBILITY_MASK); + try { + return super.createSnapshot(canvasProvider, skipChildren); + } finally { + if (skipChildren) { + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + child.mViewFlags = (child.mViewFlags & ~View.VISIBILITY_MASK) + | (visibilities[i] & View.VISIBILITY_MASK); + } } } - - return b; } /** Return true if this ViewGroup is laying out using optical bounds. */ @@ -5086,7 +5088,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (child.getVisibility() != View.GONE) { - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } if (mTransientIndices != null) { @@ -5356,7 +5358,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager dispatchViewRemoved(view); if (view.getVisibility() != View.GONE) { - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size(); @@ -6075,7 +6077,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (invalidate) { invalidateViewProperty(false, false); } - notifySubtreeAccessibilityStateChangedIfNeeded(); + notifyAccessibilitySubtreeChanged(); } @Override diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java index 6c5091c2..30f584c5 100644 --- a/android/view/ViewRootImpl.java +++ b/android/view/ViewRootImpl.java @@ -20,7 +20,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER; import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM; -import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; @@ -89,9 +89,11 @@ import android.view.accessibility.AccessibilityManager.HighTextContrastChangeLis import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeProvider; +import android.view.accessibility.AccessibilityViewHierarchyState; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; +import android.view.accessibility.ThrottlingAccessibilityEventSender; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.inputmethod.InputMethodManager; @@ -113,7 +115,6 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.HashSet; import java.util.concurrent.CountDownLatch; /** @@ -460,10 +461,6 @@ public final class ViewRootImpl implements ViewParent, new AccessibilityInteractionConnectionManager(); final HighContrastTextManager mHighContrastTextManager; - SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent; - - HashSet<View> mTempHashSet; - private final int mDensity; private final int mNoncompatDensity; @@ -478,6 +475,8 @@ public final class ViewRootImpl implements ViewParent, private boolean mNeedsRendererSetup; + protected AccessibilityViewHierarchyState mAccessibilityState; + /** * Consistency verifier for debugging purposes. */ @@ -531,7 +530,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; } @@ -1597,9 +1596,9 @@ public final class ViewRootImpl implements ViewParent, void dispatchApplyInsets(View host) { WindowInsets insets = getWindowInsets(true /* forceConstruct */); - final boolean layoutInCutout = - (mWindowAttributes.flags2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0; - if (!layoutInCutout) { + final boolean dispatchCutout = (mWindowAttributes.layoutInDisplayCutoutMode + == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS); + if (!dispatchCutout) { // Window is either not laid out in cutout or the status bar inset takes care of // clearing the cutout, so we don't need to dispatch the cutout to the hierarchy. insets = insets.consumeDisplayCutout(); @@ -2338,7 +2337,7 @@ public final class ViewRootImpl implements ViewParent, } if (mFirst) { - if (sAlwaysAssignFocus) { + if (sAlwaysAssignFocus || !isInTouchMode()) { // handle first focus request if (DEBUG_INPUT_RESIZE) { Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus()); @@ -3609,7 +3608,7 @@ public final class ViewRootImpl implements ViewParent, checkThread(); if (mView != null) { if (!mView.hasFocus()) { - if (sAlwaysAssignFocus) { + if (sAlwaysAssignFocus || !isInTouchMode()) { v.requestFocus(); } } else { @@ -4212,10 +4211,7 @@ public final class ViewRootImpl implements ViewParent, // find the best view to give focus to in this brave new non-touch-mode // world - final View focused = focusSearch(null, View.FOCUS_DOWN); - if (focused != null) { - return focused.requestFocus(View.FOCUS_DOWN); - } + return mView.restoreDefaultFocus(); } return false; } @@ -7262,11 +7258,9 @@ public final class ViewRootImpl implements ViewParent, * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}. */ private void postSendWindowContentChangedCallback(View source, int changeType) { - if (mSendWindowContentChangedAccessibilityEvent == null) { - mSendWindowContentChangedAccessibilityEvent = - new SendWindowContentChangedAccessibilityEvent(); - } - mSendWindowContentChangedAccessibilityEvent.runOrPost(source, changeType); + getAccessibilityState() + .getSendWindowContentChangedAccessibilityEvent() + .runOrPost(source, changeType); } /** @@ -7274,11 +7268,20 @@ public final class ViewRootImpl implements ViewParent, * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event. */ private void removeSendWindowContentChangedCallback() { - if (mSendWindowContentChangedAccessibilityEvent != null) { - mHandler.removeCallbacks(mSendWindowContentChangedAccessibilityEvent); + if (mAccessibilityState != null + && mAccessibilityState.isWindowContentChangedEventSenderInitialized()) { + ThrottlingAccessibilityEventSender.cancelIfPending( + mAccessibilityState.getSendWindowContentChangedAccessibilityEvent()); } } + AccessibilityViewHierarchyState getAccessibilityState() { + if (mAccessibilityState == null) { + mAccessibilityState = new AccessibilityViewHierarchyState(); + } + return mAccessibilityState; + } + @Override public boolean showContextMenuForChild(View originalView) { return false; @@ -7314,12 +7317,8 @@ public final class ViewRootImpl implements ViewParent, return false; } - // Immediately flush pending content changed event (if any) to preserve event order - if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED - && mSendWindowContentChangedAccessibilityEvent != null - && mSendWindowContentChangedAccessibilityEvent.mSource != null) { - mSendWindowContentChangedAccessibilityEvent.removeCallbacksAndRun(); - } + // Send any pending event to prevent reordering + flushPendingAccessibilityEvents(); // Intercept accessibility focus events fired by virtual nodes to keep // track of accessibility focus position in such nodes. @@ -7363,6 +7362,19 @@ public final class ViewRootImpl implements ViewParent, return true; } + /** @hide */ + public void flushPendingAccessibilityEvents() { + if (mAccessibilityState != null) { + if (mAccessibilityState.isScrollEventSenderInitialized()) { + mAccessibilityState.getSendViewScrolledAccessibilityEvent().sendNowIfPending(); + } + if (mAccessibilityState.isWindowContentChangedEventSenderInitialized()) { + mAccessibilityState.getSendWindowContentChangedAccessibilityEvent() + .sendNowIfPending(); + } + } + } + /** * Updates the focused virtual view, when necessary, in response to a * content changed event. @@ -7497,39 +7509,6 @@ public final class ViewRootImpl implements ViewParent, return View.TEXT_ALIGNMENT_RESOLVED_DEFAULT; } - private View getCommonPredecessor(View first, View second) { - if (mTempHashSet == null) { - mTempHashSet = new HashSet<View>(); - } - HashSet<View> seen = mTempHashSet; - seen.clear(); - View firstCurrent = first; - while (firstCurrent != null) { - seen.add(firstCurrent); - ViewParent firstCurrentParent = firstCurrent.mParent; - if (firstCurrentParent instanceof View) { - firstCurrent = (View) firstCurrentParent; - } else { - firstCurrent = null; - } - } - View secondCurrent = second; - while (secondCurrent != null) { - if (seen.contains(secondCurrent)) { - seen.clear(); - return secondCurrent; - } - ViewParent secondCurrentParent = secondCurrent.mParent; - if (secondCurrentParent instanceof View) { - secondCurrent = (View) secondCurrentParent; - } else { - secondCurrent = null; - } - } - seen.clear(); - return null; - } - void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( @@ -8140,80 +8119,6 @@ public final class ViewRootImpl implements ViewParent, } } - private class SendWindowContentChangedAccessibilityEvent implements Runnable { - private int mChangeTypes = 0; - - public View mSource; - public long mLastEventTimeMillis; - - @Override - public void run() { - // Protect against re-entrant code and attempt to do the right thing in the case that - // we're multithreaded. - View source = mSource; - mSource = null; - if (source == null) { - Log.e(TAG, "Accessibility content change has no source"); - return; - } - // The accessibility may be turned off while we were waiting so check again. - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - mLastEventTimeMillis = SystemClock.uptimeMillis(); - AccessibilityEvent event = AccessibilityEvent.obtain(); - event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - event.setContentChangeTypes(mChangeTypes); - source.sendAccessibilityEventUnchecked(event); - } else { - mLastEventTimeMillis = 0; - } - // In any case reset to initial state. - source.resetSubtreeAccessibilityStateChanged(); - mChangeTypes = 0; - } - - public void runOrPost(View source, int changeType) { - if (mHandler.getLooper() != Looper.myLooper()) { - CalledFromWrongThreadException e = new CalledFromWrongThreadException("Only the " - + "original thread that created a view hierarchy can touch its views."); - // TODO: Throw the exception - Log.e(TAG, "Accessibility content change on non-UI thread. Future Android " - + "versions will throw an exception.", e); - // Attempt to recover. This code does not eliminate the thread safety issue, but - // it should force any issues to happen near the above log. - mHandler.removeCallbacks(this); - if (mSource != null) { - // Dispatch whatever was pending. It's still possible that the runnable started - // just before we removed the callbacks, and bad things will happen, but at - // least they should happen very close to the logged error. - run(); - } - } - if (mSource != null) { - // If there is no common predecessor, then mSource points to - // a removed view, hence in this case always prefer the source. - View predecessor = getCommonPredecessor(mSource, source); - mSource = (predecessor != null) ? predecessor : source; - mChangeTypes |= changeType; - return; - } - mSource = source; - mChangeTypes = changeType; - final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis; - final long minEventIntevalMillis = - ViewConfiguration.getSendRecurringAccessibilityEventsInterval(); - if (timeSinceLastMillis >= minEventIntevalMillis) { - removeCallbacksAndRun(); - } else { - mHandler.postDelayed(this, minEventIntevalMillis - timeSinceLastMillis); - } - } - - public void removeCallbacksAndRun() { - mHandler.removeCallbacks(this); - run(); - } - } - private static class KeyFallbackManager { // This is used to ensure that key-fallback events are only dispatched once. We attempt diff --git a/android/view/ViewStructure.java b/android/view/ViewStructure.java index d665dde3..1d94abeb 100644 --- a/android/view/ViewStructure.java +++ b/android/view/ViewStructure.java @@ -26,6 +26,8 @@ import android.util.Pair; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; +import com.android.internal.util.Preconditions; + import java.util.List; /** @@ -204,6 +206,16 @@ public abstract class ViewStructure { public abstract void setTextLines(int[] charOffsets, int[] baselines); /** + * Sets the identifier used to set the text associated with this view. + * + * <p>Should only be set when the node is used for autofill purposes - it will be ignored + * when used for Assist. + */ + public void setTextIdEntry(@NonNull String entryName) { + Preconditions.checkNotNull(entryName); + } + + /** * Set optional hint text associated with this view; this is for example the text that is * shown by an EditText when it is empty to indicate to the user the kind of text to input. */ diff --git a/android/view/Window.java b/android/view/Window.java index 176927fe..5bd0782d 100644 --- a/android/view/Window.java +++ b/android/view/Window.java @@ -1339,9 +1339,9 @@ public abstract class Window { /** * Finds a view that was identified by the {@code android:id} XML attribute - * that was processed in {@link android.app.Activity#onCreate}. This will - * implicitly call {@link #getDecorView} with all of the associated - * side-effects. + * that was processed in {@link android.app.Activity#onCreate}. + * <p> + * This will implicitly call {@link #getDecorView} with all of the associated side-effects. * <p> * <strong>Note:</strong> In most cases -- depending on compiler support -- * the resulting view is automatically cast to the target class type. If @@ -1351,11 +1351,35 @@ public abstract class Window { * @param id the ID to search for * @return a view with given ID if found, or {@code null} otherwise * @see View#findViewById(int) + * @see Window#requireViewById(int) */ @Nullable public <T extends View> T findViewById(@IdRes int id) { return getDecorView().findViewById(id); } + /** + * Finds a view that was identified by the {@code android:id} XML attribute + * that was processed in {@link android.app.Activity#onCreate}, or throws an + * IllegalArgumentException if the ID is invalid, or there is no matching view in the hierarchy. + * <p> + * <strong>Note:</strong> In most cases -- depending on compiler support -- + * the resulting view is automatically cast to the target class type. If + * the target class type is unconstrained, an explicit cast may be + * necessary. + * + * @param id the ID to search for + * @return a view with given ID + * @see View#requireViewById(int) + * @see Window#findViewById(int) + */ + @NonNull + public final <T extends View> T requireViewById(@IdRes int id) { + T view = findViewById(id); + if (view == null) { + throw new IllegalArgumentException("ID does not reference a View inside this Window"); + } + return view; + } /** * Convenience for @@ -2244,9 +2268,36 @@ public abstract class Window { * <p> * The transitionName for the view background will be "android:navigation:background". * </p> + * @attr ref android.R.styleable#Window_navigationBarColor */ public abstract void setNavigationBarColor(@ColorInt int color); + /** + * Shows a thin line of the specified color between the navigation bar and the app + * content. + * <p> + * For this to take effect, + * the window must be drawing the system bar backgrounds with + * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and + * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_NAVIGATION} must not be set. + * + * @param dividerColor The color of the thin line. + * @attr ref android.R.styleable#Window_navigationBarDividerColor + */ + public void setNavigationBarDividerColor(@ColorInt int dividerColor) { + } + + /** + * Retrieves the color of the navigation bar divider. + * + * @return The color of the navigation bar divider color. + * @see #setNavigationBarColor(int) + * @attr ref android.R.styleable#Window_navigationBarDividerColor + */ + public @ColorInt int getNavigationBarDividerColor() { + return 0; + } + /** @hide */ public void setTheme(int resId) { } diff --git a/android/view/WindowManager.java b/android/view/WindowManager.java index cbe012af..1c5e8719 100644 --- a/android/view/WindowManager.java +++ b/android/view/WindowManager.java @@ -17,10 +17,34 @@ package android.view; import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT; +import static android.view.WindowLayoutParamsProto.ALPHA; +import static android.view.WindowLayoutParamsProto.BUTTON_BRIGHTNESS; +import static android.view.WindowLayoutParamsProto.COLOR_MODE; +import static android.view.WindowLayoutParamsProto.FLAGS; +import static android.view.WindowLayoutParamsProto.FORMAT; +import static android.view.WindowLayoutParamsProto.GRAVITY; +import static android.view.WindowLayoutParamsProto.HAS_SYSTEM_UI_LISTENERS; +import static android.view.WindowLayoutParamsProto.HEIGHT; +import static android.view.WindowLayoutParamsProto.HORIZONTAL_MARGIN; +import static android.view.WindowLayoutParamsProto.INPUT_FEATURE_FLAGS; +import static android.view.WindowLayoutParamsProto.NEEDS_MENU_KEY; +import static android.view.WindowLayoutParamsProto.PREFERRED_REFRESH_RATE; +import static android.view.WindowLayoutParamsProto.PRIVATE_FLAGS; +import static android.view.WindowLayoutParamsProto.ROTATION_ANIMATION; +import static android.view.WindowLayoutParamsProto.SCREEN_BRIGHTNESS; +import static android.view.WindowLayoutParamsProto.SOFT_INPUT_MODE; +import static android.view.WindowLayoutParamsProto.SUBTREE_SYSTEM_UI_VISIBILITY_FLAGS; +import static android.view.WindowLayoutParamsProto.SYSTEM_UI_VISIBILITY_FLAGS; +import static android.view.WindowLayoutParamsProto.TYPE; +import static android.view.WindowLayoutParamsProto.USER_ACTIVITY_TIMEOUT; +import static android.view.WindowLayoutParamsProto.VERTICAL_MARGIN; +import static android.view.WindowLayoutParamsProto.WIDTH; +import static android.view.WindowLayoutParamsProto.WINDOW_ANIMATIONS; +import static android.view.WindowLayoutParamsProto.X; +import static android.view.WindowLayoutParamsProto.Y; import android.Manifest.permission; import android.annotation.IntDef; -import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; @@ -74,11 +98,198 @@ public interface WindowManager extends ViewManager { int DOCKED_BOTTOM = 4; /** @hide */ - final static String INPUT_CONSUMER_PIP = "pip_input_consumer"; + String INPUT_CONSUMER_PIP = "pip_input_consumer"; /** @hide */ - final static String INPUT_CONSUMER_NAVIGATION = "nav_input_consumer"; + String INPUT_CONSUMER_NAVIGATION = "nav_input_consumer"; /** @hide */ - final static String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer"; + String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer"; + /** @hide */ + String INPUT_CONSUMER_RECENTS_ANIMATION = "recents_animation_input_consumer"; + + /** + * Not set up for a transition. + * @hide + */ + int TRANSIT_UNSET = -1; + + /** + * No animation for transition. + * @hide + */ + int TRANSIT_NONE = 0; + + /** + * A window in a new activity is being opened on top of an existing one in the same task. + * @hide + */ + int TRANSIT_ACTIVITY_OPEN = 6; + + /** + * The window in the top-most activity is being closed to reveal the previous activity in the + * same task. + * @hide + */ + int TRANSIT_ACTIVITY_CLOSE = 7; + + /** + * A window in a new task is being opened on top of an existing one in another activity's task. + * @hide + */ + int TRANSIT_TASK_OPEN = 8; + + /** + * A window in the top-most activity is being closed to reveal the previous activity in a + * different task. + * @hide + */ + int TRANSIT_TASK_CLOSE = 9; + + /** + * A window in an existing task is being displayed on top of an existing one in another + * activity's task. + * @hide + */ + int TRANSIT_TASK_TO_FRONT = 10; + + /** + * A window in an existing task is being put below all other tasks. + * @hide + */ + int TRANSIT_TASK_TO_BACK = 11; + + /** + * A window in a new activity that doesn't have a wallpaper is being opened on top of one that + * does, effectively closing the wallpaper. + * @hide + */ + int TRANSIT_WALLPAPER_CLOSE = 12; + + /** + * A window in a new activity that does have a wallpaper is being opened on one that didn't, + * effectively opening the wallpaper. + * @hide + */ + int TRANSIT_WALLPAPER_OPEN = 13; + + /** + * A window in a new activity is being opened on top of an existing one, and both are on top + * of the wallpaper. + * @hide + */ + int TRANSIT_WALLPAPER_INTRA_OPEN = 14; + + /** + * The window in the top-most activity is being closed to reveal the previous activity, and + * both are on top of the wallpaper. + * @hide + */ + int TRANSIT_WALLPAPER_INTRA_CLOSE = 15; + + /** + * A window in a new task is being opened behind an existing one in another activity's task. + * The new window will show briefly and then be gone. + * @hide + */ + int TRANSIT_TASK_OPEN_BEHIND = 16; + + /** + * A window in a task is being animated in-place. + * @hide + */ + int TRANSIT_TASK_IN_PLACE = 17; + + /** + * An activity is being relaunched (e.g. due to configuration change). + * @hide + */ + int TRANSIT_ACTIVITY_RELAUNCH = 18; + + /** + * A task is being docked from recents. + * @hide + */ + int TRANSIT_DOCK_TASK_FROM_RECENTS = 19; + + /** + * Keyguard is going away. + * @hide + */ + int TRANSIT_KEYGUARD_GOING_AWAY = 20; + + /** + * Keyguard is going away with showing an activity behind that requests wallpaper. + * @hide + */ + int TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER = 21; + + /** + * Keyguard is being occluded. + * @hide + */ + int TRANSIT_KEYGUARD_OCCLUDE = 22; + + /** + * Keyguard is being unoccluded. + * @hide + */ + int TRANSIT_KEYGUARD_UNOCCLUDE = 23; + + /** + * @hide + */ + @IntDef(prefix = { "TRANSIT_" }, value = { + TRANSIT_UNSET, + TRANSIT_NONE, + TRANSIT_ACTIVITY_OPEN, + TRANSIT_ACTIVITY_CLOSE, + TRANSIT_TASK_OPEN, + TRANSIT_TASK_CLOSE, + TRANSIT_TASK_TO_FRONT, + TRANSIT_TASK_TO_BACK, + TRANSIT_WALLPAPER_CLOSE, + TRANSIT_WALLPAPER_OPEN, + TRANSIT_WALLPAPER_INTRA_OPEN, + TRANSIT_WALLPAPER_INTRA_CLOSE, + TRANSIT_TASK_OPEN_BEHIND, + TRANSIT_TASK_IN_PLACE, + TRANSIT_ACTIVITY_RELAUNCH, + TRANSIT_DOCK_TASK_FROM_RECENTS, + TRANSIT_KEYGUARD_GOING_AWAY, + TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER, + TRANSIT_KEYGUARD_OCCLUDE, + TRANSIT_KEYGUARD_UNOCCLUDE + }) + @Retention(RetentionPolicy.SOURCE) + @interface TransitionType {} + + /** + * Transition flag: Keyguard is going away, but keeping the notification shade open + * @hide + */ + int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE = 0x1; + + /** + * Transition flag: Keyguard is going away, but doesn't want an animation for it + * @hide + */ + int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION = 0x2; + + /** + * Transition flag: Keyguard is going away while it was showing the system wallpaper. + * @hide + */ + int TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER = 0x4; + + /** + * @hide + */ + @IntDef(flag = true, prefix = { "TRANSIT_FLAG_" }, value = { + TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE, + TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION, + TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER, + }) + @Retention(RetentionPolicy.SOURCE) + @interface TransitionFlags {} /** * Exception that is thrown when trying to add view whose @@ -863,7 +1074,12 @@ public interface WindowManager extends ViewManager { * decorations around the border (such as the status bar). The * window must correctly position its contents to take the screen * decoration into account. This flag is normally set for you - * by Window as described in {@link Window#setFlags}. */ + * by Window as described in {@link Window#setFlags}. + * + * <p>Note: on displays that have a {@link DisplayCutout}, the window may be placed + * such that it avoids the {@link DisplayCutout} area if necessary according to the + * {@link #layoutInDisplayCutoutMode}. + */ public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100; /** Window flag: allow window to extend outside of the screen. */ @@ -1269,33 +1485,6 @@ public interface WindowManager extends ViewManager { }, formatToHexString = true) public int flags; - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @LongDef( - flag = true, - value = { - LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA, - }) - @interface Flags2 {} - - /** - * Window flag: allow placing the window within the area that overlaps with the - * display cutout. - * - * <p> - * The window must correctly position its contents to take the display cutout into account. - * - * @see DisplayCutout - */ - public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 0x00000001; - - /** - * Various behavioral options/flags. Default is none. - * - * @see #FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA - */ - @Flags2 public long flags2; - /** * If the window has requested hardware acceleration, but this is not * allowed in the process it is in, then still render it as if it is @@ -2024,6 +2213,77 @@ public interface WindowManager extends ViewManager { */ public boolean hasSystemUiListeners; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + flag = true, + value = {LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT, + LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS, + LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER}) + @interface LayoutInDisplayCutoutMode {} + + /** + * Controls how the window is laid out if there is a {@link DisplayCutout}. + * + * <p> + * Defaults to {@link #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT}. + * + * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT + * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER + * @see DisplayCutout + */ + @LayoutInDisplayCutoutMode + public int layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; + + /** + * The window is allowed to extend into the {@link DisplayCutout} area, only if the + * {@link DisplayCutout} is fully contained within the status bar. Otherwise, the window is + * laid out such that it does not overlap with the {@link DisplayCutout} area. + * + * <p> + * In practice, this means that if the window did not set FLAG_FULLSCREEN or + * SYSTEM_UI_FLAG_FULLSCREEN, it can extend into the cutout area in portrait. + * Otherwise (i.e. fullscreen or landscape) it is laid out such that it does overlap the + * cutout area. + * + * <p> + * The usual precautions for not overlapping with the status bar are sufficient for ensuring + * that no important content overlaps with the DisplayCutout. + * + * @see DisplayCutout + * @see WindowInsets + */ + public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0; + + /** + * The window is always allowed to extend into the {@link DisplayCutout} area, + * even if fullscreen or in landscape. + * + * <p> + * The window must make sure that no important content overlaps with the + * {@link DisplayCutout}. + * + * @see DisplayCutout + * @see WindowInsets#getDisplayCutout() + */ + public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1; + + /** + * The window is never allowed to overlap with the DisplayCutout area. + * + * <p> + * This should be used with windows that transiently set SYSTEM_UI_FLAG_FULLSCREEN to + * avoid a relayout of the window when the flag is set or cleared. + * + * @see DisplayCutout + * @see View#SYSTEM_UI_FLAG_FULLSCREEN SYSTEM_UI_FLAG_FULLSCREEN + * @see View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + */ + public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2; + + /** * When this window has focus, disable touch pad pointer gesture processing. * The window will receive raw position updates from the touch pad instead @@ -2247,9 +2507,9 @@ public interface WindowManager extends ViewManager { out.writeInt(y); out.writeInt(type); out.writeInt(flags); - out.writeLong(flags2); out.writeInt(privateFlags); out.writeInt(softInputMode); + out.writeInt(layoutInDisplayCutoutMode); out.writeInt(gravity); out.writeFloat(horizontalMargin); out.writeFloat(verticalMargin); @@ -2303,9 +2563,9 @@ public interface WindowManager extends ViewManager { y = in.readInt(); type = in.readInt(); flags = in.readInt(); - flags2 = in.readLong(); privateFlags = in.readInt(); softInputMode = in.readInt(); + layoutInDisplayCutoutMode = in.readInt(); gravity = in.readInt(); horizontalMargin = in.readFloat(); verticalMargin = in.readFloat(); @@ -2436,10 +2696,6 @@ public interface WindowManager extends ViewManager { flags = o.flags; changes |= FLAGS_CHANGED; } - if (flags2 != o.flags2) { - flags2 = o.flags2; - changes |= FLAGS_CHANGED; - } if (privateFlags != o.privateFlags) { privateFlags = o.privateFlags; changes |= PRIVATE_FLAGS_CHANGED; @@ -2448,6 +2704,10 @@ public interface WindowManager extends ViewManager { softInputMode = o.softInputMode; changes |= SOFT_INPUT_MODE_CHANGED; } + if (layoutInDisplayCutoutMode != o.layoutInDisplayCutoutMode) { + layoutInDisplayCutoutMode = o.layoutInDisplayCutoutMode; + changes |= LAYOUT_CHANGED; + } if (gravity != o.gravity) { gravity = o.gravity; changes |= LAYOUT_CHANGED; @@ -2625,6 +2885,10 @@ public interface WindowManager extends ViewManager { sb.append(softInputModeToString(softInputMode)); sb.append('}'); } + if (layoutInDisplayCutoutMode != 0) { + sb.append(" layoutInDisplayCutoutMode="); + sb.append(layoutInDisplayCutoutModeToString(layoutInDisplayCutoutMode)); + } sb.append(" ty="); sb.append(ViewDebug.intToString(LayoutParams.class, "type", type)); if (format != PixelFormat.OPAQUE) { @@ -2693,11 +2957,6 @@ public interface WindowManager extends ViewManager { sb.append(System.lineSeparator()); sb.append(prefix).append(" fl=").append( ViewDebug.flagsToString(LayoutParams.class, "flags", flags)); - if (flags2 != 0) { - sb.append(System.lineSeparator()); - // TODO(roosa): add a long overload for ViewDebug.flagsToString. - sb.append(prefix).append(" fl2=0x").append(Long.toHexString(flags2)); - } if (privateFlags != 0) { sb.append(System.lineSeparator()); sb.append(prefix).append(" pfl=").append(ViewDebug.flagsToString( @@ -2722,7 +2981,32 @@ public interface WindowManager extends ViewManager { */ public void writeToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); - proto.write(WindowLayoutParamsProto.TYPE, type); + proto.write(TYPE, type); + proto.write(X, x); + proto.write(Y, y); + proto.write(WIDTH, width); + proto.write(HEIGHT, height); + proto.write(HORIZONTAL_MARGIN, horizontalMargin); + proto.write(VERTICAL_MARGIN, verticalMargin); + proto.write(GRAVITY, gravity); + proto.write(SOFT_INPUT_MODE, softInputMode); + proto.write(FORMAT, format); + proto.write(WINDOW_ANIMATIONS, windowAnimations); + proto.write(ALPHA, alpha); + proto.write(SCREEN_BRIGHTNESS, screenBrightness); + proto.write(BUTTON_BRIGHTNESS, buttonBrightness); + proto.write(ROTATION_ANIMATION, rotationAnimation); + proto.write(PREFERRED_REFRESH_RATE, preferredRefreshRate); + proto.write(WindowLayoutParamsProto.PREFERRED_DISPLAY_MODE_ID, preferredDisplayModeId); + proto.write(HAS_SYSTEM_UI_LISTENERS, hasSystemUiListeners); + proto.write(INPUT_FEATURE_FLAGS, inputFeatures); + proto.write(USER_ACTIVITY_TIMEOUT, userActivityTimeout); + proto.write(NEEDS_MENU_KEY, needsMenuKey); + proto.write(COLOR_MODE, mColorMode); + proto.write(FLAGS, flags); + proto.write(PRIVATE_FLAGS, privateFlags); + proto.write(SYSTEM_UI_VISIBILITY_FLAGS, systemUiVisibility); + proto.write(SUBTREE_SYSTEM_UI_VISIBILITY_FLAGS, subtreeSystemUiVisibility); proto.end(token); } @@ -2797,6 +3081,20 @@ public interface WindowManager extends ViewManager { && height == WindowManager.LayoutParams.MATCH_PARENT; } + private static String layoutInDisplayCutoutModeToString( + @LayoutInDisplayCutoutMode int mode) { + switch (mode) { + case LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT: + return "default"; + case LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS: + return "always"; + case LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER: + return "never"; + default: + return "unknown(" + mode + ")"; + } + } + private static String softInputModeToString(@SoftInputModeFlags int softInputMode) { final StringBuilder result = new StringBuilder(); final int state = softInputMode & SOFT_INPUT_MASK_STATE; diff --git a/android/view/WindowManagerPolicyConstants.java b/android/view/WindowManagerPolicyConstants.java index 21943bd6..a6f36bbf 100644 --- a/android/view/WindowManagerPolicyConstants.java +++ b/android/view/WindowManagerPolicyConstants.java @@ -18,8 +18,6 @@ package android.view; import static android.view.Display.DEFAULT_DISPLAY; -import android.annotation.SystemApi; - /** * Constants for interfacing with WindowManagerService and WindowManagerPolicyInternal. * @hide @@ -47,6 +45,11 @@ public interface WindowManagerPolicyConstants { int PRESENCE_INTERNAL = 1 << 0; int PRESENCE_EXTERNAL = 1 << 1; + // Navigation bar position values + int NAV_BAR_LEFT = 1 << 0; + int NAV_BAR_RIGHT = 1 << 1; + int NAV_BAR_BOTTOM = 1 << 2; + /** * Sticky broadcast of the current HDMI plugged state. */ @@ -62,7 +65,6 @@ public interface WindowManagerPolicyConstants { * Set to {@code true} when intent was invoked from pressing the home key. * @hide */ - @SystemApi String EXTRA_FROM_HOME_KEY = "android.intent.extra.FROM_HOME_KEY"; // TODO: move this to a more appropriate place. diff --git a/android/view/accessibility/AccessibilityEvent.java b/android/view/accessibility/AccessibilityEvent.java index 1d19a9f5..e0f74a7d 100644 --- a/android/view/accessibility/AccessibilityEvent.java +++ b/android/view/accessibility/AccessibilityEvent.java @@ -38,19 +38,14 @@ import java.util.List; * <p> * An accessibility event is fired by an individual view which populates the event with * data for its state and requests from its parent to send the event to interested - * parties. The parent can optionally add an {@link AccessibilityRecord} for itself before - * dispatching a similar request to its parent. A parent can also choose not to respect the - * request for sending an event. The accessibility event is sent by the topmost view in the - * view tree. Therefore, an {@link android.accessibilityservice.AccessibilityService} can - * explore all records in an accessibility event to obtain more information about the - * context in which the event was fired. + * parties. The parent can optionally modify or even block the event based on its broader + * understanding of the user interface's context. * </p> * <p> - * The main purpose of an accessibility event is to expose enough information for an - * {@link android.accessibilityservice.AccessibilityService} to provide meaningful feedback - * to the user. Sometimes however, an accessibility service may need more contextual - * information then the one in the event pay-load. In such cases the service can obtain - * the event source which is an {@link AccessibilityNodeInfo} (snapshot of a View state) + * The main purpose of an accessibility event is to communicate changes in the UI to an + * {@link android.accessibilityservice.AccessibilityService}. The service may then inspect, + * if needed the user interface by examining the View hierarchy, as represented by a tree of + * {@link AccessibilityNodeInfo}s (snapshot of a View state) * which can be used for exploring the window content. Note that the privilege for accessing * an event's source, thus the window content, has to be explicitly requested. For more * details refer to {@link android.accessibilityservice.AccessibilityService}. If an @@ -85,21 +80,6 @@ import java.util.List; * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source's sub-tree.</li> - * <li>{@link #isEnabled()} - Whether the source is enabled.</li> - * <li>{@link #isPassword()} - Whether the source is password.</li> - * <li>{@link #isChecked()} - Whether the source is checked.</li> - * <li>{@link #getContentDescription()} - The content description of the source.</li> - * <li>{@link #getScrollX()} - The offset of the source left edge in pixels - * (without descendants of AdapterView).</li> - * <li>{@link #getScrollY()} - The offset of the source top edge in pixels - * (without descendants of AdapterView).</li> - * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source, - * inclusive (for descendants of AdapterView).</li> - * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source, - * inclusive (for descendants of AdapterView).</li> - * <li>{@link #getItemCount()} - The total items of the source - * (for descendants of AdapterView).</li> * </ul> * </p> * <p> @@ -113,21 +93,6 @@ import java.util.List; * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source's sub-tree.</li> - * <li>{@link #isEnabled()} - Whether the source is enabled.</li> - * <li>{@link #isPassword()} - Whether the source is password.</li> - * <li>{@link #isChecked()} - Whether the source is checked.</li> - * <li>{@link #getContentDescription()} - The content description of the source.</li> - * <li>{@link #getScrollX()} - The offset of the source left edge in pixels - * (without descendants of AdapterView).</li> - * <li>{@link #getScrollY()} - The offset of the source top edge in pixels - * (without descendants of AdapterView).</li> - * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source, - * inclusive (for descendants of AdapterView).</li> - * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source, - * inclusive (for descendants of AdapterView).</li> - * <li>{@link #getItemCount()} - The total items of the source - * (for descendants of AdapterView).</li> * </ul> * </p> * <p> @@ -141,23 +106,6 @@ import java.util.List; * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source's sub-tree.</li> - * <li>{@link #isEnabled()} - Whether the source is enabled.</li> - * <li>{@link #isPassword()} - Whether the source is password.</li> - * <li>{@link #isChecked()} - Whether the source is checked.</li> - * <li>{@link #getItemCount()} - The number of selectable items of the source.</li> - * <li>{@link #getCurrentItemIndex()} - The currently selected item index.</li> - * <li>{@link #getContentDescription()} - The content description of the source.</li> - * <li>{@link #getScrollX()} - The offset of the source left edge in pixels - * (without descendants of AdapterView).</li> - * <li>{@link #getScrollY()} - The offset of the source top edge in pixels - * (without descendants of AdapterView).</li> - * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source, - * inclusive (for descendants of AdapterView).</li> - * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source, - * inclusive (for descendants of AdapterView).</li> - * <li>{@link #getItemCount()} - The total items of the source - * (for descendants of AdapterView).</li> * </ul> * </p> * <p> @@ -171,23 +119,6 @@ import java.util.List; * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source's sub-tree.</li> - * <li>{@link #isEnabled()} - Whether the source is enabled.</li> - * <li>{@link #isPassword()} - Whether the source is password.</li> - * <li>{@link #isChecked()} - Whether the source is checked.</li> - * <li>{@link #getItemCount()} - The number of focusable items on the screen.</li> - * <li>{@link #getCurrentItemIndex()} - The currently focused item index.</li> - * <li>{@link #getContentDescription()} - The content description of the source.</li> - * <li>{@link #getScrollX()} - The offset of the source left edge in pixels - * (without descendants of AdapterView).</li> - * <li>{@link #getScrollY()} - The offset of the source top edge in pixels - * (without descendants of AdapterView).</li> - * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source, - * inclusive (for descendants of AdapterView).</li> - * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source, - * inclusive (for descendants of AdapterView).</li> - * <li>{@link #getItemCount()} - The total items of the source - * (for descendants of AdapterView).</li> * </ul> * </p> * <p> @@ -201,15 +132,11 @@ import java.util.List; * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source.</li> - * <li>{@link #isEnabled()} - Whether the source is enabled.</li> - * <li>{@link #isPassword()} - Whether the source is password.</li> - * <li>{@link #isChecked()} - Whether the source is checked.</li> + * <li>{@link #getText()} - The new text of the source.</li> + * <li>{@link #getBeforeText()} - The text of the source before the change.</li> * <li>{@link #getFromIndex()} - The text change start index.</li> * <li>{@link #getAddedCount()} - The number of added characters.</li> * <li>{@link #getRemovedCount()} - The number of removed characters.</li> - * <li>{@link #getBeforeText()} - The text of the source before the change.</li> - * <li>{@link #getContentDescription()} - The content description of the source.</li> * </ul> * </p> * <p> @@ -223,13 +150,6 @@ import java.util.List; * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source.</li> - * <li>{@link #isPassword()} - Whether the source is password.</li> - * <li>{@link #getFromIndex()} - The selection start index.</li> - * <li>{@link #getToIndex()} - The selection end index.</li> - * <li>{@link #getItemCount()} - The length of the source text.</li> - * <li>{@link #isEnabled()} - Whether the source is enabled.</li> - * <li>{@link #getContentDescription()} - The content description of the source.</li> * </ul> * </p> * <b>View text traversed at movement granularity</b> - represents the event of traversing the @@ -251,23 +171,11 @@ import java.util.List; * <li>{@link #getToIndex()} - The end of the text that was skipped over in this movement. * This is the ending point when moving forward through the text, but not when moving * back.</li> - * <li>{@link #isPassword()} - Whether the source is password.</li> - * <li>{@link #isEnabled()} - Whether the source is enabled.</li> - * <li>{@link #getContentDescription()} - The content description of the source.</li> - * <li>{@link #getMovementGranularity()} - Sets the granularity at which a view's text - * was traversed.</li> * <li>{@link #getAction()} - Gets traversal action which specifies the direction.</li> * </ul> * </p> * <p> - * <b>View scrolled</b> - represents the event of scrolling a view. If - * the source is a descendant of {@link android.widget.AdapterView} the - * scroll is reported in terms of visible items - the first visible item, - * the last visible item, and the total items - because the the source - * is unaware of its pixel size since its adapter is responsible for - * creating views. In all other cases the scroll is reported as the current - * scroll on the X and Y axis respectively plus the height of the source in - * pixels.</br> + * <b>View scrolled</b> - represents the event of scrolling a view. </br> * <em>Type:</em> {@link #TYPE_VIEW_SCROLLED}</br> * <em>Properties:</em></br> * <ul> @@ -276,37 +184,19 @@ import java.util.List; * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source's sub-tree.</li> - * <li>{@link #isEnabled()} - Whether the source is enabled.</li> - * <li>{@link #getContentDescription()} - The content description of the source.</li> - * <li>{@link #getScrollX()} - The offset of the source left edge in pixels - * (without descendants of AdapterView).</li> - * <li>{@link #getScrollY()} - The offset of the source top edge in pixels - * (without descendants of AdapterView).</li> - * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source, - * inclusive (for descendants of AdapterView).</li> - * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source, - * inclusive (for descendants of AdapterView).</li> - * <li>{@link #getItemCount()} - The total items of the source - * (for descendants of AdapterView).</li> + * <li>{@link #getScrollDeltaX()} - The difference in the horizontal position.</li> + * <li>{@link #getScrollDeltaY()} - The difference in the vertical position.</li> * </ul> - * <em>Note:</em> This event type is not dispatched to descendants though - * {@link android.view.View#dispatchPopulateAccessibilityEvent(AccessibilityEvent) - * View.dispatchPopulateAccessibilityEvent(AccessibilityEvent)}, hence the event - * source {@link android.view.View} and the sub-tree rooted at it will not receive - * calls to {@link android.view.View#onPopulateAccessibilityEvent(AccessibilityEvent) - * View.onPopulateAccessibilityEvent(AccessibilityEvent)}. The preferred way to add - * text content to such events is by setting the - * {@link android.R.styleable#View_contentDescription contentDescription} of the source - * view.</br> * </p> * <p> * <b>TRANSITION TYPES</b></br> * </p> * <p> - * <b>Window state changed</b> - represents the event of opening a - * {@link android.widget.PopupWindow}, {@link android.view.Menu}, - * {@link android.app.Dialog}, etc.</br> + * <b>Window state changed</b> - represents the event of a change to a section of + * the user interface that is visually distinct. Should be sent from either the + * root view of a window or from a view that is marked as a pane + * {@link android.view.View#setAccessibilityPaneTitle(CharSequence)}. Not that changes + * to true windows are represented by {@link #TYPE_WINDOWS_CHANGED}.</br> * <em>Type:</em> {@link #TYPE_WINDOW_STATE_CHANGED}</br> * <em>Properties:</em></br> * <ul> @@ -315,8 +205,7 @@ import java.util.List; * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source's sub-tree.</li> - * <li>{@link #isEnabled()} - Whether the source is enabled.</li> + * <li>{@link #getText()} - The text of the source's sub-tree, including the pane titles.</li> * </ul> * </p> * <p> @@ -325,10 +214,6 @@ import java.util.List; * a view size, etc.</br> * </p> * <p> - * <strong>Note:</strong> This event is fired only for the window source of the - * last accessibility event different from {@link #TYPE_NOTIFICATION_STATE_CHANGED} - * and its purpose is to notify clients that the content of the user interaction - * window has changed.</br> * <em>Type:</em> {@link #TYPE_WINDOW_CONTENT_CHANGED}</br> * <em>Properties:</em></br> * <ul> @@ -339,32 +224,26 @@ import java.util.List; * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> * </ul> - * <em>Note:</em> This event type is not dispatched to descendants though - * {@link android.view.View#dispatchPopulateAccessibilityEvent(AccessibilityEvent) - * View.dispatchPopulateAccessibilityEvent(AccessibilityEvent)}, hence the event - * source {@link android.view.View} and the sub-tree rooted at it will not receive - * calls to {@link android.view.View#onPopulateAccessibilityEvent(AccessibilityEvent) - * View.onPopulateAccessibilityEvent(AccessibilityEvent)}. The preferred way to add - * text content to such events is by setting the - * {@link android.R.styleable#View_contentDescription contentDescription} of the source - * view.</br> * </p> * <p> - * <b>Windows changed</b> - represents the event of changes in the windows shown on + * <b>Windows changed</b> - represents a change in the windows shown on * the screen such as a window appeared, a window disappeared, a window size changed, - * a window layer changed, etc.</br> + * a window layer changed, etc. These events should only come from the system, which is responsible + * for managing windows. The list of windows is available from + * {@link android.accessibilityservice.AccessibilityService#getWindows()}. + * For regions of the user interface that are presented as windows but are + * controlled by an app's process, use {@link #TYPE_WINDOW_STATE_CHANGED}.</br> * <em>Type:</em> {@link #TYPE_WINDOWS_CHANGED}</br> * <em>Properties:</em></br> * <ul> * <li>{@link #getEventType()} - The type of the event.</li> * <li>{@link #getEventTime()} - The event time.</li> + * <li>{@link #getWindowChanges()}</li> - The specific change to the source window * </ul> * <em>Note:</em> You can retrieve the {@link AccessibilityWindowInfo} for the window - * source of the event via {@link AccessibilityEvent#getSource()} to get the source - * node on which then call {@link AccessibilityNodeInfo#getWindow() - * AccessibilityNodeInfo.getWindow()} to get the window. Also all windows on the screen can - * be retrieved by a call to {@link android.accessibilityservice.AccessibilityService#getWindows() - * android.accessibilityservice.AccessibilityService.getWindows()}. + * source of the event by looking through the list returned by + * {@link android.accessibilityservice.AccessibilityService#getWindows()} for the window whose ID + * matches {@link #getWindowId()}. * </p> * <p> * <b>NOTIFICATION TYPES</b></br> @@ -402,19 +281,6 @@ import java.util.List; * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source's sub-tree.</li> - * <li>{@link #isEnabled()} - Whether the source is enabled.</li> - * <li>{@link #getContentDescription()} - The content description of the source.</li> - * <li>{@link #getScrollX()} - The offset of the source left edge in pixels - * (without descendants of AdapterView).</li> - * <li>{@link #getScrollY()} - The offset of the source top edge in pixels - * (without descendants of AdapterView).</li> - * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source, - * inclusive (for descendants of AdapterView).</li> - * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source, - * inclusive (for descendants of AdapterView).</li> - * <li>{@link #getItemCount()} - The total items of the source - * (for descendants of AdapterView).</li> * </ul> * </p> * <b>View hover exit</b> - represents the event of stopping to hover @@ -428,19 +294,6 @@ import java.util.List; * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source's sub-tree.</li> - * <li>{@link #isEnabled()} - Whether the source is enabled.</li> - * <li>{@link #getContentDescription()} - The content description of the source.</li> - * <li>{@link #getScrollX()} - The offset of the source left edge in pixels - * (without descendants of AdapterView).</li> - * <li>{@link #getScrollY()} - The offset of the source top edge in pixels - * (without descendants of AdapterView).</li> - * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source, - * inclusive (for descendants of AdapterView).</li> - * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source, - * inclusive (for descendants of AdapterView).</li> - * <li>{@link #getItemCount()} - The total items of the source - * (for descendants of AdapterView).</li> * </ul> * </p> * <p> @@ -513,10 +366,10 @@ import java.util.List; * <b>MISCELLANEOUS TYPES</b></br> * </p> * <p> - * <b>Announcement</b> - represents the event of an application making an - * announcement. Usually this announcement is related to some sort of a context - * change for which none of the events representing UI transitions is a good fit. - * For example, announcing a new page in a book.</br> + * <b>Announcement</b> - represents the event of an application requesting a screen reader to make + * an announcement. Because the event carries no semantic meaning, this event is appropriate only + * in exceptional situations where additional screen reader output is needed but other types of + * accessibility services do not need to be aware of the change.</br> * <em>Type:</em> {@link #TYPE_ANNOUNCEMENT}</br> * <em>Properties:</em></br> * <ul> @@ -526,7 +379,6 @@ import java.util.List; * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> * <li>{@link #getText()} - The text of the announcement.</li> - * <li>{@link #isEnabled()} - Whether the source is enabled.</li> * </ul> * </p> * @@ -586,8 +438,10 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010; /** - * Represents the event of opening a {@link android.widget.PopupWindow}, - * {@link android.view.Menu}, {@link android.app.Dialog}, etc. + * Represents the event of a change to a visually distinct section of the user interface. + * These events should only be dispatched from {@link android.view.View}s that have + * accessibility pane titles, and replaces {@link #TYPE_WINDOW_CONTENT_CHANGED} for those + * sources. Details about the change are available from {@link #getContentChangeTypes()}. */ public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020; @@ -674,7 +528,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000; /** - * Represents the event change in the windows shown on the screen. + * Represents the event change in the system windows shown on the screen. This event type should + * only be dispatched by the system. */ public static final int TYPE_WINDOWS_CHANGED = 0x00400000; @@ -696,7 +551,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par /** * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: - * A node in the subtree rooted at the source node was added or removed. + * One or more content changes occurred in the the subtree rooted at the source node, + * or the subtree's structure changed when a node was added or removed. */ public static final int CONTENT_CHANGE_TYPE_SUBTREE = 0x00000001; @@ -712,6 +568,124 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par */ public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004; + /** + * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event: + * The node's pane title changed. + */ + public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 0x00000008; + + /** + * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event: + * The node has a pane title, and either just appeared or just was assigned a title when it + * had none before. + */ + public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 0x00000010; + + /** + * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event: + * Can mean one of two slightly different things. The primary meaning is that the node has + * a pane title, and was removed from the node hierarchy. It will also be sent if the pane + * title is set to {@code null} after it contained a title. + * No source will be returned if the node is no longer on the screen. To make the change more + * clear for the user, the first entry in {@link #getText()} will return the value that would + * have been returned by {@code getSource().getPaneTitle()}. + */ + public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 0x00000020; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window was added. + */ + public static final int WINDOWS_CHANGE_ADDED = 0x00000001; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * A window was removed. + */ + public static final int WINDOWS_CHANGE_REMOVED = 0x00000002; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's title changed. + */ + public static final int WINDOWS_CHANGE_TITLE = 0x00000004; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's bounds changed. + */ + public static final int WINDOWS_CHANGE_BOUNDS = 0x00000008; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's layer changed. + */ + public static final int WINDOWS_CHANGE_LAYER = 0x00000010; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's {@link AccessibilityWindowInfo#isActive()} changed. + */ + public static final int WINDOWS_CHANGE_ACTIVE = 0x00000020; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's {@link AccessibilityWindowInfo#isFocused()} changed. + */ + public static final int WINDOWS_CHANGE_FOCUSED = 0x00000040; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's {@link AccessibilityWindowInfo#isAccessibilityFocused()} changed. + */ + public static final int WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED = 0x00000080; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's parent changed. + */ + public static final int WINDOWS_CHANGE_PARENT = 0x00000100; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window's children changed. + */ + public static final int WINDOWS_CHANGE_CHILDREN = 0x00000200; + + /** + * Change type for {@link #TYPE_WINDOWS_CHANGED} event: + * The window either entered or exited picture-in-picture mode. + */ + public static final int WINDOWS_CHANGE_PIP = 0x00000400; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "WINDOWS_CHANGE_" }, value = { + WINDOWS_CHANGE_ADDED, + WINDOWS_CHANGE_REMOVED, + WINDOWS_CHANGE_TITLE, + WINDOWS_CHANGE_BOUNDS, + WINDOWS_CHANGE_LAYER, + WINDOWS_CHANGE_ACTIVE, + WINDOWS_CHANGE_FOCUSED, + WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED, + WINDOWS_CHANGE_PARENT, + WINDOWS_CHANGE_CHILDREN, + WINDOWS_CHANGE_PIP + }) + public @interface WindowsChangeTypes {} + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "CONTENT_CHANGE_TYPE_" }, + value = { + CONTENT_CHANGE_TYPE_UNDEFINED, + CONTENT_CHANGE_TYPE_SUBTREE, + CONTENT_CHANGE_TYPE_TEXT, + CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION, + CONTENT_CHANGE_TYPE_PANE_TITLE + }) + public @interface ContentChangeTypes {} /** @hide */ @IntDef(flag = true, prefix = { "TYPE_" }, value = { @@ -782,6 +756,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par int mMovementGranularity; int mAction; int mContentChangeTypes; + int mWindowChangeTypes; private ArrayList<AccessibilityRecord> mRecords; @@ -802,6 +777,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mMovementGranularity = event.mMovementGranularity; mAction = event.mAction; mContentChangeTypes = event.mContentChangeTypes; + mWindowChangeTypes = event.mWindowChangeTypes; mEventTime = event.mEventTime; mPackageName = event.mPackageName; } @@ -885,6 +861,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED} * </ul> */ + @ContentChangeTypes public int getContentChangeTypes() { return mContentChangeTypes; } @@ -913,12 +890,49 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * @throws IllegalStateException If called from an AccessibilityService. * @see #getContentChangeTypes() */ - public void setContentChangeTypes(int changeTypes) { + public void setContentChangeTypes(@ContentChangeTypes int changeTypes) { enforceNotSealed(); mContentChangeTypes = changeTypes; } /** + * Get the bit mask of change types signaled by a {@link #TYPE_WINDOWS_CHANGED} event. A + * single event may represent multiple change types. + * + * @return The bit mask of change types. + */ + @WindowsChangeTypes + public int getWindowChanges() { + return mWindowChangeTypes; + } + + /** @hide */ + public void setWindowChanges(@WindowsChangeTypes int changes) { + mWindowChangeTypes = changes; + } + + private static String windowChangeTypesToString(@WindowsChangeTypes int types) { + return BitUtils.flagsToString(types, AccessibilityEvent::singleWindowChangeTypeToString); + } + + private static String singleWindowChangeTypeToString(int type) { + switch (type) { + case WINDOWS_CHANGE_ADDED: return "WINDOWS_CHANGE_ADDED"; + case WINDOWS_CHANGE_REMOVED: return "WINDOWS_CHANGE_REMOVED"; + case WINDOWS_CHANGE_TITLE: return "WINDOWS_CHANGE_TITLE"; + case WINDOWS_CHANGE_BOUNDS: return "WINDOWS_CHANGE_BOUNDS"; + case WINDOWS_CHANGE_LAYER: return "WINDOWS_CHANGE_LAYER"; + case WINDOWS_CHANGE_ACTIVE: return "WINDOWS_CHANGE_ACTIVE"; + case WINDOWS_CHANGE_FOCUSED: return "WINDOWS_CHANGE_FOCUSED"; + case WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED: + return "WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED"; + case WINDOWS_CHANGE_PARENT: return "WINDOWS_CHANGE_PARENT"; + case WINDOWS_CHANGE_CHILDREN: return "WINDOWS_CHANGE_CHILDREN"; + default: return Integer.toHexString(type); + } + } + + /** * Sets the event type. * * @param eventType The event type. @@ -1025,6 +1039,26 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } /** + * Convenience method to obtain a {@link #TYPE_WINDOWS_CHANGED} event for a specific window and + * change set. + * + * @param windowId The ID of the window that changed + * @param windowChangeTypes The changes to populate + * @return An instance of a TYPE_WINDOWS_CHANGED, populated with the requested fields and with + * importantForAccessibility set to {@code true}. + * + * @hide + */ + public static AccessibilityEvent obtainWindowsChangedEvent( + int windowId, int windowChangeTypes) { + final AccessibilityEvent event = AccessibilityEvent.obtain(TYPE_WINDOWS_CHANGED); + event.setWindowId(windowId); + event.setWindowChanges(windowChangeTypes); + event.setImportantForAccessibility(true); + return event; + } + + /** * Returns a cached instance if such is available or a new one is * instantiated with its type property set. * @@ -1099,6 +1133,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mMovementGranularity = 0; mAction = 0; mContentChangeTypes = 0; + mWindowChangeTypes = 0; mPackageName = null; mEventTime = 0; if (mRecords != null) { @@ -1120,6 +1155,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par mMovementGranularity = parcel.readInt(); mAction = parcel.readInt(); mContentChangeTypes = parcel.readInt(); + mWindowChangeTypes = parcel.readInt(); mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); mEventTime = parcel.readLong(); mConnectionId = parcel.readInt(); @@ -1178,6 +1214,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par parcel.writeInt(mMovementGranularity); parcel.writeInt(mAction); parcel.writeInt(mContentChangeTypes); + parcel.writeInt(mWindowChangeTypes); TextUtils.writeToParcel(mPackageName, parcel, 0); parcel.writeLong(mEventTime); parcel.writeInt(mConnectionId); @@ -1236,41 +1273,33 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par builder.append("EventType: ").append(eventTypeToString(mEventType)); builder.append("; EventTime: ").append(mEventTime); builder.append("; PackageName: ").append(mPackageName); - builder.append("; MovementGranularity: ").append(mMovementGranularity); - builder.append("; Action: ").append(mAction); - builder.append(super.toString()); - if (DEBUG) { - builder.append("\n"); + if (!DEBUG_CONCISE_TOSTRING || mMovementGranularity != 0) { + builder.append("; MovementGranularity: ").append(mMovementGranularity); + } + if (!DEBUG_CONCISE_TOSTRING || mAction != 0) { + builder.append("; Action: ").append(mAction); + } + if (!DEBUG_CONCISE_TOSTRING || mContentChangeTypes != 0) { builder.append("; ContentChangeTypes: ").append( contentChangeTypesToString(mContentChangeTypes)); - builder.append("; sourceWindowId: ").append(mSourceWindowId); - builder.append("; mSourceNodeId: ").append(mSourceNodeId); - for (int i = 0; i < getRecordCount(); i++) { - final AccessibilityRecord record = getRecord(i); - builder.append(" Record "); - builder.append(i); - builder.append(":"); - builder.append(" [ ClassName: " + record.mClassName); - builder.append("; Text: " + record.mText); - builder.append("; ContentDescription: " + record.mContentDescription); - builder.append("; ItemCount: " + record.mItemCount); - builder.append("; CurrentItemIndex: " + record.mCurrentItemIndex); - builder.append("; IsEnabled: " + record.isEnabled()); - builder.append("; IsPassword: " + record.isPassword()); - builder.append("; IsChecked: " + record.isChecked()); - builder.append("; IsFullScreen: " + record.isFullScreen()); - builder.append("; Scrollable: " + record.isScrollable()); - builder.append("; BeforeText: " + record.mBeforeText); - builder.append("; FromIndex: " + record.mFromIndex); - builder.append("; ToIndex: " + record.mToIndex); - builder.append("; ScrollX: " + record.mScrollX); - builder.append("; ScrollY: " + record.mScrollY); - builder.append("; AddedCount: " + record.mAddedCount); - builder.append("; RemovedCount: " + record.mRemovedCount); - builder.append("; ParcelableData: " + record.mParcelableData); - builder.append(" ]"); + } + if (!DEBUG_CONCISE_TOSTRING || mWindowChangeTypes != 0) { + builder.append("; WindowChangeTypes: ").append( + contentChangeTypesToString(mWindowChangeTypes)); + } + super.appendTo(builder); + if (DEBUG || DEBUG_CONCISE_TOSTRING) { + if (!DEBUG_CONCISE_TOSTRING) { builder.append("\n"); } + if (DEBUG) { + builder.append("; SourceWindowId: ").append(mSourceWindowId); + builder.append("; SourceNodeId: ").append(mSourceNodeId); + } + for (int i = 0; i < getRecordCount(); i++) { + builder.append(" Record ").append(i).append(":"); + getRecord(i).appendTo(builder).append("\n"); + } } else { builder.append("; recordCount: ").append(getRecordCount()); } diff --git a/android/view/accessibility/AccessibilityNodeInfo.java b/android/view/accessibility/AccessibilityNodeInfo.java index 28ef6978..23e7d619 100644 --- a/android/view/accessibility/AccessibilityNodeInfo.java +++ b/android/view/accessibility/AccessibilityNodeInfo.java @@ -639,6 +639,8 @@ public class AccessibilityNodeInfo implements Parcelable { private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 0x0100000; + private static final int BOOLEAN_PROPERTY_IS_HEADING = 0x0200000; + /** * Bits that provide the id of a virtual descendant of a view. */ @@ -723,7 +725,9 @@ public class AccessibilityNodeInfo implements Parcelable { private CharSequence mText; private CharSequence mHintText; private CharSequence mError; + private CharSequence mPaneTitle; private CharSequence mContentDescription; + private CharSequence mTooltipText; private String mViewIdResourceName; private ArrayList<String> mExtraDataKeys; @@ -2033,6 +2037,33 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * If this node represents a visually distinct region of the screen that may update separately + * from the rest of the window, it is considered a pane. Set the pane title to indicate that + * the node is a pane, and to provide a title for it. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param paneTitle The title of the pane represented by this node. + */ + public void setPaneTitle(@Nullable CharSequence paneTitle) { + enforceNotSealed(); + mPaneTitle = (paneTitle == null) + ? null : paneTitle.subSequence(0, paneTitle.length()); + } + + /** + * Get the title of the pane represented by this node. + * + * @return The title of the pane represented by this node, or {@code null} if this node does + * not represent a pane. + */ + public @Nullable CharSequence getPaneTitle() { + return mPaneTitle; + } + + /** * Get the drawing order of the view corresponding it this node. * <p> * Drawing order is determined only within the node's parent, so this index is only relative @@ -2381,6 +2412,30 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Returns whether node represents a heading. + * + * @return {@code true} if the node is a heading, {@code false} otherwise. + */ + public boolean isHeading() { + return getBooleanProperty(BOOLEAN_PROPERTY_IS_HEADING); + } + + /** + * Sets whether the node represents a heading. + * + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param isHeading {@code true} if the node is a heading, {@code false} otherwise. + */ + public void setHeading(boolean isHeading) { + setBooleanProperty(BOOLEAN_PROPERTY_IS_HEADING, isHeading); + } + + /** * Gets the package this node comes from. * * @return The package name. @@ -2601,6 +2656,34 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Gets the tooltip text of this node. + * + * @return The tooltip text. + */ + @Nullable + public CharSequence getTooltipText() { + return mTooltipText; + } + + /** + * Sets the tooltip text of this node. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param tooltipText The tooltip text. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setTooltipText(@Nullable CharSequence tooltipText) { + enforceNotSealed(); + mTooltipText = (tooltipText == null) ? null + : tooltipText.subSequence(0, tooltipText.length()); + } + + /** * Sets the view for which the view represented by this info serves as a * label for accessibility purposes. * @@ -3151,6 +3234,14 @@ public class AccessibilityNodeInfo implements Parcelable { nonDefaultFields |= bitAt(fieldIndex); } fieldIndex++; + if (!Objects.equals(mPaneTitle, DEFAULT.mPaneTitle)) { + nonDefaultFields |= bitAt(fieldIndex); + } + fieldIndex++; + if (!Objects.equals(mTooltipText, DEFAULT.mTooltipText)) { + nonDefaultFields |= bitAt(fieldIndex); + } + fieldIndex++; if (!Objects.equals(mViewIdResourceName, DEFAULT.mViewIdResourceName)) { nonDefaultFields |= bitAt(fieldIndex); } @@ -3270,6 +3361,9 @@ public class AccessibilityNodeInfo implements Parcelable { if (isBitSet(nonDefaultFields, fieldIndex++)) { parcel.writeCharSequence(mContentDescription); } + if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPaneTitle); + if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mTooltipText); + if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mViewIdResourceName); if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionStart); @@ -3341,6 +3435,8 @@ public class AccessibilityNodeInfo implements Parcelable { mHintText = other.mHintText; mError = other.mError; mContentDescription = other.mContentDescription; + mPaneTitle = other.mPaneTitle; + mTooltipText = other.mTooltipText; mViewIdResourceName = other.mViewIdResourceName; if (mActions != null) mActions.clear(); @@ -3461,6 +3557,8 @@ public class AccessibilityNodeInfo implements Parcelable { if (isBitSet(nonDefaultFields, fieldIndex++)) { mContentDescription = parcel.readCharSequence(); } + if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readString(); + if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = parcel.readCharSequence(); if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString(); if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionStart = parcel.readInt(); @@ -3623,6 +3721,10 @@ public class AccessibilityNodeInfo implements Parcelable { return "ACTION_SET_PROGRESS"; case R.id.accessibilityActionContextClick: return "ACTION_CONTEXT_CLICK"; + case R.id.accessibilityActionShowTooltip: + return "ACTION_SHOW_TOOLTIP"; + case R.id.accessibilityActionHideTooltip: + return "ACTION_HIDE_TOOLTIP"; default: return "ACTION_UNKNOWN"; } @@ -3736,6 +3838,7 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append("; error: ").append(mError); builder.append("; maxTextLength: ").append(mMaxTextLength); builder.append("; contentDescription: ").append(mContentDescription); + builder.append("; tooltipText: ").append(mTooltipText); builder.append("; viewIdResName: ").append(mViewIdResourceName); builder.append("; checkable: ").append(isCheckable()); @@ -4150,6 +4253,20 @@ public class AccessibilityNodeInfo implements Parcelable { public static final AccessibilityAction ACTION_MOVE_WINDOW = new AccessibilityAction(R.id.accessibilityActionMoveWindow); + /** + * Action to show a tooltip. A node should expose this action only for views with tooltip + * text that but are not currently showing a tooltip. + */ + public static final AccessibilityAction ACTION_SHOW_TOOLTIP = + new AccessibilityAction(R.id.accessibilityActionShowTooltip); + + /** + * Action to hide a tooltip. A node should expose this action only for views that are + * currently showing a tooltip. + */ + public static final AccessibilityAction ACTION_HIDE_TOOLTIP = + new AccessibilityAction(R.id.accessibilityActionHideTooltip); + private final int mActionId; private final CharSequence mLabel; @@ -4562,7 +4679,8 @@ public class AccessibilityNodeInfo implements Parcelable { * @param rowSpan The number of rows the item spans. * @param columnIndex The column index at which the item is located. * @param columnSpan The number of columns the item spans. - * @param heading Whether the item is a heading. + * @param heading Whether the item is a heading. (Prefer + * {@link AccessibilityNodeInfo#setHeading(boolean)}). */ public static CollectionItemInfo obtain(int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading) { @@ -4576,7 +4694,8 @@ public class AccessibilityNodeInfo implements Parcelable { * @param rowSpan The number of rows the item spans. * @param columnIndex The column index at which the item is located. * @param columnSpan The number of columns the item spans. - * @param heading Whether the item is a heading. + * @param heading Whether the item is a heading. (Prefer + * {@link AccessibilityNodeInfo#setHeading(boolean)}) * @param selected Whether the item is selected. */ public static CollectionItemInfo obtain(int rowIndex, int rowSpan, @@ -4663,6 +4782,7 @@ public class AccessibilityNodeInfo implements Parcelable { * heading, table header, etc. * * @return If the item is a heading. + * @deprecated Use {@link AccessibilityNodeInfo#isHeading()} */ public boolean isHeading() { return mHeading; diff --git a/android/view/accessibility/AccessibilityRecord.java b/android/view/accessibility/AccessibilityRecord.java index fa505c97..0a709f8f 100644 --- a/android/view/accessibility/AccessibilityRecord.java +++ b/android/view/accessibility/AccessibilityRecord.java @@ -16,6 +16,8 @@ package android.view.accessibility; +import static com.android.internal.util.CollectionUtils.isEmpty; + import android.annotation.Nullable; import android.os.Parcelable; import android.view.View; @@ -55,6 +57,8 @@ import java.util.List; * @see AccessibilityNodeInfo */ public class AccessibilityRecord { + /** @hide */ + protected static final boolean DEBUG_CONCISE_TOSTRING = false; private static final int UNDEFINED = -1; @@ -888,28 +892,69 @@ public class AccessibilityRecord { @Override public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(" [ ClassName: " + mClassName); - builder.append("; Text: " + mText); - builder.append("; ContentDescription: " + mContentDescription); - builder.append("; ItemCount: " + mItemCount); - builder.append("; CurrentItemIndex: " + mCurrentItemIndex); - builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED)); - builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD)); - builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED)); - builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN)); - builder.append("; Scrollable: " + getBooleanProperty(PROPERTY_SCROLLABLE)); - builder.append("; BeforeText: " + mBeforeText); - builder.append("; FromIndex: " + mFromIndex); - builder.append("; ToIndex: " + mToIndex); - builder.append("; ScrollX: " + mScrollX); - builder.append("; ScrollY: " + mScrollY); - builder.append("; MaxScrollX: " + mMaxScrollX); - builder.append("; MaxScrollY: " + mMaxScrollY); - builder.append("; AddedCount: " + mAddedCount); - builder.append("; RemovedCount: " + mRemovedCount); - builder.append("; ParcelableData: " + mParcelableData); + return appendTo(new StringBuilder()).toString(); + } + + StringBuilder appendTo(StringBuilder builder) { + builder.append(" [ ClassName: ").append(mClassName); + if (!DEBUG_CONCISE_TOSTRING || !isEmpty(mText)) { + appendPropName(builder, "Text").append(mText); + } + append(builder, "ContentDescription", mContentDescription); + append(builder, "ItemCount", mItemCount); + append(builder, "CurrentItemIndex", mCurrentItemIndex); + + appendUnless(true, PROPERTY_ENABLED, builder); + appendUnless(false, PROPERTY_PASSWORD, builder); + appendUnless(false, PROPERTY_CHECKED, builder); + appendUnless(false, PROPERTY_FULL_SCREEN, builder); + appendUnless(false, PROPERTY_SCROLLABLE, builder); + + append(builder, "BeforeText", mBeforeText); + append(builder, "FromIndex", mFromIndex); + append(builder, "ToIndex", mToIndex); + append(builder, "ScrollX", mScrollX); + append(builder, "ScrollY", mScrollY); + append(builder, "MaxScrollX", mMaxScrollX); + append(builder, "MaxScrollY", mMaxScrollY); + append(builder, "AddedCount", mAddedCount); + append(builder, "RemovedCount", mRemovedCount); + append(builder, "ParcelableData", mParcelableData); builder.append(" ]"); - return builder.toString(); + return builder; + } + + private void appendUnless(boolean defValue, int prop, StringBuilder builder) { + boolean value = getBooleanProperty(prop); + if (DEBUG_CONCISE_TOSTRING && value == defValue) return; + appendPropName(builder, singleBooleanPropertyToString(prop)) + .append(value); + } + + private static String singleBooleanPropertyToString(int prop) { + switch (prop) { + case PROPERTY_CHECKED: return "Checked"; + case PROPERTY_ENABLED: return "Enabled"; + case PROPERTY_PASSWORD: return "Password"; + case PROPERTY_FULL_SCREEN: return "FullScreen"; + case PROPERTY_SCROLLABLE: return "Scrollable"; + case PROPERTY_IMPORTANT_FOR_ACCESSIBILITY: + return "ImportantForAccessibility"; + default: return Integer.toHexString(prop); + } + } + + private void append(StringBuilder builder, String propName, int propValue) { + if (DEBUG_CONCISE_TOSTRING && propValue == UNDEFINED) return; + appendPropName(builder, propName).append(propValue); + } + + private void append(StringBuilder builder, String propName, Object propValue) { + if (DEBUG_CONCISE_TOSTRING && propValue == null) return; + appendPropName(builder, propName).append(propValue); + } + + private StringBuilder appendPropName(StringBuilder builder, String propName) { + return builder.append("; ").append(propName).append(": "); } } diff --git a/android/view/accessibility/AccessibilityViewHierarchyState.java b/android/view/accessibility/AccessibilityViewHierarchyState.java new file mode 100644 index 00000000..447fafaa --- /dev/null +++ b/android/view/accessibility/AccessibilityViewHierarchyState.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.accessibility; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +/** + * Accessibility-related state of a {@link android.view.ViewRootImpl} + * + * @hide + */ +public class AccessibilityViewHierarchyState { + private @Nullable SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent; + private @Nullable SendWindowContentChangedAccessibilityEvent + mSendWindowContentChangedAccessibilityEvent; + + /** + * @return a {@link SendViewScrolledAccessibilityEvent}, creating one if needed + */ + public @NonNull SendViewScrolledAccessibilityEvent getSendViewScrolledAccessibilityEvent() { + if (mSendViewScrolledAccessibilityEvent == null) { + mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent(); + } + return mSendViewScrolledAccessibilityEvent; + } + + public boolean isScrollEventSenderInitialized() { + return mSendViewScrolledAccessibilityEvent != null; + } + + /** + * @return a {@link SendWindowContentChangedAccessibilityEvent}, creating one if needed + */ + public @NonNull SendWindowContentChangedAccessibilityEvent + getSendWindowContentChangedAccessibilityEvent() { + if (mSendWindowContentChangedAccessibilityEvent == null) { + mSendWindowContentChangedAccessibilityEvent = + new SendWindowContentChangedAccessibilityEvent(); + } + return mSendWindowContentChangedAccessibilityEvent; + } + + public boolean isWindowContentChangedEventSenderInitialized() { + return mSendWindowContentChangedAccessibilityEvent != null; + } +} diff --git a/android/view/accessibility/AccessibilityWindowInfo.java b/android/view/accessibility/AccessibilityWindowInfo.java index ef1a3f3b..c1c9174c 100644 --- a/android/view/accessibility/AccessibilityWindowInfo.java +++ b/android/view/accessibility/AccessibilityWindowInfo.java @@ -21,9 +21,12 @@ import android.annotation.TestApi; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import android.util.LongArray; import android.util.Pools.SynchronizedPool; +import android.view.accessibility.AccessibilityEvent.WindowsChangeTypes; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; /** @@ -575,7 +578,7 @@ public final class AccessibilityWindowInfo implements Parcelable { StringBuilder builder = new StringBuilder(); builder.append("AccessibilityWindowInfo["); builder.append("title=").append(mTitle); - builder.append("id=").append(mId); + builder.append(", id=").append(mId); builder.append(", type=").append(typeToString(mType)); builder.append(", layer=").append(mLayer); builder.append(", bounds=").append(mBoundsInScreen); @@ -713,6 +716,60 @@ public final class AccessibilityWindowInfo implements Parcelable { return false; } + /** + * Reports how this window differs from a possibly different state of the same window. The + * argument must have the same id and type as neither of those properties may change. + * + * @param other The new state. + * @return A set of flags showing how the window has changes, or 0 if the two states are the + * same. + * + * @hide + */ + @WindowsChangeTypes + public int differenceFrom(AccessibilityWindowInfo other) { + if (other.mId != mId) { + throw new IllegalArgumentException("Not same window."); + } + if (other.mType != mType) { + throw new IllegalArgumentException("Not same type."); + } + int changes = 0; + if (!TextUtils.equals(mTitle, other.mTitle)) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_TITLE; + } + + if (!mBoundsInScreen.equals(other.mBoundsInScreen)) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_BOUNDS; + } + if (mLayer != other.mLayer) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_LAYER; + } + if (getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE) + != other.getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE)) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_ACTIVE; + } + if (getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED) + != other.getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED)) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_FOCUSED; + } + if (getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED) + != other.getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED)) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED; + } + if (getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE) + != other.getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE)) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_PIP; + } + if (mParentId != other.mParentId) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_PARENT; + } + if (!Objects.equals(mChildIds, other.mChildIds)) { + changes |= AccessibilityEvent.WINDOWS_CHANGE_CHILDREN; + } + return changes; + } + public static final Parcelable.Creator<AccessibilityWindowInfo> CREATOR = new Creator<AccessibilityWindowInfo>() { @Override diff --git a/android/view/accessibility/SendViewScrolledAccessibilityEvent.java b/android/view/accessibility/SendViewScrolledAccessibilityEvent.java new file mode 100644 index 00000000..40a1b6a2 --- /dev/null +++ b/android/view/accessibility/SendViewScrolledAccessibilityEvent.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.accessibility; + + +import android.annotation.NonNull; +import android.view.View; + +/** + * Sender for {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. + * + * @hide + */ +public class SendViewScrolledAccessibilityEvent extends ThrottlingAccessibilityEventSender { + + public int mDeltaX; + public int mDeltaY; + + /** + * Post a scroll event to be sent for the given view + */ + public void post(View source, int dx, int dy) { + if (!isPendingFor(source)) sendNowIfPending(); + + mDeltaX += dx; + mDeltaY += dy; + + if (!isPendingFor(source)) scheduleFor(source); + } + + @Override + protected void performSendEvent(@NonNull View source) { + AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); + event.setScrollDeltaX(mDeltaX); + event.setScrollDeltaY(mDeltaY); + source.sendAccessibilityEventUnchecked(event); + } + + @Override + protected void resetState(@NonNull View source) { + mDeltaX = 0; + mDeltaY = 0; + } +} diff --git a/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java b/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java new file mode 100644 index 00000000..df38fba5 --- /dev/null +++ b/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.accessibility; + + +import static com.android.internal.util.ObjectUtils.firstNotNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.view.View; +import android.view.ViewParent; + +import java.util.HashSet; + +/** + * @hide + */ +public class SendWindowContentChangedAccessibilityEvent + extends ThrottlingAccessibilityEventSender { + + private int mChangeTypes = 0; + + private HashSet<View> mTempHashSet; + + @Override + protected void performSendEvent(@NonNull View source) { + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + event.setContentChangeTypes(mChangeTypes); + source.sendAccessibilityEventUnchecked(event); + } + + @Override + protected void resetState(@Nullable View source) { + if (source != null) { + source.resetSubtreeAccessibilityStateChanged(); + } + mChangeTypes = 0; + } + + /** + * Post the {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event with the given + * {@link AccessibilityEvent#getContentChangeTypes change type} for the given view + */ + public void runOrPost(View source, int changeType) { + if (source.getAccessibilityLiveRegion() != View.ACCESSIBILITY_LIVE_REGION_NONE) { + sendNowIfPending(); + mChangeTypes = changeType; + sendNow(source); + } else { + mChangeTypes |= changeType; + scheduleFor(source); + } + } + + @Override + protected @Nullable View tryMerge(@NonNull View oldSource, @NonNull View newSource) { + // If there is no common predecessor, then oldSource points to + // a removed view, hence in this case always prefer the newSource. + return firstNotNull( + getCommonPredecessor(oldSource, newSource), + newSource); + } + + private View getCommonPredecessor(View first, View second) { + if (mTempHashSet == null) { + mTempHashSet = new HashSet<>(); + } + HashSet<View> seen = mTempHashSet; + seen.clear(); + View firstCurrent = first; + while (firstCurrent != null) { + seen.add(firstCurrent); + ViewParent firstCurrentParent = firstCurrent.getParent(); + if (firstCurrentParent instanceof View) { + firstCurrent = (View) firstCurrentParent; + } else { + firstCurrent = null; + } + } + View secondCurrent = second; + while (secondCurrent != null) { + if (seen.contains(secondCurrent)) { + seen.clear(); + return secondCurrent; + } + ViewParent secondCurrentParent = secondCurrent.getParent(); + if (secondCurrentParent instanceof View) { + secondCurrent = (View) secondCurrentParent; + } else { + secondCurrent = null; + } + } + seen.clear(); + return null; + } +} diff --git a/android/view/accessibility/ThrottlingAccessibilityEventSender.java b/android/view/accessibility/ThrottlingAccessibilityEventSender.java new file mode 100644 index 00000000..66fa3010 --- /dev/null +++ b/android/view/accessibility/ThrottlingAccessibilityEventSender.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.accessibility; + + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import android.util.Log; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewRootImpl; +import android.view.ViewRootImpl.CalledFromWrongThreadException; + +/** + * A throttling {@link AccessibilityEvent} sender that relies on its currently associated + * 'source' view's {@link View#postDelayed delayed execution} to delay and possibly + * {@link #tryMerge merge} together any events that come in less than + * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval + * the configured amount of milliseconds} apart. + * + * The suggested usage is to create a singleton extending this class, holding any state specific to + * the particular event type that the subclass represents, and have an 'entrypoint' method that + * delegates to {@link #scheduleFor(View)}. + * For example: + * + * {@code + * public void post(View view, String text, int resId) { + * mText = text; + * mId = resId; + * scheduleFor(view); + * } + * } + * + * @see #scheduleFor(View) + * @see #tryMerge(View, View) + * @see #performSendEvent(View) + * @hide + */ +public abstract class ThrottlingAccessibilityEventSender { + + private static final boolean DEBUG = false; + private static final String LOG_TAG = "ThrottlingA11ySender"; + + View mSource; + private long mLastSendTimeMillis = Long.MIN_VALUE; + private boolean mIsPending = false; + + private final Runnable mWorker = () -> { + View source = mSource; + if (DEBUG) Log.d(LOG_TAG, thisClass() + ".run(mSource = " + source + ")"); + + if (!checkAndResetIsPending() || source == null) { + resetStateInternal(); + return; + } + + // Accessibility may be turned off while we were waiting + if (isAccessibilityEnabled(source)) { + mLastSendTimeMillis = SystemClock.uptimeMillis(); + performSendEvent(source); + } + resetStateInternal(); + }; + + /** + * Populate and send an {@link AccessibilityEvent} using the given {@code source} view, as well + * as any extra data from this instance's state. + * + * Send the event via {@link View#sendAccessibilityEventUnchecked(AccessibilityEvent)} or + * {@link View#sendAccessibilityEvent(int)} on the provided {@code source} view to allow for + * overrides of those methods on {@link View} subclasses to take effect, and/or make sure that + * an {@link View#getAccessibilityDelegate() accessibility delegate} is not ignored if any. + */ + protected abstract void performSendEvent(@NonNull View source); + + /** + * Perform optional cleanup after {@link #performSendEvent} + * + * @param source the view this event was associated with + */ + protected abstract void resetState(@Nullable View source); + + /** + * Attempt to merge the pending events for source views {@code oldSource} and {@code newSource} + * into one, with source set to the resulting {@link View} + * + * A result of {@code null} means merger is not possible, resulting in the currently pending + * event being flushed before proceeding. + */ + protected @Nullable View tryMerge(@NonNull View oldSource, @NonNull View newSource) { + return null; + } + + /** + * Schedules a {@link #performSendEvent} with the source {@link View} set to given + * {@code source} + * + * If an event is already scheduled a {@link #tryMerge merge} will be attempted. + * If merging is not possible (as indicated by the null result from {@link #tryMerge}), + * the currently scheduled event will be {@link #sendNow sent immediately} and the new one + * will be scheduled afterwards. + */ + protected final void scheduleFor(@NonNull View source) { + if (DEBUG) Log.d(LOG_TAG, thisClass() + ".scheduleFor(source = " + source + ")"); + + Handler uiHandler = source.getHandler(); + if (uiHandler == null || uiHandler.getLooper() != Looper.myLooper()) { + CalledFromWrongThreadException e = new CalledFromWrongThreadException( + "Expected to be called from main thread but was called from " + + Thread.currentThread()); + // TODO: Throw the exception + Log.e(LOG_TAG, "Accessibility content change on non-UI thread. Future Android " + + "versions will throw an exception.", e); + } + + if (!isAccessibilityEnabled(source)) return; + + if (mIsPending) { + View merged = tryMerge(mSource, source); + if (merged != null) { + setSource(merged); + return; + } else { + sendNow(); + } + } + + setSource(source); + + final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastSendTimeMillis; + final long minEventIntervalMillis = + ViewConfiguration.getSendRecurringAccessibilityEventsInterval(); + if (timeSinceLastMillis >= minEventIntervalMillis) { + sendNow(); + } else { + mSource.postDelayed(mWorker, minEventIntervalMillis - timeSinceLastMillis); + } + } + + static boolean isAccessibilityEnabled(@NonNull View contextProvider) { + return AccessibilityManager.getInstance(contextProvider.getContext()).isEnabled(); + } + + protected final void sendNow(View source) { + setSource(source); + sendNow(); + } + + private void sendNow() { + mSource.removeCallbacks(mWorker); + mWorker.run(); + } + + /** + * Flush the event if one is pending + */ + public void sendNowIfPending() { + if (mIsPending) sendNow(); + } + + /** + * Cancel the event if one is pending and is for the given view + */ + public final void cancelIfPendingFor(@NonNull View source) { + if (isPendingFor(source)) cancelIfPending(this); + } + + /** + * @return whether an event is currently pending for the given source view + */ + protected final boolean isPendingFor(@Nullable View source) { + return mIsPending && mSource == source; + } + + /** + * Cancel the event if one is not null and pending + */ + public static void cancelIfPending(@Nullable ThrottlingAccessibilityEventSender sender) { + if (sender == null || !sender.checkAndResetIsPending()) return; + sender.mSource.removeCallbacks(sender.mWorker); + sender.resetStateInternal(); + } + + void resetStateInternal() { + if (DEBUG) Log.d(LOG_TAG, thisClass() + ".resetStateInternal()"); + + resetState(mSource); + setSource(null); + } + + boolean checkAndResetIsPending() { + if (mIsPending) { + mIsPending = false; + return true; + } else { + return false; + } + } + + private void setSource(@Nullable View source) { + if (DEBUG) Log.d(LOG_TAG, thisClass() + ".setSource(" + source + ")"); + + if (source == null && mIsPending) { + Log.e(LOG_TAG, "mSource nullified while callback still pending: " + this); + return; + } + + if (source != null && !mIsPending) { + // At most one can be pending at any given time + View oldSource = mSource; + if (oldSource != null) { + ViewRootImpl viewRootImpl = oldSource.getViewRootImpl(); + if (viewRootImpl != null) { + viewRootImpl.flushPendingAccessibilityEvents(); + } + } + mIsPending = true; + } + mSource = source; + } + + String thisClass() { + return getClass().getSimpleName(); + } + + @Override + public String toString() { + return thisClass() + "(" + mSource + ")"; + } + +} diff --git a/android/view/animation/AnimationUtils.java b/android/view/animation/AnimationUtils.java index f5c36139..990fbdb0 100644 --- a/android/view/animation/AnimationUtils.java +++ b/android/view/animation/AnimationUtils.java @@ -156,6 +156,8 @@ public class AnimationUtils { anim = new RotateAnimation(c, attrs); } else if (name.equals("translate")) { anim = new TranslateAnimation(c, attrs); + } else if (name.equals("cliprect")) { + anim = new ClipRectAnimation(c, attrs); } else { throw new RuntimeException("Unknown animation name: " + parser.getName()); } diff --git a/android/view/animation/ClipRectAnimation.java b/android/view/animation/ClipRectAnimation.java index e194927e..21509d3a 100644 --- a/android/view/animation/ClipRectAnimation.java +++ b/android/view/animation/ClipRectAnimation.java @@ -16,7 +16,11 @@ package android.view.animation; +import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.DisplayMetrics; /** * An animation that controls the clip of an object. See the @@ -26,8 +30,84 @@ import android.graphics.Rect; * @hide */ public class ClipRectAnimation extends Animation { - protected Rect mFromRect = new Rect(); - protected Rect mToRect = new Rect(); + protected final Rect mFromRect = new Rect(); + protected final Rect mToRect = new Rect(); + + private int mFromLeftType = ABSOLUTE; + private int mFromTopType = ABSOLUTE; + private int mFromRightType = ABSOLUTE; + private int mFromBottomType = ABSOLUTE; + + private int mToLeftType = ABSOLUTE; + private int mToTopType = ABSOLUTE; + private int mToRightType = ABSOLUTE; + private int mToBottomType = ABSOLUTE; + + private float mFromLeftValue; + private float mFromTopValue; + private float mFromRightValue; + private float mFromBottomValue; + + private float mToLeftValue; + private float mToTopValue; + private float mToRightValue; + private float mToBottomValue; + + /** + * Constructor used when a ClipRectAnimation is loaded from a resource. + * + * @param context Application context to use + * @param attrs Attribute set from which to read values + */ + public ClipRectAnimation(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ClipRectAnimation); + + Description d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_fromLeft)); + mFromLeftType = d.type; + mFromLeftValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_fromTop)); + mFromTopType = d.type; + mFromTopValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_fromRight)); + mFromRightType = d.type; + mFromRightValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_fromBottom)); + mFromBottomType = d.type; + mFromBottomValue = d.value; + + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_toLeft)); + mToLeftType = d.type; + mToLeftValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_toTop)); + mToTopType = d.type; + mToTopValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_toRight)); + mToRightType = d.type; + mToRightValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ClipRectAnimation_toBottom)); + mToBottomType = d.type; + mToBottomValue = d.value; + + a.recycle(); + } /** * Constructor to use when building a ClipRectAnimation from code @@ -39,8 +119,15 @@ public class ClipRectAnimation extends Animation { if (fromClip == null || toClip == null) { throw new RuntimeException("Expected non-null animation clip rects"); } - mFromRect.set(fromClip); - mToRect.set(toClip); + mFromLeftValue = fromClip.left; + mFromTopValue = fromClip.top; + mFromRightValue= fromClip.right; + mFromBottomValue = fromClip.bottom; + + mToLeftValue = toClip.left; + mToTopValue = toClip.top; + mToRightValue= toClip.right; + mToBottomValue = toClip.bottom; } /** @@ -48,8 +135,7 @@ public class ClipRectAnimation extends Animation { */ public ClipRectAnimation(int fromL, int fromT, int fromR, int fromB, int toL, int toT, int toR, int toB) { - mFromRect.set(fromL, fromT, fromR, fromB); - mToRect.set(toL, toT, toR, toB); + this(new Rect(fromL, fromT, fromR, fromB), new Rect(toL, toT, toR, toB)); } @Override @@ -65,4 +151,17 @@ public class ClipRectAnimation extends Animation { public boolean willChangeTransformationMatrix() { return false; } + + @Override + public void initialize(int width, int height, int parentWidth, int parentHeight) { + super.initialize(width, height, parentWidth, parentHeight); + mFromRect.set((int) resolveSize(mFromLeftType, mFromLeftValue, width, parentWidth), + (int) resolveSize(mFromTopType, mFromTopValue, height, parentHeight), + (int) resolveSize(mFromRightType, mFromRightValue, width, parentWidth), + (int) resolveSize(mFromBottomType, mFromBottomValue, height, parentHeight)); + mToRect.set((int) resolveSize(mToLeftType, mToLeftValue, width, parentWidth), + (int) resolveSize(mToTopType, mToTopValue, height, parentHeight), + (int) resolveSize(mToRightType, mToRightValue, width, parentWidth), + (int) resolveSize(mToBottomType, mToBottomValue, height, parentHeight)); + } } diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java index 26974545..4b24a71c 100644 --- a/android/view/autofill/AutofillManager.java +++ b/android/view/autofill/AutofillManager.java @@ -53,6 +53,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -168,7 +170,6 @@ public final class AutofillManager { public static final String EXTRA_CLIENT_STATE = "android.view.autofill.extra.CLIENT_STATE"; - /** @hide */ public static final String EXTRA_RESTORE_SESSION_TOKEN = "android.view.autofill.extra.RESTORE_SESSION_TOKEN"; @@ -258,6 +259,12 @@ public final class AutofillManager { public static final int STATE_DISABLED_BY_SERVICE = 4; /** + * Timeout in ms for calls to the field classification service. + * @hide + */ + public static final int FC_SERVICE_TIMEOUT = 5000; + + /** * Makes an authentication id from a request id and a dataset id. * * @param requestId The request id. @@ -340,6 +347,10 @@ public final class AutofillManager { @GuardedBy("mLock") @Nullable private AutofillId mSaveTriggerId; + /** set to true when onInvisibleForAutofill is called, used by onAuthenticationResult */ + @GuardedBy("mLock") + private boolean mOnInvisibleCalled; + /** If set, session is commited when the activity is finished; otherwise session is canceled. */ @GuardedBy("mLock") private boolean mSaveOnFinish; @@ -396,6 +407,11 @@ public final class AutofillManager { boolean isVisibleForAutofill(); /** + * Client might disable enter/exit event e.g. when activity is paused. + */ + boolean isDisablingEnterExitEventForAutofill(); + + /** * Finds views by traversing the hierarchies of the client. * * @param viewIds The autofill ids of the views to find @@ -498,6 +514,19 @@ public final class AutofillManager { } /** + * Called once the client becomes invisible. + * + * @see AutofillClient#isVisibleForAutofill() + * + * {@hide} + */ + public void onInvisibleForAutofill() { + synchronized (mLock) { + mOnInvisibleCalled = true; + } + } + + /** * Save state before activity lifecycle * * @param outState Place to store the state @@ -622,21 +651,45 @@ public final class AutofillManager { return false; } + private boolean isClientVisibleForAutofillLocked() { + final AutofillClient client = getClient(); + return client != null && client.isVisibleForAutofill(); + } + + private boolean isClientDisablingEnterExitEvent() { + final AutofillClient client = getClient(); + return client != null && client.isDisablingEnterExitEventForAutofill(); + } + private void notifyViewEntered(@NonNull View view, int flags) { if (!hasAutofillFeature()) { return; } - AutofillCallback callback = null; + AutofillCallback callback; synchronized (mLock) { - if (shouldIgnoreViewEnteredLocked(view, flags)) return; + callback = notifyViewEnteredLocked(view, flags); + } - ensureServiceClientAddedIfNeededLocked(); + if (callback != null) { + mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE); + } + } - if (!mEnabled) { - if (mCallback != null) { - callback = mCallback; - } - } else { + /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */ + private AutofillCallback notifyViewEnteredLocked(@NonNull View view, int flags) { + if (shouldIgnoreViewEnteredLocked(view, flags)) return null; + + AutofillCallback callback = null; + + ensureServiceClientAddedIfNeededLocked(); + + if (!mEnabled) { + if (mCallback != null) { + callback = mCallback; + } + } else { + // don't notify entered when Activity is already in background + if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view); final AutofillValue value = view.getAutofillValue(); @@ -649,10 +702,7 @@ public final class AutofillManager { } } } - - if (callback != null) { - mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE); - } + return callback; } /** @@ -665,9 +715,16 @@ public final class AutofillManager { return; } synchronized (mLock) { - ensureServiceClientAddedIfNeededLocked(); + notifyViewExitedLocked(view); + } + } - if (mEnabled && isActiveLocked()) { + void notifyViewExitedLocked(@NonNull View view) { + ensureServiceClientAddedIfNeededLocked(); + + if (mEnabled && isActiveLocked()) { + // dont notify exited when Activity is already in background + if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view); // Update focus on existing session. @@ -718,7 +775,7 @@ public final class AutofillManager { } } if (mTrackedViews != null) { - mTrackedViews.notifyViewVisibilityChanged(id, isVisible); + mTrackedViews.notifyViewVisibilityChangedLocked(id, isVisible); } } } @@ -751,17 +808,32 @@ public final class AutofillManager { if (!hasAutofillFeature()) { return; } - AutofillCallback callback = null; + AutofillCallback callback; synchronized (mLock) { - if (shouldIgnoreViewEnteredLocked(view, flags)) return; + callback = notifyViewEnteredLocked(view, virtualId, bounds, flags); + } - ensureServiceClientAddedIfNeededLocked(); + if (callback != null) { + callback.onAutofillEvent(view, virtualId, + AutofillCallback.EVENT_INPUT_UNAVAILABLE); + } + } - if (!mEnabled) { - if (mCallback != null) { - callback = mCallback; - } - } else { + /** Returns AutofillCallback if need fire EVENT_INPUT_UNAVAILABLE */ + private AutofillCallback notifyViewEnteredLocked(View view, int virtualId, Rect bounds, + int flags) { + AutofillCallback callback = null; + if (shouldIgnoreViewEnteredLocked(view, flags)) return callback; + + ensureServiceClientAddedIfNeededLocked(); + + if (!mEnabled) { + if (mCallback != null) { + callback = mCallback; + } + } else { + // don't notify entered when Activity is already in background + if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view, virtualId); if (!isActiveLocked()) { @@ -773,11 +845,7 @@ public final class AutofillManager { } } } - - if (callback != null) { - callback.onAutofillEvent(view, virtualId, - AutofillCallback.EVENT_INPUT_UNAVAILABLE); - } + return callback; } /** @@ -791,9 +859,16 @@ public final class AutofillManager { return; } synchronized (mLock) { - ensureServiceClientAddedIfNeededLocked(); + notifyViewExitedLocked(view, virtualId); + } + } - if (mEnabled && isActiveLocked()) { + private void notifyViewExitedLocked(@NonNull View view, int virtualId) { + ensureServiceClientAddedIfNeededLocked(); + + if (mEnabled && isActiveLocked()) { + // don't notify exited when Activity is already in background + if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view, virtualId); // Update focus on existing session. @@ -1027,7 +1102,9 @@ public final class AutofillManager { * Gets the user data used for * <a href="AutofillService.html#FieldClassification">field classification</a>. * - * <p><b>Note:</b> This method should only be called by an app providing an autofill service. + * <p><b>Note:</b> This method should only be called by an app providing an autofill service, + * and it's ignored if the caller currently doesn't have an enabled autofill service for + * the user. * * @return value previously set by {@link #setUserData(UserData)} or {@code null} if it was * reset or if the caller currently does not have an enabled autofill service for the user. @@ -1079,6 +1156,47 @@ public final class AutofillManager { } /** + * Gets the name of the default algorithm used for + * <a href="AutofillService.html#FieldClassification">field classification</a>. + * + * <p>The default algorithm is used when the algorithm on {@link UserData} is invalid or not + * set. + * + * <p><b>Note:</b> This method should only be called by an app providing an autofill service, + * and it's ignored if the caller currently doesn't have an enabled autofill service for + * the user. + */ + @Nullable + public String getDefaultFieldClassificationAlgorithm() { + try { + return mService.getDefaultFieldClassificationAlgorithm(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return null; + } + } + + /** + * Gets the name of all algorithms currently available for + * <a href="AutofillService.html#FieldClassification">field classification</a>. + * + * <p><b>Note:</b> This method should only be called by an app providing an autofill service, + * and it returns an empty list if the caller currently doesn't have an enabled autofill service + * for the user. + */ + @NonNull + public List<String> getAvailableFieldClassificationAlgorithms() { + final String[] algorithms; + try { + algorithms = mService.getAvailableFieldClassificationAlgorithms(); + return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return null; + } + } + + /** * Returns {@code true} if autofill is supported by the current device and * is supported for this user. * @@ -1109,7 +1227,7 @@ public final class AutofillManager { } /** @hide */ - public void onAuthenticationResult(int authenticationId, Intent data) { + public void onAuthenticationResult(int authenticationId, Intent data, View focusView) { if (!hasAutofillFeature()) { return; } @@ -1121,9 +1239,24 @@ public final class AutofillManager { if (sDebug) Log.d(TAG, "onAuthenticationResult(): d=" + data); synchronized (mLock) { - if (!isActiveLocked() || data == null) { + if (!isActiveLocked()) { + return; + } + // If authenticate activity closes itself during onCreate(), there is no onStop/onStart + // of app activity. We enforce enter event to re-show fill ui in such case. + // CTS example: + // LoginActivityTest#testDatasetAuthTwoFieldsUserCancelsFirstAttempt + // LoginActivityTest#testFillResponseAuthBothFieldsUserCancelsFirstAttempt + if (!mOnInvisibleCalled && focusView != null + && focusView.canNotifyAutofillEnterExitEvent()) { + notifyViewExitedLocked(focusView); + notifyViewEnteredLocked(focusView, 0); + } + if (data == null) { + // data is set to null when result is not RESULT_OK return; } + final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT); final Bundle responseData = new Bundle(); responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result); @@ -1356,6 +1489,9 @@ public final class AutofillManager { if (sessionId == mSessionId) { final AutofillClient client = getClient(); if (client != null) { + // clear mOnInvisibleCalled and we will see if receive onInvisibleForAutofill() + // before onAuthenticationResult() + mOnInvisibleCalled = false; client.autofillCallbackAuthenticate(authenticationId, intent, fillInIntent); } } @@ -1721,6 +1857,7 @@ public final class AutofillManager { 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); + pw.print(pfx); pw.print("onInvisibleCalled "); pw.println(mOnInvisibleCalled); pw.print(pfx); pw.print("last autofilled data: "); pw.println(mLastAutofilledData); pw.print(pfx); pw.print("tracked views: "); if (mTrackedViews == null) { @@ -1891,15 +2028,13 @@ public final class AutofillManager { * @param id the id of the view/virtual view whose visibility changed. * @param isVisible visible if the view is visible in the view hierarchy. */ - void notifyViewVisibilityChanged(@NonNull AutofillId id, boolean isVisible) { - AutofillClient client = getClient(); - + void notifyViewVisibilityChangedLocked(@NonNull AutofillId id, boolean isVisible) { if (sDebug) { Log.d(TAG, "notifyViewVisibilityChanged(): id=" + id + " isVisible=" + isVisible); } - if (client != null && client.isVisibleForAutofill()) { + if (isClientVisibleForAutofillLocked()) { if (isVisible) { if (isInSet(mInvisibleTrackedIds, id)) { mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id); diff --git a/android/view/autofill/AutofillPopupWindow.java b/android/view/autofill/AutofillPopupWindow.java index 5cba21e3..e80fdd93 100644 --- a/android/view/autofill/AutofillPopupWindow.java +++ b/android/view/autofill/AutofillPopupWindow.java @@ -78,8 +78,10 @@ public class AutofillPopupWindow extends PopupWindow { public AutofillPopupWindow(@NonNull IAutofillWindowPresenter presenter) { mWindowPresenter = new WindowPresenter(presenter); + setTouchModal(false); setOutsideTouchable(true); - setInputMethodMode(INPUT_METHOD_NEEDED); + setInputMethodMode(INPUT_METHOD_NOT_NEEDED); + setFocusable(true); } @Override diff --git a/android/view/inputmethod/ExtractedText.java b/android/view/inputmethod/ExtractedText.java index 003f221d..1eb300ea 100644 --- a/android/view/inputmethod/ExtractedText.java +++ b/android/view/inputmethod/ExtractedText.java @@ -29,6 +29,8 @@ import android.text.TextUtils; public class ExtractedText implements Parcelable { /** * The text that has been extracted. + * + * @see android.widget.TextView#getText() */ public CharSequence text; @@ -88,6 +90,8 @@ public class ExtractedText implements Parcelable { /** * The hint that has been extracted. + * + * @see android.widget.TextView#getHint() */ public CharSequence hint; diff --git a/android/view/inputmethod/InputConnection.java b/android/view/inputmethod/InputConnection.java index 57f9895f..eba91763 100644 --- a/android/view/inputmethod/InputConnection.java +++ b/android/view/inputmethod/InputConnection.java @@ -1,17 +1,17 @@ /* - * Copyright (C) 2007-2008 The Android Open Source Project + * Copyright (C) 2007 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.inputmethod; @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.inputmethodservice.InputMethodService; import android.os.Bundle; import android.os.Handler; +import android.os.LocaleList; import android.view.KeyCharacterMap; import android.view.KeyEvent; @@ -131,13 +132,13 @@ public interface InputConnection { * spans. <strong>Editor authors</strong>: you should strive to * send text with styles if possible, but it is not required. */ - static final int GET_TEXT_WITH_STYLES = 0x0001; + int GET_TEXT_WITH_STYLES = 0x0001; /** * Flag for use with {@link #getExtractedText} to indicate you * would like to receive updates when the extracted text changes. */ - public static final int GET_EXTRACTED_TEXT_MONITOR = 0x0001; + int GET_EXTRACTED_TEXT_MONITOR = 0x0001; /** * Get <var>n</var> characters of text before the current cursor @@ -176,7 +177,7 @@ public interface InputConnection { * @return the text before the cursor position; the length of the * returned text might be less than <var>n</var>. */ - public CharSequence getTextBeforeCursor(int n, int flags); + CharSequence getTextBeforeCursor(int n, int flags); /** * Get <var>n</var> characters of text after the current cursor @@ -215,7 +216,7 @@ public interface InputConnection { * @return the text after the cursor position; the length of the * returned text might be less than <var>n</var>. */ - public CharSequence getTextAfterCursor(int n, int flags); + CharSequence getTextAfterCursor(int n, int flags); /** * Gets the selected text, if any. @@ -249,7 +250,7 @@ public interface InputConnection { * later, returns false when the target application does not implement * this method. */ - public CharSequence getSelectedText(int flags); + CharSequence getSelectedText(int flags); /** * Retrieve the current capitalization mode in effect at the @@ -279,7 +280,7 @@ public interface InputConnection { * @return the caps mode flags that are in effect at the current * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}. */ - public int getCursorCapsMode(int reqModes); + int getCursorCapsMode(int reqModes); /** * Retrieve the current text in the input connection's editor, and @@ -314,8 +315,7 @@ public interface InputConnection { * longer valid of the editor can't comply with the request for * some reason. */ - public ExtractedText getExtractedText(ExtractedTextRequest request, - int flags); + ExtractedText getExtractedText(ExtractedTextRequest request, int flags); /** * Delete <var>beforeLength</var> characters of text before the @@ -342,8 +342,8 @@ public interface InputConnection { * delete more characters than are in the editor, as that may have * ill effects on the application. Calling this method will cause * the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on your service after the batch input is over.</p> + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on your service after the batch input is over.</p> * * <p><strong>Editor authors:</strong> please be careful of race * conditions in implementing this call. An IME can make a change @@ -369,7 +369,7 @@ public interface InputConnection { * that range. * @return true on success, false if the input connection is no longer valid. */ - public boolean deleteSurroundingText(int beforeLength, int afterLength); + boolean deleteSurroundingText(int beforeLength, int afterLength); /** * A variant of {@link #deleteSurroundingText(int, int)}. Major differences are: @@ -397,7 +397,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer valid. Returns * {@code false} when the target application does not implement this method. */ - public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength); + boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength); /** * Replace the currently composing text with the given text, and @@ -416,8 +416,8 @@ public interface InputConnection { * <p>This is usually called by IMEs to add or remove or change * characters in the composing span. Calling this method will * cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over.</p> + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over.</p> * * <p><strong>Editor authors:</strong> please keep in mind the * text may be very similar or completely different than what was @@ -455,7 +455,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean setComposingText(CharSequence text, int newCursorPosition); + boolean setComposingText(CharSequence text, int newCursorPosition); /** * Mark a certain region of text as composing text. If there was a @@ -474,8 +474,8 @@ public interface InputConnection { * <p>Since this does not change the contents of the text, editors should not call * {@link InputMethodManager#updateSelection(View, int, int, int, int)} and * IMEs should not receive - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}. - * </p> + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)}.</p> * * <p>This has no impact on the cursor/selection position. It may * result in the cursor being anywhere inside or outside the @@ -488,7 +488,7 @@ public interface InputConnection { * valid. In {@link android.os.Build.VERSION_CODES#N} and later, false is returned when the * target application does not implement this method. */ - public boolean setComposingRegion(int start, int end); + boolean setComposingRegion(int start, int end); /** * Have the text editor finish whatever composing text is @@ -507,7 +507,7 @@ public interface InputConnection { * @return true on success, false if the input connection * is no longer valid. */ - public boolean finishComposingText(); + boolean finishComposingText(); /** * Commit text to the text box and set the new cursor position. @@ -522,8 +522,8 @@ public interface InputConnection { * then {@link #finishComposingText()}.</p> * * <p>Calling this method will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -543,7 +543,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean commitText(CharSequence text, int newCursorPosition); + boolean commitText(CharSequence text, int newCursorPosition); /** * Commit a completion the user has selected from the possible ones @@ -569,8 +569,8 @@ public interface InputConnection { * * <p>Calling this method (with a valid {@link CompletionInfo} object) * will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -581,15 +581,15 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean commitCompletion(CompletionInfo text); + boolean commitCompletion(CompletionInfo text); /** * Commit a correction automatically performed on the raw user's input. A * typical example would be to correct typos using a dictionary. * * <p>Calling this method will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -601,7 +601,7 @@ public interface InputConnection { * In {@link android.os.Build.VERSION_CODES#N} and later, returns false * when the target application does not implement this method. */ - public boolean commitCorrection(CorrectionInfo correctionInfo); + boolean commitCorrection(CorrectionInfo correctionInfo); /** * Set the selection of the text editor. To set the cursor @@ -609,8 +609,8 @@ public interface InputConnection { * * <p>Since this moves the cursor, calling this method will cause * the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -628,7 +628,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean setSelection(int start, int end); + boolean setSelection(int start, int end); /** * Have the editor perform an action it has said it can do. @@ -642,7 +642,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean performEditorAction(int editorAction); + boolean performEditorAction(int editorAction); /** * Perform a context menu action on the field. The given id may be one of: @@ -652,7 +652,7 @@ public interface InputConnection { * {@link android.R.id#paste}, {@link android.R.id#copyUrl}, * or {@link android.R.id#switchInputMethod} */ - public boolean performContextMenuAction(int id); + boolean performContextMenuAction(int id); /** * Tell the editor that you are starting a batch of editor @@ -662,8 +662,8 @@ public interface InputConnection { * * <p><strong>IME authors:</strong> use this to avoid getting * calls to - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * corresponding to intermediate state. Also, use this to avoid + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} corresponding to intermediate state. Also, use this to avoid * flickers that may arise from displaying intermediate state. Be * sure to call {@link #endBatchEdit} for each call to this, or * you may block updates in the editor.</p> @@ -678,7 +678,7 @@ public interface InputConnection { * this method starts a batch edit, that means it will always return true * unless the input connection is no longer valid. */ - public boolean beginBatchEdit(); + boolean beginBatchEdit(); /** * Tell the editor that you are done with a batch edit previously @@ -696,7 +696,7 @@ public interface InputConnection { * the latest one (in other words, if the nesting count is > 0), false * otherwise or if the input connection is no longer valid. */ - public boolean endBatchEdit(); + boolean endBatchEdit(); /** * Send a key event to the process that is currently attached @@ -734,7 +734,7 @@ public interface InputConnection { * @see KeyCharacterMap#PREDICTIVE * @see KeyCharacterMap#ALPHA */ - public boolean sendKeyEvent(KeyEvent event); + boolean sendKeyEvent(KeyEvent event); /** * Clear the given meta key pressed states in the given input @@ -749,7 +749,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean clearMetaKeyStates(int states); + boolean clearMetaKeyStates(int states); /** * Called back when the connected IME switches between fullscreen and normal modes. @@ -766,7 +766,7 @@ public interface InputConnection { * devices. * @see InputMethodManager#isFullscreenMode() */ - public boolean reportFullscreenMode(boolean enabled); + boolean reportFullscreenMode(boolean enabled); /** * API to send private commands from an input method to its @@ -786,7 +786,7 @@ public interface InputConnection { * associated editor understood it), false if the input connection is no longer * valid. */ - public boolean performPrivateCommand(String action, Bundle data); + boolean performPrivateCommand(String action, Bundle data); /** * The editor is requested to call @@ -794,7 +794,7 @@ public interface InputConnection { * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be * used together with {@link #CURSOR_UPDATE_MONITOR}. */ - public static final int CURSOR_UPDATE_IMMEDIATE = 1 << 0; + int CURSOR_UPDATE_IMMEDIATE = 1 << 0; /** * The editor is requested to call @@ -805,7 +805,7 @@ public interface InputConnection { * This flag can be used together with {@link #CURSOR_UPDATE_IMMEDIATE}. * </p> */ - public static final int CURSOR_UPDATE_MONITOR = 1 << 1; + int CURSOR_UPDATE_MONITOR = 1 << 1; /** * Called by the input method to ask the editor for calling back @@ -821,7 +821,7 @@ public interface InputConnection { * In {@link android.os.Build.VERSION_CODES#N} and later, returns {@code false} also when the * target application does not implement this method. */ - public boolean requestCursorUpdates(int cursorUpdateMode); + boolean requestCursorUpdates(int cursorUpdateMode); /** * Called by the {@link InputMethodManager} to enable application developers to specify a @@ -832,7 +832,7 @@ public interface InputConnection { * * @return {@code null} to use the default {@link Handler}. */ - public Handler getHandler(); + Handler getHandler(); /** * Called by the system up to only once to notify that the system is about to invalidate @@ -846,7 +846,7 @@ public interface InputConnection { * * <p>Note: This does nothing when called from input methods.</p> */ - public void closeConnection(); + void closeConnection(); /** * When this flag is used, the editor will be able to request read access to the content URI @@ -863,7 +863,7 @@ public interface InputConnection { * client is able to request a temporary read-only access even after the current IME is switched * to any other IME as long as the client keeps {@link InputContentInfo} object.</p> **/ - public static int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = + int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; // 0x00000001 /** @@ -897,6 +897,39 @@ public interface InputConnection { * @return {@code true} if this request is accepted by the application, whether the request * is already handled or still being handled in background, {@code false} otherwise. */ - public boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags, + boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags, @Nullable Bundle opts); + + /** + * Called by the input method to tell a hint about the locales of text to be committed. + * + * <p>This is just a hint for editor authors (and the system) to choose better options when + * they have to disambiguate languages, like editor authors can do for input methods with + * {@link EditorInfo#hintLocales}.</p> + * + * <p>The language hint provided by this callback should have higher priority than + * {@link InputMethodSubtype#getLanguageTag()}, which cannot be updated dynamically.</p> + * + * <p>Note that in general it is discouraged for input method to specify + * {@link android.text.style.LocaleSpan} when inputting text, mainly because of application + * compatibility concerns.</p> + * <ul> + * <li>When an existing text that already has {@link android.text.style.LocaleSpan} is being + * modified by both the input method and application, there is no reliable and easy way to + * keep track of who modified {@link android.text.style.LocaleSpan}. For instance, if the + * text was updated by JavaScript, it it highly likely that span information is completely + * removed, while some input method attempts to preserve spans if possible.</li> + * <li>There is no clear semantics regarding whether {@link android.text.style.LocaleSpan} + * means a weak (ignorable) hint or a strong hint. This becomes more problematic when + * multiple {@link android.text.style.LocaleSpan} instances are specified to the same + * text region, especially when those spans are conflicting.</li> + * </ul> + * @param languageHint list of languages sorted by the priority and/or probability + */ + default void reportLanguageHint(@NonNull LocaleList languageHint) { + // Intentionally empty. + // + // We need to have *some* default implementation for the source compatibility. + // See Bug 72127682 for details. + } } diff --git a/android/view/inputmethod/InputConnectionWrapper.java b/android/view/inputmethod/InputConnectionWrapper.java index 317730ca..cbe6856b 100644 --- a/android/view/inputmethod/InputConnectionWrapper.java +++ b/android/view/inputmethod/InputConnectionWrapper.java @@ -1,23 +1,25 @@ /* - * Copyright (C) 2007-2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. + * 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.inputmethod; +import android.annotation.NonNull; import android.os.Bundle; import android.os.Handler; +import android.os.LocaleList; import android.view.KeyEvent; /** @@ -74,6 +76,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getTextBeforeCursor(int n, int flags) { return mTarget.getTextBeforeCursor(n, flags); } @@ -82,6 +85,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getTextAfterCursor(int n, int flags) { return mTarget.getTextAfterCursor(n, flags); } @@ -90,6 +94,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getSelectedText(int flags) { return mTarget.getSelectedText(flags); } @@ -98,6 +103,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public int getCursorCapsMode(int reqModes) { return mTarget.getCursorCapsMode(reqModes); } @@ -106,6 +112,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { return mTarget.getExtractedText(request, flags); } @@ -114,6 +121,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { return mTarget.deleteSurroundingTextInCodePoints(beforeLength, afterLength); } @@ -122,6 +130,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean deleteSurroundingText(int beforeLength, int afterLength) { return mTarget.deleteSurroundingText(beforeLength, afterLength); } @@ -130,6 +139,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setComposingText(CharSequence text, int newCursorPosition) { return mTarget.setComposingText(text, newCursorPosition); } @@ -138,6 +148,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setComposingRegion(int start, int end) { return mTarget.setComposingRegion(start, end); } @@ -146,6 +157,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean finishComposingText() { return mTarget.finishComposingText(); } @@ -154,6 +166,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitText(CharSequence text, int newCursorPosition) { return mTarget.commitText(text, newCursorPosition); } @@ -162,6 +175,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitCompletion(CompletionInfo text) { return mTarget.commitCompletion(text); } @@ -170,6 +184,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitCorrection(CorrectionInfo correctionInfo) { return mTarget.commitCorrection(correctionInfo); } @@ -178,6 +193,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setSelection(int start, int end) { return mTarget.setSelection(start, end); } @@ -186,6 +202,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performEditorAction(int editorAction) { return mTarget.performEditorAction(editorAction); } @@ -194,6 +211,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performContextMenuAction(int id) { return mTarget.performContextMenuAction(id); } @@ -202,6 +220,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean beginBatchEdit() { return mTarget.beginBatchEdit(); } @@ -210,6 +229,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean endBatchEdit() { return mTarget.endBatchEdit(); } @@ -218,6 +238,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean sendKeyEvent(KeyEvent event) { return mTarget.sendKeyEvent(event); } @@ -226,6 +247,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean clearMetaKeyStates(int states) { return mTarget.clearMetaKeyStates(states); } @@ -234,6 +256,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean reportFullscreenMode(boolean enabled) { return mTarget.reportFullscreenMode(enabled); } @@ -242,6 +265,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performPrivateCommand(String action, Bundle data) { return mTarget.performPrivateCommand(action, data); } @@ -250,6 +274,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean requestCursorUpdates(int cursorUpdateMode) { return mTarget.requestCursorUpdates(cursorUpdateMode); } @@ -258,6 +283,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public Handler getHandler() { return mTarget.getHandler(); } @@ -266,6 +292,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public void closeConnection() { mTarget.closeConnection(); } @@ -274,7 +301,17 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { return mTarget.commitContent(inputContentInfo, flags, opts); } + + /** + * {@inheritDoc} + * @throws NullPointerException if the target is {@code null}. + */ + @Override + public void reportLanguageHint(@NonNull LocaleList languageHint) { + mTarget.reportLanguageHint(languageHint); + } } diff --git a/android/view/inputmethod/InputMethodManager.java b/android/view/inputmethod/InputMethodManager.java index 80d7b6b7..7db5c320 100644 --- a/android/view/inputmethod/InputMethodManager.java +++ b/android/view/inputmethod/InputMethodManager.java @@ -337,20 +337,23 @@ public final class InputMethodManager { int mCursorCandEnd; /** - * Represents an invalid action notification sequence number. {@link InputMethodManagerService} - * always issues a positive integer for action notification sequence numbers. Thus -1 is - * guaranteed to be different from any valid sequence number. + * Represents an invalid action notification sequence number. + * {@link com.android.server.InputMethodManagerService} always issues a positive integer for + * action notification sequence numbers. Thus {@code -1} is guaranteed to be different from any + * valid sequence number. */ private static final int NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER = -1; /** - * The next sequence number that is to be sent to {@link InputMethodManagerService} via + * The next sequence number that is to be sent to + * {@link com.android.server.InputMethodManagerService} via * {@link IInputMethodManager#notifyUserAction(int)} at once when a user action is observed. */ private int mNextUserActionNotificationSequenceNumber = NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER; /** - * The last sequence number that is already sent to {@link InputMethodManagerService}. + * The last sequence number that is already sent to + * {@link com.android.server.InputMethodManagerService}. */ private int mLastSentUserActionNotificationSequenceNumber = NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER; @@ -1079,15 +1082,15 @@ public final class InputMethodManager { } /** - * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft - * input window should only be hidden if it was not explicitly shown + * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)} + * to indicate that the soft input window should only be hidden if it was not explicitly shown * by the user. */ public static final int HIDE_IMPLICIT_ONLY = 0x0001; /** - * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft - * input window should normally be hidden, unless it was originally + * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestShowSelf(int)} + * to indicate that the soft input window should normally be hidden, unless it was originally * shown with {@link #SHOW_FORCED}. */ public static final int HIDE_NOT_ALWAYS = 0x0002; @@ -1255,12 +1258,7 @@ public final class InputMethodManager { // The view is running on a different thread than our own, so // we need to reschedule our work for over there. if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread"); - vh.post(new Runnable() { - @Override - public void run() { - startInputInner(startInputReason, null, 0, 0, 0); - } - }); + vh.post(() -> startInputInner(startInputReason, null, 0, 0, 0)); return false; } @@ -1871,9 +1869,9 @@ public final class InputMethodManager { * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #HIDE_IMPLICIT_ONLY}, * {@link #HIDE_NOT_ALWAYS} bit set. - * @deprecated Use {@link InputMethodService#hideSoftInputFromInputMethod(int)} - * instead. This method was intended for IME developers who should be accessing APIs through - * the service. APIs in this class are intended for app developers interacting with the IME. + * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was + * intended for IME developers who should be accessing APIs through the service. APIs in this + * class are intended for app developers interacting with the IME. */ @Deprecated public void hideSoftInputFromInputMethod(IBinder token, int flags) { @@ -1903,9 +1901,9 @@ public final class InputMethodManager { * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #SHOW_IMPLICIT} or * {@link #SHOW_FORCED} bit set. - * @deprecated Use {@link InputMethodService#showSoftInputFromInputMethod(int)} - * instead. This method was intended for IME developers who should be accessing APIs through - * the service. APIs in this class are intended for app developers interacting with the IME. + * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was + * intended for IME developers who should be accessing APIs through the service. APIs in this + * class are intended for app developers interacting with the IME. */ @Deprecated public void showSoftInputFromInputMethod(IBinder token, int flags) { @@ -2429,8 +2427,8 @@ public final class InputMethodManager { * Allow the receiver of {@link InputContentInfo} to obtain a temporary read-only access * permission to the content. * - * <p>See {@link android.inputmethodservice.InputMethodService#exposeContent(InputContentInfo, EditorInfo)} - * for details.</p> + * <p>See {@link android.inputmethodservice.InputMethodService#exposeContent(InputContentInfo, + * InputConnection)} for details.</p> * * @param token Supplies the identifying token given to an input method when it was started, * which allows it to perform this operation on itself. diff --git a/android/view/textclassifier/EntityConfidence.java b/android/view/textclassifier/EntityConfidence.java index 19660d95..69a59a5b 100644 --- a/android/view/textclassifier/EntityConfidence.java +++ b/android/view/textclassifier/EntityConfidence.java @@ -18,6 +18,8 @@ package android.view.textclassifier; import android.annotation.FloatRange; import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; import android.util.ArrayMap; import com.android.internal.util.Preconditions; @@ -30,17 +32,16 @@ import java.util.Map; /** * Helper object for setting and getting entity scores for classified text. * - * @param <T> the entity type. * @hide */ -final class EntityConfidence<T> { +final class EntityConfidence implements Parcelable { - private final ArrayMap<T, Float> mEntityConfidence = new ArrayMap<>(); - private final ArrayList<T> mSortedEntities = new ArrayList<>(); + private final ArrayMap<String, Float> mEntityConfidence = new ArrayMap<>(); + private final ArrayList<String> mSortedEntities = new ArrayList<>(); EntityConfidence() {} - EntityConfidence(@NonNull EntityConfidence<T> source) { + EntityConfidence(@NonNull EntityConfidence source) { Preconditions.checkNotNull(source); mEntityConfidence.putAll(source.mEntityConfidence); mSortedEntities.addAll(source.mSortedEntities); @@ -54,24 +55,16 @@ final class EntityConfidence<T> { * @param source a map from entity to a confidence value in the range 0 (low confidence) to * 1 (high confidence). */ - EntityConfidence(@NonNull Map<T, Float> source) { + EntityConfidence(@NonNull Map<String, Float> source) { Preconditions.checkNotNull(source); // Prune non-existent entities and clamp to 1. mEntityConfidence.ensureCapacity(source.size()); - for (Map.Entry<T, Float> it : source.entrySet()) { + for (Map.Entry<String, Float> it : source.entrySet()) { if (it.getValue() <= 0) continue; mEntityConfidence.put(it.getKey(), Math.min(1, it.getValue())); } - - // Create a list of entities sorted by decreasing confidence for getEntities(). - mSortedEntities.ensureCapacity(mEntityConfidence.size()); - mSortedEntities.addAll(mEntityConfidence.keySet()); - mSortedEntities.sort((e1, e2) -> { - float score1 = mEntityConfidence.get(e1); - float score2 = mEntityConfidence.get(e2); - return Float.compare(score2, score1); - }); + resetSortedEntitiesFromMap(); } /** @@ -79,7 +72,7 @@ final class EntityConfidence<T> { * high confidence to low confidence. */ @NonNull - public List<T> getEntities() { + public List<String> getEntities() { return Collections.unmodifiableList(mSortedEntities); } @@ -89,7 +82,7 @@ final class EntityConfidence<T> { * classified text. */ @FloatRange(from = 0.0, to = 1.0) - public float getConfidenceScore(T entity) { + public float getConfidenceScore(String entity) { if (mEntityConfidence.containsKey(entity)) { return mEntityConfidence.get(entity); } @@ -100,4 +93,51 @@ final class EntityConfidence<T> { public String toString() { return mEntityConfidence.toString(); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mEntityConfidence.size()); + for (Map.Entry<String, Float> entry : mEntityConfidence.entrySet()) { + dest.writeString(entry.getKey()); + dest.writeFloat(entry.getValue()); + } + } + + public static final Parcelable.Creator<EntityConfidence> CREATOR = + new Parcelable.Creator<EntityConfidence>() { + @Override + public EntityConfidence createFromParcel(Parcel in) { + return new EntityConfidence(in); + } + + @Override + public EntityConfidence[] newArray(int size) { + return new EntityConfidence[size]; + } + }; + + private EntityConfidence(Parcel in) { + final int numEntities = in.readInt(); + mEntityConfidence.ensureCapacity(numEntities); + for (int i = 0; i < numEntities; ++i) { + mEntityConfidence.put(in.readString(), in.readFloat()); + } + resetSortedEntitiesFromMap(); + } + + private void resetSortedEntitiesFromMap() { + mSortedEntities.clear(); + mSortedEntities.ensureCapacity(mEntityConfidence.size()); + mSortedEntities.addAll(mEntityConfidence.keySet()); + mSortedEntities.sort((e1, e2) -> { + float score1 = mEntityConfidence.get(e1); + float score2 = mEntityConfidence.get(e2); + return Float.compare(score2, score1); + }); + } } diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java index 7ffbf635..7089677d 100644 --- a/android/view/textclassifier/TextClassification.java +++ b/android/view/textclassifier/TextClassification.java @@ -22,8 +22,13 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.LocaleList; +import android.os.Parcel; +import android.os.Parcelable; import android.util.ArrayMap; import android.view.View.OnClickListener; import android.view.textclassifier.TextClassifier.EntityType; @@ -52,7 +57,7 @@ import java.util.Map; * Button button = new Button(context); * button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null); * button.setText(classification.getLabel()); - * button.setOnClickListener(classification.getOnClickListener()); + * button.setOnClickListener(v -> context.startActivity(classification.getIntent())); * }</pre> * * <p>e.g. starting an action mode with menu items that can handle the classified text: @@ -90,7 +95,6 @@ import java.util.Map; * ... * }); * }</pre> - * */ public final class TextClassification { @@ -99,6 +103,10 @@ public final class TextClassification { */ static final TextClassification EMPTY = new TextClassification.Builder().build(); + // TODO(toki): investigate a way to derive this based on device properties. + private static final int MAX_PRIMARY_ICON_SIZE = 192; + private static final int MAX_SECONDARY_ICON_SIZE = 144; + @NonNull private final String mText; @Nullable private final Drawable mPrimaryIcon; @Nullable private final String mPrimaryLabel; @@ -107,8 +115,7 @@ public final class TextClassification { @NonNull private final List<Drawable> mSecondaryIcons; @NonNull private final List<String> mSecondaryLabels; @NonNull private final List<Intent> mSecondaryIntents; - @NonNull private final List<OnClickListener> mSecondaryOnClickListeners; - @NonNull private final EntityConfidence<String> mEntityConfidence; + @NonNull private final EntityConfidence mEntityConfidence; @NonNull private final String mSignature; private TextClassification( @@ -120,12 +127,10 @@ public final class TextClassification { @NonNull List<Drawable> secondaryIcons, @NonNull List<String> secondaryLabels, @NonNull List<Intent> secondaryIntents, - @NonNull List<OnClickListener> secondaryOnClickListeners, @NonNull Map<String, Float> entityConfidence, @NonNull String signature) { Preconditions.checkArgument(secondaryLabels.size() == secondaryIntents.size()); Preconditions.checkArgument(secondaryIcons.size() == secondaryIntents.size()); - Preconditions.checkArgument(secondaryOnClickListeners.size() == secondaryIntents.size()); mText = text; mPrimaryIcon = primaryIcon; mPrimaryLabel = primaryLabel; @@ -134,8 +139,7 @@ public final class TextClassification { mSecondaryIcons = secondaryIcons; mSecondaryLabels = secondaryLabels; mSecondaryIntents = secondaryIntents; - mSecondaryOnClickListeners = secondaryOnClickListeners; - mEntityConfidence = new EntityConfidence<>(entityConfidence); + mEntityConfidence = new EntityConfidence(entityConfidence); mSignature = signature; } @@ -186,7 +190,6 @@ public final class TextClassification { * @see #getSecondaryIntent(int) * @see #getSecondaryLabel(int) * @see #getSecondaryIcon(int) - * @see #getSecondaryOnClickListener(int) */ @IntRange(from = 0) public int getSecondaryActionsCount() { @@ -198,13 +201,10 @@ public final class TextClassification { * classified text. * * @param index Index of the action to get the icon for. - * * @throws IndexOutOfBoundsException if the specified index is out of range. - * * @see #getSecondaryActionsCount() for the number of actions available. * @see #getSecondaryIntent(int) * @see #getSecondaryLabel(int) - * @see #getSecondaryOnClickListener(int) * @see #getIcon() */ @Nullable @@ -228,13 +228,10 @@ public final class TextClassification { * 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 #getSecondaryActionsCount() * @see #getSecondaryIntent(int) * @see #getSecondaryIcon(int) - * @see #getSecondaryOnClickListener(int) * @see #getLabel() */ @Nullable @@ -257,13 +254,10 @@ public final class TextClassification { * Returns one of the <i>secondary</i> 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 #getSecondaryActionsCount() * @see #getSecondaryLabel(int) * @see #getSecondaryIcon(int) - * @see #getSecondaryOnClickListener(int) * @see #getIntent() */ @Nullable @@ -282,29 +276,10 @@ public final class TextClassification { } /** - * Returns one of the <i>secondary</i> 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 #getSecondaryActionsCount() - * @see #getSecondaryIntent(int) - * @see #getSecondaryLabel(int) - * @see #getSecondaryIcon(int) - * @see #getOnClickListener() - */ - @Nullable - public OnClickListener getSecondaryOnClickListener(int index) { - return mSecondaryOnClickListeners.get(index); - } - - /** * Returns the <i>primary</i> OnClickListener that may be triggered to act on the classified - * text. - * - * @see #getSecondaryOnClickListener(int) + * text. This field is not parcelable and will be null for all objects read from a parcel. + * Instead, call Context#startActivity(Intent) with the result of #getSecondaryIntent(int). + * Note that this may fail if the activity doesn't have permission to send the intent. */ @Nullable public OnClickListener getOnClickListener() { @@ -334,6 +309,42 @@ public final class TextClassification { mSignature); } + /** Helper for parceling via #ParcelableWrapper. */ + private void writeToParcel(Parcel dest, int flags) { + dest.writeString(mText); + final Bitmap primaryIconBitmap = drawableToBitmap(mPrimaryIcon, MAX_PRIMARY_ICON_SIZE); + dest.writeInt(primaryIconBitmap != null ? 1 : 0); + if (primaryIconBitmap != null) { + primaryIconBitmap.writeToParcel(dest, flags); + } + dest.writeString(mPrimaryLabel); + dest.writeInt(mPrimaryIntent != null ? 1 : 0); + if (mPrimaryIntent != null) { + mPrimaryIntent.writeToParcel(dest, flags); + } + // mPrimaryOnClickListener is not parcelable. + dest.writeTypedList(drawablesToBitmaps(mSecondaryIcons, MAX_SECONDARY_ICON_SIZE)); + dest.writeStringList(mSecondaryLabels); + dest.writeTypedList(mSecondaryIntents); + mEntityConfidence.writeToParcel(dest, flags); + dest.writeString(mSignature); + } + + /** Helper for unparceling via #ParcelableWrapper. */ + private TextClassification(Parcel in) { + mText = in.readString(); + mPrimaryIcon = in.readInt() == 0 + ? null : new BitmapDrawable(null, Bitmap.CREATOR.createFromParcel(in)); + mPrimaryLabel = in.readString(); + mPrimaryIntent = in.readInt() == 0 ? null : Intent.CREATOR.createFromParcel(in); + mPrimaryOnClickListener = null; // not parcelable + mSecondaryIcons = bitmapsToDrawables(in.createTypedArrayList(Bitmap.CREATOR)); + mSecondaryLabels = in.createStringArrayList(); + mSecondaryIntents = in.createTypedArrayList(Intent.CREATOR); + mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); + mSignature = in.readString(); + } + /** * Creates an OnClickListener that starts an activity with the specified intent. * @@ -349,6 +360,68 @@ public final class TextClassification { } /** + * Returns a Bitmap representation of the Drawable + * + * @param drawable The drawable to convert. + * @param maxDims The maximum edge length of the resulting bitmap (in pixels). + */ + @Nullable + private static Bitmap drawableToBitmap(@Nullable Drawable drawable, int maxDims) { + if (drawable == null) { + return null; + } + final int actualWidth = Math.max(1, drawable.getIntrinsicWidth()); + final int actualHeight = Math.max(1, drawable.getIntrinsicHeight()); + final double scaleWidth = ((double) maxDims) / actualWidth; + final double scaleHeight = ((double) maxDims) / actualHeight; + final double scale = Math.min(1.0, Math.min(scaleWidth, scaleHeight)); + final int width = (int) (actualWidth * scale); + final int height = (int) (actualHeight * scale); + if (drawable instanceof BitmapDrawable) { + final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; + if (actualWidth != width || actualHeight != height) { + return Bitmap.createScaledBitmap( + bitmapDrawable.getBitmap(), width, height, /*filter=*/false); + } else { + return bitmapDrawable.getBitmap(); + } + } else { + final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } + } + + /** + * Returns a list of drawables converted to Bitmaps + * + * @param drawables The drawables to convert. + * @param maxDims The maximum edge length of the resulting bitmaps (in pixels). + */ + private static List<Bitmap> drawablesToBitmaps(List<Drawable> drawables, int maxDims) { + final List<Bitmap> bitmaps = new ArrayList<>(drawables.size()); + for (Drawable drawable : drawables) { + bitmaps.add(drawableToBitmap(drawable, maxDims)); + } + return bitmaps; + } + + /** Returns a list of drawable wrappers for a list of bitmaps. */ + private static List<Drawable> bitmapsToDrawables(List<Bitmap> bitmaps) { + final List<Drawable> drawables = new ArrayList<>(bitmaps.size()); + for (Bitmap bitmap : bitmaps) { + if (bitmap != null) { + drawables.add(new BitmapDrawable(null, bitmap)); + } else { + drawables.add(null); + } + } + return drawables; + } + + /** * Builder for building {@link TextClassification} objects. * * <p>e.g. @@ -358,9 +431,9 @@ public final class TextClassification { * .setText(classifiedText) * .setEntityType(TextClassifier.TYPE_EMAIL, 0.9) * .setEntityType(TextClassifier.TYPE_OTHER, 0.1) - * .setPrimaryAction(intent, label, icon, onClickListener) - * .addSecondaryAction(intent1, label1, icon1, onClickListener1) - * .addSecondaryAction(intent2, label2, icon2, onClickListener2) + * .setPrimaryAction(intent, label, icon) + * .addSecondaryAction(intent1, label1, icon1) + * .addSecondaryAction(intent2, label2, icon2) * .build(); * }</pre> */ @@ -370,7 +443,6 @@ public final class TextClassification { @NonNull private final List<Drawable> mSecondaryIcons = new ArrayList<>(); @NonNull private final List<String> mSecondaryLabels = new ArrayList<>(); @NonNull private final List<Intent> mSecondaryIntents = new ArrayList<>(); - @NonNull private final List<OnClickListener> mSecondaryOnClickListeners = new ArrayList<>(); @NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>(); @Nullable Drawable mPrimaryIcon; @Nullable String mPrimaryLabel; @@ -413,16 +485,14 @@ public final class TextClassification { * <p><stong>Note: </stong> If all input parameters are set to null, this method will be a * no-op. * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * @see #setPrimaryAction(Intent, String, Drawable) */ public Builder addSecondaryAction( - @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon, - @Nullable OnClickListener onClickListener) { - if (intent != null || label != null || icon != null || onClickListener != null) { + @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon) { + if (intent != null || label != null || icon != null) { mSecondaryIntents.add(intent); mSecondaryLabels.add(label); mSecondaryIcons.add(icon); - mSecondaryOnClickListeners.add(onClickListener); } return this; } @@ -432,7 +502,6 @@ public final class TextClassification { */ public Builder clearSecondaryActions() { mSecondaryIntents.clear(); - mSecondaryOnClickListeners.clear(); mSecondaryLabels.clear(); mSecondaryIcons.clear(); return this; @@ -440,26 +509,23 @@ public final class TextClassification { /** * Sets the <i>primary</i> action that may be performed on the classified text. This is - * equivalent to calling {@code - * setIntent(intent).setLabel(label).setIcon(icon).setOnClickListener(onClickListener)}. + * equivalent to calling {@code setIntent(intent).setLabel(label).setIcon(icon)}. * * <p><strong>Note: </strong>If all input parameters are null, there will be no * <i>primary</i> action but there may still be <i>secondary</i> actions. * - * @see #addSecondaryAction(Intent, String, Drawable, OnClickListener) + * @see #addSecondaryAction(Intent, String, Drawable) */ public Builder setPrimaryAction( - @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon, - @Nullable OnClickListener onClickListener) { - return setIntent(intent).setLabel(label).setIcon(icon) - .setOnClickListener(onClickListener); + @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon) { + return setIntent(intent).setLabel(label).setIcon(icon); } /** * Sets the icon for the <i>primary</i> action that may be rendered on a widget used to act * on the classified text. * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * @see #setPrimaryAction(Intent, String, Drawable) */ public Builder setIcon(@Nullable Drawable icon) { mPrimaryIcon = icon; @@ -470,7 +536,7 @@ public final class TextClassification { * Sets the label for the <i>primary</i> action that may be rendered on a widget used to * act on the classified text. * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * @see #setPrimaryAction(Intent, String, Drawable) */ public Builder setLabel(@Nullable String label) { mPrimaryLabel = label; @@ -481,7 +547,7 @@ public final class TextClassification { * Sets the intent for the <i>primary</i> action that may be fired to act on the classified * text. * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * @see #setPrimaryAction(Intent, String, Drawable) */ public Builder setIntent(@Nullable Intent intent) { mPrimaryIntent = intent; @@ -490,9 +556,8 @@ public final class TextClassification { /** * Sets the OnClickListener for the <i>primary</i> action that may be triggered to act on - * the classified text. - * - * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) + * the classified text. This field is not parcelable and will always be null when the + * object is read from a parcel. */ public Builder setOnClickListener(@Nullable OnClickListener onClickListener) { mPrimaryOnClickListener = onClickListener; @@ -515,10 +580,8 @@ public final class TextClassification { public TextClassification build() { return new TextClassification( mText, - mPrimaryIcon, mPrimaryLabel, - mPrimaryIntent, mPrimaryOnClickListener, - mSecondaryIcons, mSecondaryLabels, - mSecondaryIntents, mSecondaryOnClickListeners, + mPrimaryIcon, mPrimaryLabel, mPrimaryIntent, mPrimaryOnClickListener, + mSecondaryIcons, mSecondaryLabels, mSecondaryIntents, mEntityConfidence, mSignature); } } @@ -526,9 +589,11 @@ public final class TextClassification { /** * Optional input parameters for generating TextClassification. */ - public static final class Options { + public static final class Options implements Parcelable { + + private @Nullable LocaleList mDefaultLocales; - private LocaleList mDefaultLocales; + public Options() {} /** * @param defaultLocales ordered list of locale preferences that may be used to disambiguate @@ -548,5 +613,80 @@ public final class TextClassification { public LocaleList getDefaultLocales() { return mDefaultLocales; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mDefaultLocales != null ? 1 : 0); + if (mDefaultLocales != null) { + mDefaultLocales.writeToParcel(dest, flags); + } + } + + public static final Parcelable.Creator<Options> CREATOR = + new Parcelable.Creator<Options>() { + @Override + public Options createFromParcel(Parcel in) { + return new Options(in); + } + + @Override + public Options[] newArray(int size) { + return new Options[size]; + } + }; + + private Options(Parcel in) { + if (in.readInt() > 0) { + mDefaultLocales = LocaleList.CREATOR.createFromParcel(in); + } + } + } + + /** + * Parcelable wrapper for TextClassification objects. + * @hide + */ + public static final class ParcelableWrapper implements Parcelable { + + @NonNull private TextClassification mTextClassification; + + public ParcelableWrapper(@NonNull TextClassification textClassification) { + Preconditions.checkNotNull(textClassification); + mTextClassification = textClassification; + } + + @NonNull + public TextClassification getTextClassification() { + return mTextClassification; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mTextClassification.writeToParcel(dest, flags); + } + + public static final Parcelable.Creator<ParcelableWrapper> CREATOR = + new Parcelable.Creator<ParcelableWrapper>() { + @Override + public ParcelableWrapper createFromParcel(Parcel in) { + return new ParcelableWrapper(new TextClassification(in)); + } + + @Override + public ParcelableWrapper[] newArray(int size) { + return new ParcelableWrapper[size]; + } + }; + } } diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java index ed604303..e9715c51 100644 --- a/android/view/textclassifier/TextClassifier.java +++ b/android/view/textclassifier/TextClassifier.java @@ -23,6 +23,8 @@ import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.WorkerThread; import android.os.LocaleList; +import android.os.Parcel; +import android.os.Parcelable; import android.util.ArraySet; import com.android.internal.util.Preconditions; @@ -275,8 +277,8 @@ public interface TextClassifier { /** * Returns a {@link Collection} of the entity types in the specified preset. * - * @see #ENTITIES_ALL - * @see #ENTITIES_NONE + * @see #ENTITY_PRESET_ALL + * @see #ENTITY_PRESET_NONE */ default Collection<String> getEntitiesForPreset(@EntityPreset int entityPreset) { return Collections.EMPTY_LIST; @@ -305,7 +307,7 @@ public interface TextClassifier { * * Configs are initially based on a predefined preset, and can be modified from there. */ - final class EntityConfig { + final class EntityConfig implements Parcelable { private final @TextClassifier.EntityPreset int mEntityPreset; private final Collection<String> mExcludedEntityTypes; private final Collection<String> mIncludedEntityTypes; @@ -355,6 +357,37 @@ public interface TextClassifier { } return Collections.unmodifiableList(entities); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mEntityPreset); + dest.writeStringList(new ArrayList<>(mExcludedEntityTypes)); + dest.writeStringList(new ArrayList<>(mIncludedEntityTypes)); + } + + public static final Parcelable.Creator<EntityConfig> CREATOR = + new Parcelable.Creator<EntityConfig>() { + @Override + public EntityConfig createFromParcel(Parcel in) { + return new EntityConfig(in); + } + + @Override + public EntityConfig[] newArray(int size) { + return new EntityConfig[size]; + } + }; + + private EntityConfig(Parcel in) { + mEntityPreset = in.readInt(); + mExcludedEntityTypes = new ArraySet<>(in.createStringArrayList()); + mIncludedEntityTypes = new ArraySet<>(in.createStringArrayList()); + } } /** diff --git a/android/view/textclassifier/TextClassifierConstants.java b/android/view/textclassifier/TextClassifierConstants.java index 51e6168e..00695b79 100644 --- a/android/view/textclassifier/TextClassifierConstants.java +++ b/android/view/textclassifier/TextClassifierConstants.java @@ -45,19 +45,24 @@ public final class TextClassifierConstants { "smart_selection_dark_launch"; private static final String SMART_SELECTION_ENABLED_FOR_EDIT_TEXT = "smart_selection_enabled_for_edit_text"; + private static final String SMART_LINKIFY_ENABLED = + "smart_linkify_enabled"; private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false; private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true; + private static final boolean SMART_LINKIFY_ENABLED_DEFAULT = true; /** Default settings. */ static final TextClassifierConstants DEFAULT = new TextClassifierConstants(); private final boolean mDarkLaunch; private final boolean mSuggestSelectionEnabledForEditableText; + private final boolean mSmartLinkifyEnabled; private TextClassifierConstants() { mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT; mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT; + mSmartLinkifyEnabled = SMART_LINKIFY_ENABLED_DEFAULT; } private TextClassifierConstants(@Nullable String settings) { @@ -74,6 +79,9 @@ public final class TextClassifierConstants { mSuggestSelectionEnabledForEditableText = parser.getBoolean( SMART_SELECTION_ENABLED_FOR_EDIT_TEXT, SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT); + mSmartLinkifyEnabled = parser.getBoolean( + SMART_LINKIFY_ENABLED, + SMART_LINKIFY_ENABLED_DEFAULT); } static TextClassifierConstants loadFromString(String settings) { @@ -87,4 +95,8 @@ public final class TextClassifierConstants { public boolean isSuggestSelectionEnabledForEditableText() { return mSuggestSelectionEnabledForEditableText; } + + public boolean isSmartLinkifyEnabled() { + return mSmartLinkifyEnabled; + } } diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java index aea3cb06..7db0e76d 100644 --- a/android/view/textclassifier/TextClassifierImpl.java +++ b/android/view/textclassifier/TextClassifierImpl.java @@ -32,7 +32,6 @@ import android.provider.ContactsContract; import android.provider.Settings; import android.text.util.Linkify; import android.util.Patterns; -import android.view.View.OnClickListener; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; @@ -187,6 +186,11 @@ final class TextClassifierImpl implements TextClassifier { Utils.validateInput(text); final String textString = text.toString(); final TextLinks.Builder builder = new TextLinks.Builder(textString); + + if (!getSettings().isSmartLinkifyEnabled()) { + return builder.build(); + } + try { final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null; final Collection<String> entitiesToIdentify = @@ -457,12 +461,10 @@ final class TextClassifierImpl implements TextClassifier { } } final String labelString = (label != null) ? label.toString() : null; - final OnClickListener onClickListener = - TextClassification.createStartActivityOnClickListener(mContext, intent); if (i == 0) { - builder.setPrimaryAction(intent, labelString, icon, onClickListener); + builder.setPrimaryAction(intent, labelString, icon); } else { - builder.addSecondaryAction(intent, labelString, icon, onClickListener); + builder.addSecondaryAction(intent, labelString, icon); } } } diff --git a/android/view/textclassifier/TextLinks.java b/android/view/textclassifier/TextLinks.java index 6c587cf9..ba854e04 100644 --- a/android/view/textclassifier/TextLinks.java +++ b/android/view/textclassifier/TextLinks.java @@ -20,6 +20,8 @@ import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.LocaleList; +import android.os.Parcel; +import android.os.Parcelable; import android.text.SpannableString; import android.text.style.ClickableSpan; import android.view.View; @@ -38,7 +40,7 @@ import java.util.function.Function; * A collection of links, representing subsequences of text and the entity types (phone number, * address, url, etc) they may be. */ -public final class TextLinks { +public final class TextLinks implements Parcelable { private final String mFullText; private final List<TextLink> mLinks; @@ -83,11 +85,40 @@ public final class TextLinks { return true; } + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mFullText); + dest.writeTypedList(mLinks); + } + + public static final Parcelable.Creator<TextLinks> CREATOR = + new Parcelable.Creator<TextLinks>() { + @Override + public TextLinks createFromParcel(Parcel in) { + return new TextLinks(in); + } + + @Override + public TextLinks[] newArray(int size) { + return new TextLinks[size]; + } + }; + + private TextLinks(Parcel in) { + mFullText = in.readString(); + mLinks = in.createTypedArrayList(TextLink.CREATOR); + } + /** * A link, identifying a substring of text and possible entity types for it. */ - public static final class TextLink { - private final EntityConfidence<String> mEntityScores; + public static final class TextLink implements Parcelable { + private final EntityConfidence mEntityScores; private final String mOriginalText; private final int mStart; private final int mEnd; @@ -105,7 +136,7 @@ public final class TextLinks { mOriginalText = originalText; mStart = start; mEnd = end; - mEntityScores = new EntityConfidence<>(entityScores); + mEntityScores = new EntityConfidence(entityScores); } /** @@ -153,16 +184,51 @@ public final class TextLinks { @TextClassifier.EntityType String entityType) { return mEntityScores.getConfidenceScore(entityType); } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mEntityScores.writeToParcel(dest, flags); + dest.writeString(mOriginalText); + dest.writeInt(mStart); + dest.writeInt(mEnd); + } + + public static final Parcelable.Creator<TextLink> CREATOR = + new Parcelable.Creator<TextLink>() { + @Override + public TextLink createFromParcel(Parcel in) { + return new TextLink(in); + } + + @Override + public TextLink[] newArray(int size) { + return new TextLink[size]; + } + }; + + private TextLink(Parcel in) { + mEntityScores = EntityConfidence.CREATOR.createFromParcel(in); + mOriginalText = in.readString(); + mStart = in.readInt(); + mEnd = in.readInt(); + } } /** * Optional input parameters for generating TextLinks. */ - public static final class Options { + public static final class Options implements Parcelable { private LocaleList mDefaultLocales; private TextClassifier.EntityConfig mEntityConfig; + public Options() {} + /** * @param defaultLocales ordered list of locale preferences that may be used to * disambiguate the provided text. If no locale preferences exist, @@ -201,6 +267,45 @@ public final class TextLinks { public TextClassifier.EntityConfig getEntityConfig() { return mEntityConfig; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mDefaultLocales != null ? 1 : 0); + if (mDefaultLocales != null) { + mDefaultLocales.writeToParcel(dest, flags); + } + dest.writeInt(mEntityConfig != null ? 1 : 0); + if (mEntityConfig != null) { + mEntityConfig.writeToParcel(dest, flags); + } + } + + public static final Parcelable.Creator<Options> CREATOR = + new Parcelable.Creator<Options>() { + @Override + public Options createFromParcel(Parcel in) { + return new Options(in); + } + + @Override + public Options[] newArray(int size) { + return new Options[size]; + } + }; + + private Options(Parcel in) { + if (in.readInt() > 0) { + mDefaultLocales = LocaleList.CREATOR.createFromParcel(in); + } + if (in.readInt() > 0) { + mEntityConfig = TextClassifier.EntityConfig.CREATOR.createFromParcel(in); + } + } } /** diff --git a/android/view/textclassifier/TextSelection.java b/android/view/textclassifier/TextSelection.java index 25e9e7ec..774d42db 100644 --- a/android/view/textclassifier/TextSelection.java +++ b/android/view/textclassifier/TextSelection.java @@ -21,6 +21,8 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.LocaleList; +import android.os.Parcel; +import android.os.Parcelable; import android.util.ArrayMap; import android.view.textclassifier.TextClassifier.EntityType; @@ -36,7 +38,7 @@ public final class TextSelection { private final int mStartIndex; private final int mEndIndex; - @NonNull private final EntityConfidence<String> mEntityConfidence; + @NonNull private final EntityConfidence mEntityConfidence; @NonNull private final String mSignature; private TextSelection( @@ -44,7 +46,7 @@ public final class TextSelection { @NonNull String signature) { mStartIndex = startIndex; mEndIndex = endIndex; - mEntityConfidence = new EntityConfidence<>(entityConfidence); + mEntityConfidence = new EntityConfidence(entityConfidence); mSignature = signature; } @@ -110,6 +112,22 @@ public final class TextSelection { mStartIndex, mEndIndex, mEntityConfidence, mSignature); } + /** Helper for parceling via #ParcelableWrapper. */ + private void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mStartIndex); + dest.writeInt(mEndIndex); + mEntityConfidence.writeToParcel(dest, flags); + dest.writeString(mSignature); + } + + /** Helper for unparceling via #ParcelableWrapper. */ + private TextSelection(Parcel in) { + mStartIndex = in.readInt(); + mEndIndex = in.readInt(); + mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); + mSignature = in.readString(); + } + /** * Builder used to build {@link TextSelection} objects. */ @@ -170,11 +188,13 @@ public final class TextSelection { /** * Optional input parameters for generating TextSelection. */ - public static final class Options { + public static final class Options implements Parcelable { - private LocaleList mDefaultLocales; + private @Nullable LocaleList mDefaultLocales; private boolean mDarkLaunchAllowed; + public Options() {} + /** * @param defaultLocales ordered list of locale preferences that may be used to disambiguate * the provided text. If no locale preferences exist, set this to null or an empty @@ -216,5 +236,82 @@ public final class TextSelection { public boolean isDarkLaunchAllowed() { return mDarkLaunchAllowed; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mDefaultLocales != null ? 1 : 0); + if (mDefaultLocales != null) { + mDefaultLocales.writeToParcel(dest, flags); + } + dest.writeInt(mDarkLaunchAllowed ? 1 : 0); + } + + public static final Parcelable.Creator<Options> CREATOR = + new Parcelable.Creator<Options>() { + @Override + public Options createFromParcel(Parcel in) { + return new Options(in); + } + + @Override + public Options[] newArray(int size) { + return new Options[size]; + } + }; + + private Options(Parcel in) { + if (in.readInt() > 0) { + mDefaultLocales = LocaleList.CREATOR.createFromParcel(in); + } + mDarkLaunchAllowed = in.readInt() != 0; + } + } + + /** + * Parcelable wrapper for TextSelection objects. + * @hide + */ + public static final class ParcelableWrapper implements Parcelable { + + @NonNull private TextSelection mTextSelection; + + public ParcelableWrapper(@NonNull TextSelection textSelection) { + Preconditions.checkNotNull(textSelection); + mTextSelection = textSelection; + } + + @NonNull + public TextSelection getTextSelection() { + return mTextSelection; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mTextSelection.writeToParcel(dest, flags); + } + + public static final Parcelable.Creator<ParcelableWrapper> CREATOR = + new Parcelable.Creator<ParcelableWrapper>() { + @Override + public ParcelableWrapper createFromParcel(Parcel in) { + return new ParcelableWrapper(new TextSelection(in)); + } + + @Override + public ParcelableWrapper[] newArray(int size) { + return new ParcelableWrapper[size]; + } + }; + } } |