summaryrefslogtreecommitdiff
path: root/android/view
diff options
context:
space:
mode:
authorJeff Davidson <jpd@google.com>2018-02-08 15:30:06 -0800
committerJeff Davidson <jpd@google.com>2018-02-08 15:30:06 -0800
commita192cc2a132cb0ee8588e2df755563ec7008c179 (patch)
tree380e4db22df19c819bd37df34bf06e7568916a50 /android/view
parent98fe7819c6d14f4f464a5cac047f9e82dee5da58 (diff)
downloadandroid-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')
-rw-r--r--android/view/Choreographer.java6
-rw-r--r--android/view/DisplayCutout.java104
-rw-r--r--android/view/DisplayInfo.java11
-rw-r--r--android/view/IWindowManagerImpl.java26
-rw-r--r--android/view/KeyEvent.java7
-rw-r--r--android/view/MotionEvent.java45
-rw-r--r--android/view/NotificationHeaderView.java46
-rw-r--r--android/view/PointerIcon.java4
-rw-r--r--android/view/RecordingCanvas.java21
-rw-r--r--android/view/RemoteAnimationAdapter.java108
-rw-r--r--android/view/RemoteAnimationDefinition.java93
-rw-r--r--android/view/RemoteAnimationTarget.java161
-rw-r--r--android/view/Surface.java6
-rw-r--r--android/view/SurfaceControl.java25
-rw-r--r--android/view/ThreadedRenderer.java6
-rw-r--r--android/view/View.java673
-rw-r--r--android/view/ViewDebug.java102
-rw-r--r--android/view/ViewGroup.java84
-rw-r--r--android/view/ViewRootImpl.java177
-rw-r--r--android/view/ViewStructure.java12
-rw-r--r--android/view/Window.java57
-rw-r--r--android/view/WindowManager.java386
-rw-r--r--android/view/WindowManagerPolicyConstants.java8
-rw-r--r--android/view/accessibility/AccessibilityEvent.java459
-rw-r--r--android/view/accessibility/AccessibilityNodeInfo.java124
-rw-r--r--android/view/accessibility/AccessibilityRecord.java89
-rw-r--r--android/view/accessibility/AccessibilityViewHierarchyState.java61
-rw-r--r--android/view/accessibility/AccessibilityWindowInfo.java59
-rw-r--r--android/view/accessibility/SendViewScrolledAccessibilityEvent.java58
-rw-r--r--android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java111
-rw-r--r--android/view/accessibility/ThrottlingAccessibilityEventSender.java248
-rw-r--r--android/view/animation/AnimationUtils.java2
-rw-r--r--android/view/animation/ClipRectAnimation.java111
-rw-r--r--android/view/autofill/AutofillManager.java211
-rw-r--r--android/view/autofill/AutofillPopupWindow.java4
-rw-r--r--android/view/inputmethod/ExtractedText.java4
-rw-r--r--android/view/inputmethod/InputConnection.java147
-rw-r--r--android/view/inputmethod/InputConnectionWrapper.java61
-rw-r--r--android/view/inputmethod/InputMethodManager.java44
-rw-r--r--android/view/textclassifier/EntityConfidence.java76
-rw-r--r--android/view/textclassifier/TextClassification.java278
-rw-r--r--android/view/textclassifier/TextClassifier.java39
-rw-r--r--android/view/textclassifier/TextClassifierConstants.java12
-rw-r--r--android/view/textclassifier/TextClassifierImpl.java12
-rw-r--r--android/view/textclassifier/TextLinks.java115
-rw-r--r--android/view/textclassifier/TextSelection.java105
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];
+ }
+ };
+
}
}