From 98fe7819c6d14f4f464a5cac047f9e82dee5da58 Mon Sep 17 00:00:00 2001 From: Justin Klaassen Date: Wed, 3 Jan 2018 13:39:41 -0500 Subject: Import Android SDK Platform P [4524038] /google/data/ro/projects/android/fetch_artifact \ --bid 4524038 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4524038.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: Ic193bf1cf0cae78d4f2bfb4fbddfe42025c5c3c2 --- android/view/Choreographer.java | 14 + android/view/Display.java | 8 +- android/view/DisplayCutout.java | 145 ++- android/view/FrameInfo.java | 4 +- android/view/GestureDetector.java | 289 ++--- android/view/IWindowManagerImpl.java | 6 - android/view/Surface.java | 15 +- android/view/SurfaceControl.java | 180 ++- android/view/SurfaceView.java | 1142 +------------------- android/view/ThreadedRenderer.java | 29 +- android/view/View.java | 211 ++-- android/view/ViewConfiguration.java | 12 + android/view/ViewRootImpl.java | 324 ++++-- android/view/View_Delegate.java | 49 + android/view/WindowInsets.java | 31 +- android/view/WindowManager.java | 49 +- .../AccessibilityInteractionClient.java | 79 +- .../view/accessibility/AccessibilityManager.java | 925 +--------------- .../view/accessibility/AccessibilityNodeInfo.java | 11 +- .../AccessibilityRequestPreparer.java | 7 +- .../accessibility/AccessibilityWindowInfo.java | 38 +- android/view/autofill/AutofillManager.java | 151 ++- android/view/autofill/AutofillPopupWindow.java | 11 +- android/view/autofill/AutofillValue.java | 2 +- android/view/autofill/Helper.java | 51 +- android/view/inputmethod/InputMethodInfo.java | 28 +- android/view/inputmethod/InputMethodManager.java | 206 +++- .../inputmethod/InputMethodManagerInternal.java | 7 + android/view/textclassifier/EntityConfidence.java | 57 +- .../view/textclassifier/TextClassification.java | 394 ++++--- android/view/textclassifier/TextClassifier.java | 99 +- .../view/textclassifier/TextClassifierImpl.java | 163 +-- android/view/textclassifier/TextLinks.java | 46 +- android/view/textclassifier/TextSelection.java | 72 +- .../logging/SmartSelectionEventTracker.java | 37 +- android/view/textservice/TextServicesManager.java | 200 +--- 36 files changed, 1927 insertions(+), 3165 deletions(-) (limited to 'android/view') diff --git a/android/view/Choreographer.java b/android/view/Choreographer.java index 2ffa1c5e..ba6b6cf6 100644 --- a/android/view/Choreographer.java +++ b/android/view/Choreographer.java @@ -164,6 +164,7 @@ public final class Choreographer { private long mLastFrameTimeNanos; private long mFrameIntervalNanos; private boolean mDebugPrintNextFrameTimeDelta; + private int mFPSDivisor = 1; /** * Contains information about the current frame for jank-tracking, @@ -601,6 +602,11 @@ public final class Choreographer { } } + void setFPSDivisor(int divisor) { + if (divisor <= 0) divisor = 1; + mFPSDivisor = divisor; + } + void doFrame(long frameTimeNanos, int frame) { final long startNanos; synchronized (mLock) { @@ -643,6 +649,14 @@ public final class Choreographer { return; } + if (mFPSDivisor > 1) { + long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos; + if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) { + scheduleVsyncLocked(); + return; + } + } + mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos); mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; diff --git a/android/view/Display.java b/android/view/Display.java index 9673be01..5bd7446d 100644 --- a/android/view/Display.java +++ b/android/view/Display.java @@ -1323,10 +1323,10 @@ public final class Display { public static final int HDR_TYPE_HLG = 3; /** @hide */ - @IntDef({ - HDR_TYPE_DOLBY_VISION, - HDR_TYPE_HDR10, - HDR_TYPE_HLG, + @IntDef(prefix = { "HDR_TYPE_" }, value = { + HDR_TYPE_DOLBY_VISION, + HDR_TYPE_HDR10, + HDR_TYPE_HLG, }) @Retention(RetentionPolicy.SOURCE) public @interface HdrType {} diff --git a/android/view/DisplayCutout.java b/android/view/DisplayCutout.java index 19cd42e1..e448f14c 100644 --- a/android/view/DisplayCutout.java +++ b/android/view/DisplayCutout.java @@ -21,40 +21,37 @@ import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; -import android.annotation.NonNull; +import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; -import java.util.ArrayList; import java.util.List; /** * Represents a part of the display that is not functional for displaying content. * *

{@code DisplayCutout} is immutable. - * - * @hide will become API */ public final class DisplayCutout { - private static final Rect ZERO_RECT = new Rect(0, 0, 0, 0); - private static final ArrayList EMPTY_LIST = new ArrayList<>(); + private static final Rect ZERO_RECT = new Rect(); + private static final Region EMPTY_REGION = new Region(); /** - * An instance where {@link #hasCutout()} returns {@code false}. + * An instance where {@link #isEmpty()} returns {@code true}. * * @hide */ - public static final DisplayCutout NO_CUTOUT = - new DisplayCutout(ZERO_RECT, ZERO_RECT, EMPTY_LIST); + public static final DisplayCutout NO_CUTOUT = new DisplayCutout(ZERO_RECT, EMPTY_REGION); private final Rect mSafeInsets; - private final Rect mBoundingRect; - private final List mBoundingPolygon; + private final Region mBounds; /** * Creates a DisplayCutout instance. @@ -64,22 +61,18 @@ public final class DisplayCutout { * @hide */ @VisibleForTesting - public DisplayCutout(Rect safeInsets, Rect boundingRect, List boundingPolygon) { + public DisplayCutout(Rect safeInsets, Region bounds) { mSafeInsets = safeInsets != null ? safeInsets : ZERO_RECT; - mBoundingRect = boundingRect != null ? boundingRect : ZERO_RECT; - mBoundingPolygon = boundingPolygon != null ? boundingPolygon : EMPTY_LIST; + mBounds = bounds != null ? bounds : Region.obtain(); } /** - * Returns whether there is a cutout. - * - * If false, the safe insets will all return zero, and the bounding box or polygon will be - * empty or outside the content view. + * Returns true if there is no cutout or it is outside of the content view. * - * @return {@code true} if there is a cutout, {@code false} otherwise + * @hide */ - public boolean hasCutout() { - return !mSafeInsets.equals(ZERO_RECT); + public boolean isEmpty() { + return mSafeInsets.equals(ZERO_RECT); } /** Returns the inset from the top which avoids the display cutout. */ @@ -103,44 +96,41 @@ public final class DisplayCutout { } /** - * Obtains the safe insets in a rect. + * Returns the safe insets in a rect. * - * @param out a rect which is set to the safe insets. + * @return a rect which is set to the safe insets. * @hide */ - public void getSafeInsets(@NonNull Rect out) { - out.set(mSafeInsets); + public Rect getSafeInsets() { + return new Rect(mSafeInsets); } /** - * Obtains the bounding rect of the cutout. + * Returns the bounding region of the cutout. * - * @param outRect is filled with the bounding rect of the cutout. Coordinates are relative + * @return the bounding region of the cutout. Coordinates are relative * to the top-left corner of the content view. */ - public void getBoundingRect(@NonNull Rect outRect) { - outRect.set(mBoundingRect); + public Region getBounds() { + return Region.obtain(mBounds); } /** - * Obtains the bounding polygon of the cutout. + * Returns the bounding rect of the cutout. * - * @param outPolygon is filled with a list of points representing the corners of a convex - * polygon which covers the cutout. Coordinates are relative to the - * top-left corner of the content view. + * @return the bounding rect of the cutout. Coordinates are relative + * to the top-left corner of the content view. + * @hide */ - public void getBoundingPolygon(List outPolygon) { - outPolygon.clear(); - for (int i = 0; i < mBoundingPolygon.size(); i++) { - outPolygon.add(new Point(mBoundingPolygon.get(i))); - } + public Rect getBoundingRect() { + // TODO(roosa): Inline. + return mBounds.getBounds(); } @Override public int hashCode() { int result = mSafeInsets.hashCode(); - result = result * 31 + mBoundingRect.hashCode(); - result = result * 31 + mBoundingPolygon.hashCode(); + result = result * 31 + mBounds.getBounds().hashCode(); return result; } @@ -152,8 +142,7 @@ public final class DisplayCutout { if (o instanceof DisplayCutout) { DisplayCutout c = (DisplayCutout) o; return mSafeInsets.equals(c.mSafeInsets) - && mBoundingRect.equals(c.mBoundingRect) - && mBoundingPolygon.equals(c.mBoundingPolygon); + && mBounds.equals(c.mBounds); } return false; } @@ -161,7 +150,7 @@ public final class DisplayCutout { @Override public String toString() { return "DisplayCutout{insets=" + mSafeInsets - + " bounding=" + mBoundingRect + + " bounds=" + mBounds + "}"; } @@ -172,15 +161,13 @@ public final class DisplayCutout { * @hide */ public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) { - if (mBoundingRect.isEmpty() + if (mBounds.isEmpty() || insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) { return this; } Rect safeInsets = new Rect(mSafeInsets); - Rect boundingRect = new Rect(mBoundingRect); - ArrayList boundingPolygon = new ArrayList<>(); - getBoundingPolygon(boundingPolygon); + Region bounds = Region.obtain(mBounds); // Note: it's not really well defined what happens when the inset is negative, because we // don't know if the safe inset needs to expand in general. @@ -197,10 +184,9 @@ public final class DisplayCutout { safeInsets.right = atLeastZero(safeInsets.right - insetRight); } - boundingRect.offset(-insetLeft, -insetTop); - offset(boundingPolygon, -insetLeft, -insetTop); + bounds.translate(-insetLeft, -insetTop); - return new DisplayCutout(safeInsets, boundingRect, boundingPolygon); + return new DisplayCutout(safeInsets, bounds); } /** @@ -210,20 +196,17 @@ public final class DisplayCutout { * @hide */ public DisplayCutout calculateRelativeTo(Rect frame) { - if (mBoundingRect.isEmpty() || !Rect.intersects(frame, mBoundingRect)) { + if (mBounds.isEmpty() || !Rect.intersects(frame, mBounds.getBounds())) { return NO_CUTOUT; } - Rect boundingRect = new Rect(mBoundingRect); - ArrayList boundingPolygon = new ArrayList<>(); - getBoundingPolygon(boundingPolygon); - - return DisplayCutout.calculateRelativeTo(frame, boundingRect, boundingPolygon); + return DisplayCutout.calculateRelativeTo(frame, Region.obtain(mBounds)); } - private static DisplayCutout calculateRelativeTo(Rect frame, Rect boundingRect, - ArrayList boundingPolygon) { + private static DisplayCutout calculateRelativeTo(Rect frame, Region bounds) { + Rect boundingRect = bounds.getBounds(); Rect safeRect = new Rect(); + int bestArea = 0; int bestVariant = 0; for (int variant = ROTATION_0; variant <= ROTATION_270; variant++) { @@ -247,10 +230,9 @@ public final class DisplayCutout { Math.max(0, frame.bottom - safeRect.bottom)); } - boundingRect.offset(-frame.left, -frame.top); - offset(boundingPolygon, -frame.left, -frame.top); + bounds.translate(-frame.left, -frame.top); - return new DisplayCutout(safeRect, boundingRect, boundingPolygon); + return new DisplayCutout(safeRect, bounds); } private static int calculateInsetVariantArea(Rect frame, Rect boundingRect, int variant, @@ -277,11 +259,6 @@ public final class DisplayCutout { return value < 0 ? 0 : value; } - private static void offset(ArrayList points, int dx, int dy) { - for (int i = 0; i < points.size(); i++) { - points.get(i).offset(dx, dy); - } - } /** * Creates an instance from a bounding polygon. @@ -289,20 +266,28 @@ public final class DisplayCutout { * @hide */ public static DisplayCutout fromBoundingPolygon(List points) { - Rect boundingRect = new Rect(Integer.MAX_VALUE, Integer.MAX_VALUE, - Integer.MIN_VALUE, Integer.MIN_VALUE); - ArrayList boundingPolygon = new ArrayList<>(); + Region bounds = Region.obtain(); + Path path = new Path(); + path.reset(); for (int i = 0; i < points.size(); i++) { Point point = points.get(i); - boundingRect.left = Math.min(boundingRect.left, point.x); - boundingRect.right = Math.max(boundingRect.right, point.x); - boundingRect.top = Math.min(boundingRect.top, point.y); - boundingRect.bottom = Math.max(boundingRect.bottom, point.y); - boundingPolygon.add(new Point(point)); + if (i == 0) { + path.moveTo(point.x, point.y); + } else { + path.lineTo(point.x, point.y); + } } + path.close(); + + 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); - return new DisplayCutout(ZERO_RECT, boundingRect, boundingPolygon); + bounds.setPath(path, clipRegion); + return new DisplayCutout(ZERO_RECT, bounds); } /** @@ -336,8 +321,7 @@ public final class DisplayCutout { } else { out.writeInt(1); out.writeTypedObject(mInner.mSafeInsets, flags); - out.writeTypedObject(mInner.mBoundingRect, flags); - out.writeTypedList(mInner.mBoundingPolygon, flags); + out.writeTypedObject(mInner.mBounds, flags); } } @@ -368,13 +352,10 @@ public final class DisplayCutout { return NO_CUTOUT; } - ArrayList boundingPolygon = new ArrayList<>(); - Rect safeInsets = in.readTypedObject(Rect.CREATOR); - Rect boundingRect = in.readTypedObject(Rect.CREATOR); - in.readTypedList(boundingPolygon, Point.CREATOR); + Region bounds = in.readTypedObject(Region.CREATOR); - return new DisplayCutout(safeInsets, boundingRect, boundingPolygon); + return new DisplayCutout(safeInsets, bounds); } public DisplayCutout get() { diff --git a/android/view/FrameInfo.java b/android/view/FrameInfo.java index c79547c8..6c5e048f 100644 --- a/android/view/FrameInfo.java +++ b/android/view/FrameInfo.java @@ -16,7 +16,7 @@ package android.view; -import android.annotation.IntDef; +import android.annotation.LongDef; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -48,7 +48,7 @@ final class FrameInfo { // Is this the first-draw following a window layout? public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1; - @IntDef(flag = true, value = { + @LongDef(flag = true, value = { FLAG_WINDOW_LAYOUT_CHANGED }) @Retention(RetentionPolicy.SOURCE) public @interface FrameInfoFlags {} diff --git a/android/view/GestureDetector.java b/android/view/GestureDetector.java index 52e53b07..bc2953e0 100644 --- a/android/view/GestureDetector.java +++ b/android/view/GestureDetector.java @@ -520,162 +520,163 @@ public class GestureDetector { boolean handled = false; switch (action & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_POINTER_DOWN: - mDownFocusX = mLastFocusX = focusX; - mDownFocusY = mLastFocusY = focusY; - // Cancel long press and taps - cancelTaps(); - break; - - case MotionEvent.ACTION_POINTER_UP: - mDownFocusX = mLastFocusX = focusX; - mDownFocusY = mLastFocusY = focusY; - - // Check the dot product of current velocities. - // If the pointer that left was opposing another velocity vector, clear. - mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); - final int upIndex = ev.getActionIndex(); - final int id1 = ev.getPointerId(upIndex); - final float x1 = mVelocityTracker.getXVelocity(id1); - final float y1 = mVelocityTracker.getYVelocity(id1); - for (int i = 0; i < count; i++) { - if (i == upIndex) continue; - - final int id2 = ev.getPointerId(i); - final float x = x1 * mVelocityTracker.getXVelocity(id2); - final float y = y1 * mVelocityTracker.getYVelocity(id2); - - final float dot = x + y; - if (dot < 0) { - mVelocityTracker.clear(); - break; + case MotionEvent.ACTION_POINTER_DOWN: + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; + // Cancel long press and taps + cancelTaps(); + break; + + case MotionEvent.ACTION_POINTER_UP: + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; + + // Check the dot product of current velocities. + // If the pointer that left was opposing another velocity vector, clear. + mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); + final int upIndex = ev.getActionIndex(); + final int id1 = ev.getPointerId(upIndex); + final float x1 = mVelocityTracker.getXVelocity(id1); + final float y1 = mVelocityTracker.getYVelocity(id1); + for (int i = 0; i < count; i++) { + if (i == upIndex) continue; + + final int id2 = ev.getPointerId(i); + final float x = x1 * mVelocityTracker.getXVelocity(id2); + final float y = y1 * mVelocityTracker.getYVelocity(id2); + + final float dot = x + y; + if (dot < 0) { + mVelocityTracker.clear(); + break; + } } - } - break; - - case MotionEvent.ACTION_DOWN: - if (mDoubleTapListener != null) { - boolean hadTapMessage = mHandler.hasMessages(TAP); - if (hadTapMessage) mHandler.removeMessages(TAP); - if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage && - isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) { - // This is a second tap - mIsDoubleTapping = true; - // Give a callback with the first tap of the double-tap - handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent); - // Give a callback with down event of the double-tap - handled |= mDoubleTapListener.onDoubleTapEvent(ev); - } else { - // This is a first tap - mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT); + break; + + case MotionEvent.ACTION_DOWN: + if (mDoubleTapListener != null) { + boolean hadTapMessage = mHandler.hasMessages(TAP); + if (hadTapMessage) mHandler.removeMessages(TAP); + if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) + && hadTapMessage + && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) { + // This is a second tap + mIsDoubleTapping = true; + // Give a callback with the first tap of the double-tap + handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent); + // Give a callback with down event of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } else { + // This is a first tap + mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT); + } } - } - mDownFocusX = mLastFocusX = focusX; - mDownFocusY = mLastFocusY = focusY; - if (mCurrentDownEvent != null) { - mCurrentDownEvent.recycle(); - } - mCurrentDownEvent = MotionEvent.obtain(ev); - mAlwaysInTapRegion = true; - mAlwaysInBiggerTapRegion = true; - mStillDown = true; - mInLongPress = false; - mDeferConfirmSingleTap = false; - - if (mIsLongpressEnabled) { - mHandler.removeMessages(LONG_PRESS); - mHandler.sendEmptyMessageAtTime(LONG_PRESS, - mCurrentDownEvent.getDownTime() + LONGPRESS_TIMEOUT); - } - mHandler.sendEmptyMessageAtTime(SHOW_PRESS, - mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); - handled |= mListener.onDown(ev); - break; + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; + if (mCurrentDownEvent != null) { + mCurrentDownEvent.recycle(); + } + mCurrentDownEvent = MotionEvent.obtain(ev); + mAlwaysInTapRegion = true; + mAlwaysInBiggerTapRegion = true; + mStillDown = true; + mInLongPress = false; + mDeferConfirmSingleTap = false; - case MotionEvent.ACTION_MOVE: - if (mInLongPress || mInContextClick) { + if (mIsLongpressEnabled) { + mHandler.removeMessages(LONG_PRESS); + mHandler.sendEmptyMessageAtTime(LONG_PRESS, + mCurrentDownEvent.getDownTime() + LONGPRESS_TIMEOUT); + } + mHandler.sendEmptyMessageAtTime(SHOW_PRESS, + mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); + handled |= mListener.onDown(ev); break; - } - final float scrollX = mLastFocusX - focusX; - final float scrollY = mLastFocusY - focusY; - if (mIsDoubleTapping) { - // Give the move events of the double-tap - handled |= mDoubleTapListener.onDoubleTapEvent(ev); - } else if (mAlwaysInTapRegion) { - final int deltaX = (int) (focusX - mDownFocusX); - final int deltaY = (int) (focusY - mDownFocusY); - int distance = (deltaX * deltaX) + (deltaY * deltaY); - int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare; - if (distance > slopSquare) { + + case MotionEvent.ACTION_MOVE: + if (mInLongPress || mInContextClick) { + break; + } + final float scrollX = mLastFocusX - focusX; + final float scrollY = mLastFocusY - focusY; + if (mIsDoubleTapping) { + // Give the move events of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } else if (mAlwaysInTapRegion) { + final int deltaX = (int) (focusX - mDownFocusX); + final int deltaY = (int) (focusY - mDownFocusY); + int distance = (deltaX * deltaX) + (deltaY * deltaY); + int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare; + if (distance > slopSquare) { + handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); + mLastFocusX = focusX; + mLastFocusY = focusY; + mAlwaysInTapRegion = false; + mHandler.removeMessages(TAP); + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + } + int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare; + if (distance > doubleTapSlopSquare) { + mAlwaysInBiggerTapRegion = false; + } + } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); mLastFocusX = focusX; mLastFocusY = focusY; - mAlwaysInTapRegion = false; - mHandler.removeMessages(TAP); - mHandler.removeMessages(SHOW_PRESS); - mHandler.removeMessages(LONG_PRESS); } - int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare; - if (distance > doubleTapSlopSquare) { - mAlwaysInBiggerTapRegion = false; + break; + + case MotionEvent.ACTION_UP: + mStillDown = false; + MotionEvent currentUpEvent = MotionEvent.obtain(ev); + if (mIsDoubleTapping) { + // Finally, give the up event of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } else if (mInLongPress) { + mHandler.removeMessages(TAP); + mInLongPress = false; + } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) { + handled = mListener.onSingleTapUp(ev); + if (mDeferConfirmSingleTap && mDoubleTapListener != null) { + mDoubleTapListener.onSingleTapConfirmed(ev); + } + } else if (!mIgnoreNextUpEvent) { + + // A fling must travel the minimum tap distance + final VelocityTracker velocityTracker = mVelocityTracker; + final int pointerId = ev.getPointerId(0); + velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); + final float velocityY = velocityTracker.getYVelocity(pointerId); + final float velocityX = velocityTracker.getXVelocity(pointerId); + + if ((Math.abs(velocityY) > mMinimumFlingVelocity) + || (Math.abs(velocityX) > mMinimumFlingVelocity)) { + handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY); + } } - } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { - handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); - mLastFocusX = focusX; - mLastFocusY = focusY; - } - break; - - case MotionEvent.ACTION_UP: - mStillDown = false; - MotionEvent currentUpEvent = MotionEvent.obtain(ev); - if (mIsDoubleTapping) { - // Finally, give the up event of the double-tap - handled |= mDoubleTapListener.onDoubleTapEvent(ev); - } else if (mInLongPress) { - mHandler.removeMessages(TAP); - mInLongPress = false; - } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) { - handled = mListener.onSingleTapUp(ev); - if (mDeferConfirmSingleTap && mDoubleTapListener != null) { - mDoubleTapListener.onSingleTapConfirmed(ev); + if (mPreviousUpEvent != null) { + mPreviousUpEvent.recycle(); } - } else if (!mIgnoreNextUpEvent) { - - // A fling must travel the minimum tap distance - final VelocityTracker velocityTracker = mVelocityTracker; - final int pointerId = ev.getPointerId(0); - velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); - final float velocityY = velocityTracker.getYVelocity(pointerId); - final float velocityX = velocityTracker.getXVelocity(pointerId); - - if ((Math.abs(velocityY) > mMinimumFlingVelocity) - || (Math.abs(velocityX) > mMinimumFlingVelocity)){ - handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY); + // Hold the event we obtained above - listeners may have changed the original. + mPreviousUpEvent = currentUpEvent; + if (mVelocityTracker != null) { + // This may have been cleared when we called out to the + // application above. + mVelocityTracker.recycle(); + mVelocityTracker = null; } - } - if (mPreviousUpEvent != null) { - mPreviousUpEvent.recycle(); - } - // Hold the event we obtained above - listeners may have changed the original. - mPreviousUpEvent = currentUpEvent; - if (mVelocityTracker != null) { - // This may have been cleared when we called out to the - // application above. - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - mIsDoubleTapping = false; - mDeferConfirmSingleTap = false; - mIgnoreNextUpEvent = false; - mHandler.removeMessages(SHOW_PRESS); - mHandler.removeMessages(LONG_PRESS); - break; - - case MotionEvent.ACTION_CANCEL: - cancel(); - break; + mIsDoubleTapping = false; + mDeferConfirmSingleTap = false; + mIgnoreNextUpEvent = false; + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + break; + + case MotionEvent.ACTION_CANCEL: + cancel(); + break; } if (!handled && mInputEventConsistencyVerifier != null) { diff --git a/android/view/IWindowManagerImpl.java b/android/view/IWindowManagerImpl.java index 6c006cae..4d804c55 100644 --- a/android/view/IWindowManagerImpl.java +++ b/android/view/IWindowManagerImpl.java @@ -155,12 +155,6 @@ public class IWindowManagerImpl implements IWindowManager { return 0; } - @Override - public boolean inKeyguardRestrictedInputMode() throws RemoteException { - // TODO Auto-generated method stub - return false; - } - @Override public boolean inputMethodClientHasFocus(IInputMethodClient arg0) throws RemoteException { // TODO Auto-generated method stub diff --git a/android/view/Surface.java b/android/view/Surface.java index ddced6cd..a417a4a0 100644 --- a/android/view/Surface.java +++ b/android/view/Surface.java @@ -121,8 +121,12 @@ public class Surface implements Parcelable { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({SCALING_MODE_FREEZE, SCALING_MODE_SCALE_TO_WINDOW, - SCALING_MODE_SCALE_CROP, SCALING_MODE_NO_SCALE_CROP}) + @IntDef(prefix = { "SCALING_MODE_" }, value = { + SCALING_MODE_FREEZE, + SCALING_MODE_SCALE_TO_WINDOW, + SCALING_MODE_SCALE_CROP, + SCALING_MODE_NO_SCALE_CROP + }) public @interface ScalingMode {} // From system/window.h /** @hide */ @@ -135,7 +139,12 @@ public class Surface implements Parcelable { public static final int SCALING_MODE_NO_SCALE_CROP = 3; /** @hide */ - @IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270}) + @IntDef(prefix = { "ROTATION_" }, value = { + ROTATION_0, + ROTATION_90, + ROTATION_180, + ROTATION_270 + }) @Retention(RetentionPolicy.SOURCE) public @interface Rotation {} diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java index 3d01ec23..268e460d 100644 --- a/android/view/SurfaceControl.java +++ b/android/view/SurfaceControl.java @@ -16,17 +16,34 @@ 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 android.annotation.Size; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; import android.graphics.PixelFormat; +import android.graphics.Matrix; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; import android.os.Process; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.Log; import android.view.Surface.OutOfResourcesException; + +import com.android.internal.annotations.GuardedBy; + import dalvik.system.CloseGuard; import libcore.util.NativeAllocationRegistry; @@ -36,12 +53,14 @@ import java.io.Closeable; * SurfaceControl * @hide */ -public class SurfaceControl { +public class SurfaceControl implements Parcelable { private static final String TAG = "SurfaceControl"; private static native long nativeCreate(SurfaceSession session, String name, int w, int h, int format, int flags, long parentObject, int windowType, int ownerUid) throws OutOfResourcesException; + private static native long nativeReadFromParcel(Parcel in); + private static native void nativeWriteToParcel(long nativeObject, Parcel out); private static native void nativeRelease(long nativeObject); private static native void nativeDestroy(long nativeObject); private static native void nativeDisconnect(long nativeObject); @@ -55,8 +74,6 @@ public class SurfaceControl { private static native void nativeScreenshot(IBinder displayToken, Surface consumer, Rect sourceCrop, int width, int height, int minLayer, int maxLayer, boolean allLayers, boolean useIdentityTransform); - private static native void nativeCaptureLayers(IBinder layerHandleToken, Surface consumer, - Rect sourceCrop, float frameScale); private static native GraphicBuffer nativeCaptureLayers(IBinder layerHandleToken, Rect sourceCrop, float frameScale); @@ -141,6 +158,13 @@ public class SurfaceControl { private final String mName; long mNativeObject; // package visibility only for Surface.java access + // TODO: Move this to native. + private final Object mSizeLock = new Object(); + @GuardedBy("mSizeLock") + private int mWidth; + @GuardedBy("mSizeLock") + private int mHeight; + static Transaction sGlobalTransaction; static long sTransactionNestCount = 0; @@ -555,6 +579,8 @@ public class SurfaceControl { } mName = name; + mWidth = w; + mHeight = h; mNativeObject = nativeCreate(session, name, w, h, format, flags, parent != null ? parent.mNativeObject : 0, windowType, ownerUid); if (mNativeObject == 0) { @@ -570,12 +596,49 @@ public class SurfaceControl { // event logging. public SurfaceControl(SurfaceControl other) { mName = other.mName; + mWidth = other.mWidth; + mHeight = other.mHeight; mNativeObject = other.mNativeObject; other.mCloseGuard.close(); other.mNativeObject = 0; mCloseGuard.open("release"); } + private SurfaceControl(Parcel in) { + mName = in.readString(); + mWidth = in.readInt(); + mHeight = in.readInt(); + mNativeObject = nativeReadFromParcel(in); + if (mNativeObject == 0) { + throw new IllegalArgumentException("Couldn't read SurfaceControl from parcel=" + in); + } + mCloseGuard.open("release"); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mName); + dest.writeInt(mWidth); + dest.writeInt(mHeight); + nativeWriteToParcel(mNativeObject, dest); + } + + public static final Creator CREATOR + = new Creator() { + public SurfaceControl createFromParcel(Parcel in) { + return new SurfaceControl(in); + } + + public SurfaceControl[] newArray(int size) { + return new SurfaceControl[size]; + } + }; + @Override protected void finalize() throws Throwable { try { @@ -666,7 +729,7 @@ public class SurfaceControl { */ @Deprecated public static void mergeToGlobalTransaction(Transaction t) { - synchronized(sGlobalTransaction) { + synchronized(SurfaceControl.class) { sGlobalTransaction.merge(t); } } @@ -826,6 +889,22 @@ public class SurfaceControl { } } + /** + * Sets the transform and position of a {@link SurfaceControl} from a 3x3 transformation matrix. + * + * @param matrix The matrix to apply. + * @param float9 An array of 9 floats to be used to extract the values from the matrix. + */ + public void setMatrix(Matrix matrix, float[] float9) { + checkNotReleased(); + matrix.getValues(float9); + synchronized (SurfaceControl.class) { + sGlobalTransaction.setMatrix(this, float9[MSCALE_X], float9[MSKEW_Y], + float9[MSKEW_X], float9[MSCALE_Y]); + sGlobalTransaction.setPosition(this, float9[MTRANS_X], float9[MTRANS_Y]); + } + } + public void setWindowCrop(Rect crop) { checkNotReleased(); synchronized (SurfaceControl.class) { @@ -863,6 +942,18 @@ public class SurfaceControl { } } + public int getWidth() { + synchronized (mSizeLock) { + return mWidth; + } + } + + public int getHeight() { + synchronized (mSizeLock) { + return mHeight; + } + } + @Override public String toString() { return "Surface(name=" + mName + ")/@0x" + @@ -1090,7 +1181,9 @@ public class SurfaceControl { } /** - * Copy the current screen contents into a bitmap and return it. + * Copy the current screen contents into a hardware bitmap and return it. + * Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap into + * a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)} * * CAVEAT: Versions of screenshot that return a {@link Bitmap} can * be extremely slow; avoid use unless absolutely necessary; prefer @@ -1115,7 +1208,7 @@ public class SurfaceControl { * screenshots in its native portrait orientation by default, so this is * useful for returning screenshots that are independent of device * orientation. - * @return Returns a Bitmap containing the screen contents, or null + * @return Returns a hardware Bitmap containing the screen contents, or null * if an error occurs. Make sure to call Bitmap.recycle() as soon as * possible, once its content is not needed anymore. */ @@ -1143,23 +1236,36 @@ public class SurfaceControl { } /** - * Like {@link SurfaceControl#screenshot(int, int, int, int, boolean)} but - * includes all Surfaces in the screenshot. + * Like {@link SurfaceControl#screenshot(Rect, int, int, int, int, boolean, int)} but + * includes all Surfaces in the screenshot. This will also update the orientation so it + * sends the correct coordinates to SF based on the rotation value. * + * @param sourceCrop The portion of the screen to capture into the Bitmap; + * caller may pass in 'new Rect()' if no cropping is desired. * @param width The desired width of the returned bitmap; the raw * screen will be scaled down to this size. * @param height The desired height of the returned bitmap; the raw * screen will be scaled down to this size. + * @param rotation Apply a custom clockwise rotation to the screenshot, i.e. + * Surface.ROTATION_0,90,180,270. Surfaceflinger will always take + * screenshots in its native portrait orientation by default, so this is + * useful for returning screenshots that are independent of device + * orientation. * @return Returns a Bitmap containing the screen contents, or null * if an error occurs. Make sure to call Bitmap.recycle() as soon as * possible, once its content is not needed anymore. */ - public static Bitmap screenshot(int width, int height) { + public static Bitmap screenshot(Rect sourceCrop, int width, int height, int rotation) { // TODO: should take the display as a parameter IBinder displayToken = SurfaceControl.getBuiltInDisplay( SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN); - return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true, - false, Surface.ROTATION_0); + if (rotation == ROTATION_90 || rotation == ROTATION_270) { + rotation = (rotation == ROTATION_90) ? ROTATION_270 : ROTATION_90; + } + + SurfaceControl.rotateCropForSF(sourceCrop, rotation); + return nativeScreenshot(displayToken, sourceCrop, width, height, 0, 0, true, + false, rotation); } private static void screenshot(IBinder display, Surface consumer, Rect sourceCrop, @@ -1175,26 +1281,29 @@ public class SurfaceControl { minLayer, maxLayer, allLayers, useIdentityTransform); } + private static void rotateCropForSF(Rect crop, int rot) { + if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) { + int tmp = crop.top; + crop.top = crop.left; + crop.left = tmp; + tmp = crop.right; + crop.right = crop.bottom; + crop.bottom = tmp; + } + } + /** - * Captures a layer and its children into the provided {@link Surface}. + * Captures a layer and its children and returns a {@link GraphicBuffer} with the content. * * @param layerHandleToken The root layer to capture. - * @param consumer The {@link Surface} to capture the layer into. * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new * Rect()' or null if no cropping is desired. * @param frameScale The desired scale of the returned buffer; the raw * screen will be scaled up/down. + * + * @return Returns a GraphicBuffer that contains the layer capture. */ - public static void captureLayers(IBinder layerHandleToken, Surface consumer, Rect sourceCrop, - float frameScale) { - nativeCaptureLayers(layerHandleToken, consumer, sourceCrop, frameScale); - } - - /** - * Same as {@link #captureLayers(IBinder, Surface, Rect, float)} except this - * captures to a {@link GraphicBuffer} instead of a {@link Surface}. - */ - public static GraphicBuffer captureLayersToBuffer(IBinder layerHandleToken, Rect sourceCrop, + public static GraphicBuffer captureLayers(IBinder layerHandleToken, Rect sourceCrop, float frameScale) { return nativeCaptureLayers(layerHandleToken, sourceCrop, frameScale); } @@ -1205,6 +1314,7 @@ public class SurfaceControl { nativeGetNativeTransactionFinalizer(), 512); private long mNativeObject; + private final ArrayMap mResizedSurfaces = new ArrayMap<>(); Runnable mFreeNativeResources; public Transaction() { @@ -1235,9 +1345,22 @@ public class SurfaceControl { * Jankier version of apply. Avoid use (b/28068298). */ public void apply(boolean sync) { + applyResizedSurfaces(); nativeApplyTransaction(mNativeObject, sync); } + private void applyResizedSurfaces() { + for (int i = mResizedSurfaces.size() - 1; i >= 0; i--) { + final Point size = mResizedSurfaces.valueAt(i); + final SurfaceControl surfaceControl = mResizedSurfaces.keyAt(i); + synchronized (surfaceControl.mSizeLock) { + surfaceControl.mWidth = size.x; + surfaceControl.mHeight = size.y; + } + } + mResizedSurfaces.clear(); + } + public Transaction show(SurfaceControl sc) { sc.checkNotReleased(); nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN); @@ -1258,6 +1381,7 @@ public class SurfaceControl { public Transaction setSize(SurfaceControl sc, int w, int h) { sc.checkNotReleased(); + mResizedSurfaces.put(sc, new Point(w, h)); nativeSetSize(mNativeObject, sc.mNativeObject, w, h); return this; } @@ -1296,6 +1420,14 @@ public class SurfaceControl { return this; } + public Transaction setMatrix(SurfaceControl sc, Matrix matrix, float[] float9) { + matrix.getValues(float9); + setMatrix(sc, float9[MSCALE_X], float9[MSKEW_Y], + float9[MSKEW_X], float9[MSCALE_Y]); + setPosition(sc, float9[MTRANS_X], float9[MTRANS_Y]); + return this; + } + public Transaction setWindowCrop(SurfaceControl sc, Rect crop) { sc.checkNotReleased(); if (crop != null) { @@ -1482,6 +1614,8 @@ public class SurfaceControl { * other transaction as if it had been applied. */ public Transaction merge(Transaction other) { + mResizedSurfaces.putAll(other.mResizedSurfaces); + other.mResizedSurfaces.clear(); nativeMergeTransaction(mNativeObject, other.mNativeObject); return this; } diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java index 578679b1..ebb2af45 100644 --- a/android/view/SurfaceView.java +++ b/android/view/SurfaceView.java @@ -16,1215 +16,115 @@ package android.view; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; -import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_OVERLAY_SUBLAYER; -import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_SUBLAYER; -import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAYER; +import com.android.layoutlib.bridge.MockView; import android.content.Context; -import android.content.res.CompatibilityInfo.Translator; -import android.content.res.Configuration; import android.graphics.Canvas; -import android.graphics.PixelFormat; -import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Region; -import android.os.Build; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.SystemClock; import android.util.AttributeSet; -import android.util.Log; - -import com.android.internal.view.SurfaceCallbackHelper; - -import java.util.ArrayList; -import java.util.concurrent.locks.ReentrantLock; /** - * Provides a dedicated drawing surface embedded inside of a view hierarchy. - * You can control the format of this surface and, if you like, its size; the - * SurfaceView takes care of placing the surface at the correct location on the - * screen - * - *

The surface is Z ordered so that it is behind the window holding its - * SurfaceView; the SurfaceView punches a hole in its window to allow its - * surface to be displayed. The view hierarchy will take care of correctly - * compositing with the Surface any siblings of the SurfaceView that would - * normally appear on top of it. This can be used to place overlays such as - * buttons on top of the Surface, though note however that it can have an - * impact on performance since a full alpha-blended composite will be performed - * each time the Surface changes. - * - *

The transparent region that makes the surface visible is based on the - * layout positions in the view hierarchy. If the post-layout transform - * properties are used to draw a sibling view on top of the SurfaceView, the - * view may not be properly composited with the surface. + * Mock version of the SurfaceView. + * Only non override public methods from the real SurfaceView have been added in there. + * Methods that take an unknown class as parameter or as return object, have been removed for now. * - *

Access to the underlying surface is provided via the SurfaceHolder interface, - * which can be retrieved by calling {@link #getHolder}. + * TODO: generate automatically. * - *

The Surface will be created for you while the SurfaceView's window is - * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated} - * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the - * Surface is created and destroyed as the window is shown and hidden. - * - *

One of the purposes of this class is to provide a surface in which a - * secondary thread can render into the screen. If you are going to use it - * this way, you need to be aware of some threading semantics: - * - *

- * - *

Note: Starting in platform version - * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is - * updated synchronously with other View rendering. This means that translating - * and scaling a SurfaceView on screen will not cause rendering artifacts. Such - * artifacts may occur on previous versions of the platform when its window is - * positioned asynchronously.

*/ -public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback { - private static final String TAG = "SurfaceView"; - private static final boolean DEBUG = false; - - final ArrayList mCallbacks - = new ArrayList(); - - final int[] mLocation = new int[2]; - - final ReentrantLock mSurfaceLock = new ReentrantLock(); - final Surface mSurface = new Surface(); // Current surface in use - boolean mDrawingStopped = true; - // We use this to track if the application has produced a frame - // in to the Surface. Up until that point, we should be careful not to punch - // holes. - boolean mDrawFinished = false; - - final Rect mScreenRect = new Rect(); - SurfaceSession mSurfaceSession; - - SurfaceControl mSurfaceControl; - // In the case of format changes we switch out the surface in-place - // we need to preserve the old one until the new one has drawn. - SurfaceControl mDeferredDestroySurfaceControl; - final Rect mTmpRect = new Rect(); - final Configuration mConfiguration = new Configuration(); - - int mSubLayer = APPLICATION_MEDIA_SUBLAYER; - - boolean mIsCreating = false; - private volatile boolean mRtHandlingPositionUpdates = false; - - private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener - = new ViewTreeObserver.OnScrollChangedListener() { - @Override - public void onScrollChanged() { - updateSurface(); - } - }; - - private final ViewTreeObserver.OnPreDrawListener mDrawListener = - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - // reposition ourselves where the surface is - mHaveFrame = getWidth() > 0 && getHeight() > 0; - updateSurface(); - return true; - } - }; - - boolean mRequestedVisible = false; - boolean mWindowVisibility = false; - boolean mLastWindowVisibility = false; - boolean mViewVisibility = false; - boolean mWindowStopped = false; - - int mRequestedWidth = -1; - int mRequestedHeight = -1; - /* Set SurfaceView's format to 565 by default to maintain backward - * compatibility with applications assuming this format. - */ - int mRequestedFormat = PixelFormat.RGB_565; - - boolean mHaveFrame = false; - boolean mSurfaceCreated = false; - long mLastLockTime = 0; - - boolean mVisible = false; - int mWindowSpaceLeft = -1; - int mWindowSpaceTop = -1; - int mSurfaceWidth = -1; - int mSurfaceHeight = -1; - int mFormat = -1; - final Rect mSurfaceFrame = new Rect(); - int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; - private Translator mTranslator; - - private boolean mGlobalListenersAdded; - private boolean mAttachedToWindow; - - private int mSurfaceFlags = SurfaceControl.HIDDEN; - - private int mPendingReportDraws; +public class SurfaceView extends MockView { public SurfaceView(Context context) { this(context, null); } public SurfaceView(Context context, AttributeSet attrs) { - this(context, attrs, 0); + this(context, attrs , 0); } - public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); + public SurfaceView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); } public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mRenderNode.requestPositionUpdates(this); - - setWillNotDraw(true); - } - - /** - * Return the SurfaceHolder providing access and control over this - * SurfaceView's underlying surface. - * - * @return SurfaceHolder The holder of the surface. - */ - public SurfaceHolder getHolder() { - return mSurfaceHolder; - } - - private void updateRequestedVisibility() { - mRequestedVisible = mViewVisibility && mWindowVisibility && !mWindowStopped; - } - - /** @hide */ - @Override - public void windowStopped(boolean stopped) { - mWindowStopped = stopped; - updateRequestedVisibility(); - updateSurface(); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - getViewRootImpl().addWindowStoppedCallback(this); - mWindowStopped = false; - - mViewVisibility = getVisibility() == VISIBLE; - updateRequestedVisibility(); - - mAttachedToWindow = true; - mParent.requestTransparentRegion(SurfaceView.this); - if (!mGlobalListenersAdded) { - ViewTreeObserver observer = getViewTreeObserver(); - observer.addOnScrollChangedListener(mScrollChangedListener); - observer.addOnPreDrawListener(mDrawListener); - mGlobalListenersAdded = true; - } - } - - @Override - protected void onWindowVisibilityChanged(int visibility) { - super.onWindowVisibilityChanged(visibility); - mWindowVisibility = visibility == VISIBLE; - updateRequestedVisibility(); - updateSurface(); - } - - @Override - public void setVisibility(int visibility) { - super.setVisibility(visibility); - mViewVisibility = visibility == VISIBLE; - boolean newRequestedVisible = mWindowVisibility && mViewVisibility && !mWindowStopped; - if (newRequestedVisible != mRequestedVisible) { - // our base class (View) invalidates the layout only when - // we go from/to the GONE state. However, SurfaceView needs - // to request a re-layout when the visibility changes at all. - // This is needed because the transparent region is computed - // as part of the layout phase, and it changes (obviously) when - // the visibility changes. - requestLayout(); - } - mRequestedVisible = newRequestedVisible; - updateSurface(); - } - - private void performDrawFinished() { - if (mPendingReportDraws > 0) { - mDrawFinished = true; - if (mAttachedToWindow) { - notifyDrawFinished(); - invalidate(); - } - } else { - Log.e(TAG, System.identityHashCode(this) + "finished drawing" - + " but no pending report draw (extra call" - + " to draw completion runnable?)"); - } - } - - void notifyDrawFinished() { - ViewRootImpl viewRoot = getViewRootImpl(); - if (viewRoot != null) { - viewRoot.pendingDrawFinished(); - } - mPendingReportDraws--; - } - - @Override - protected void onDetachedFromWindow() { - ViewRootImpl viewRoot = getViewRootImpl(); - // It's possible to create a SurfaceView using the default constructor and never - // attach it to a view hierarchy, this is a common use case when dealing with - // OpenGL. A developer will probably create a new GLSurfaceView, and let it manage - // the lifecycle. Instead of attaching it to a view, he/she can just pass - // the SurfaceHolder forward, most live wallpapers do it. - if (viewRoot != null) { - viewRoot.removeWindowStoppedCallback(this); - } - - mAttachedToWindow = false; - if (mGlobalListenersAdded) { - ViewTreeObserver observer = getViewTreeObserver(); - observer.removeOnScrollChangedListener(mScrollChangedListener); - observer.removeOnPreDrawListener(mDrawListener); - mGlobalListenersAdded = false; - } - - while (mPendingReportDraws > 0) { - notifyDrawFinished(); - } - - mRequestedVisible = false; - - updateSurface(); - if (mSurfaceControl != null) { - mSurfaceControl.destroy(); - } - mSurfaceControl = null; - - mHaveFrame = false; - - super.onDetachedFromWindow(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = mRequestedWidth >= 0 - ? resolveSizeAndState(mRequestedWidth, widthMeasureSpec, 0) - : getDefaultSize(0, widthMeasureSpec); - int height = mRequestedHeight >= 0 - ? resolveSizeAndState(mRequestedHeight, heightMeasureSpec, 0) - : getDefaultSize(0, heightMeasureSpec); - setMeasuredDimension(width, height); - } - - /** @hide */ - @Override - protected boolean setFrame(int left, int top, int right, int bottom) { - boolean result = super.setFrame(left, top, right, bottom); - updateSurface(); - return result; } - @Override public boolean gatherTransparentRegion(Region region) { - if (isAboveParent() || !mDrawFinished) { - return super.gatherTransparentRegion(region); - } - - boolean opaque = true; - if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) { - // this view draws, remove it from the transparent region - opaque = super.gatherTransparentRegion(region); - } else if (region != null) { - int w = getWidth(); - int h = getHeight(); - if (w>0 && h>0) { - getLocationInWindow(mLocation); - // otherwise, punch a hole in the whole hierarchy - int l = mLocation[0]; - int t = mLocation[1]; - region.op(l, t, l+w, t+h, Region.Op.UNION); - } - } - if (PixelFormat.formatHasAlpha(mRequestedFormat)) { - opaque = false; - } - return opaque; + return false; } - @Override - public void draw(Canvas canvas) { - if (mDrawFinished && !isAboveParent()) { - // draw() is not called when SKIP_DRAW is set - if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) { - // punch a whole in the view-hierarchy below us - canvas.drawColor(0, PorterDuff.Mode.CLEAR); - } - } - super.draw(canvas); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - if (mDrawFinished && !isAboveParent()) { - // draw() is not called when SKIP_DRAW is set - if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { - // punch a whole in the view-hierarchy below us - canvas.drawColor(0, PorterDuff.Mode.CLEAR); - } - } - super.dispatchDraw(canvas); - } - - /** - * Control whether the surface view's surface is placed on top of another - * regular surface view in the window (but still behind the window itself). - * This is typically used to place overlays on top of an underlying media - * surface view. - * - *

Note that this must be set before the surface view's containing - * window is attached to the window manager. - * - *

Calling this overrides any previous call to {@link #setZOrderOnTop}. - */ public void setZOrderMediaOverlay(boolean isMediaOverlay) { - mSubLayer = isMediaOverlay - ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER; } - /** - * Control whether the surface view's surface is placed on top of its - * window. Normally it is placed behind the window, to allow it to - * (for the most part) appear to composite with the views in the - * hierarchy. By setting this, you cause it to be placed above the - * window. This means that none of the contents of the window this - * SurfaceView is in will be visible on top of its surface. - * - *

Note that this must be set before the surface view's containing - * window is attached to the window manager. - * - *

Calling this overrides any previous call to {@link #setZOrderMediaOverlay}. - */ public void setZOrderOnTop(boolean onTop) { - if (onTop) { - mSubLayer = APPLICATION_PANEL_SUBLAYER; - } else { - mSubLayer = APPLICATION_MEDIA_SUBLAYER; - } } - /** - * Control whether the surface view's content should be treated as secure, - * preventing it from appearing in screenshots or from being viewed on - * non-secure displays. - * - *

Note that this must be set before the surface view's containing - * window is attached to the window manager. - * - *

See {@link android.view.Display#FLAG_SECURE} for details. - * - * @param isSecure True if the surface view is secure. - */ public void setSecure(boolean isSecure) { - if (isSecure) { - mSurfaceFlags |= SurfaceControl.SECURE; - } else { - mSurfaceFlags &= ~SurfaceControl.SECURE; - } - } - - private void updateOpaqueFlag() { - if (!PixelFormat.formatHasAlpha(mRequestedFormat)) { - mSurfaceFlags |= SurfaceControl.OPAQUE; - } else { - mSurfaceFlags &= ~SurfaceControl.OPAQUE; - } } - private Rect getParentSurfaceInsets() { - final ViewRootImpl root = getViewRootImpl(); - if (root == null) { - return null; - } else { - return root.mWindowAttributes.surfaceInsets; - } - } - - /** @hide */ - protected void updateSurface() { - if (!mHaveFrame) { - return; - } - ViewRootImpl viewRoot = getViewRootImpl(); - if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) { - return; - } - - mTranslator = viewRoot.mTranslator; - if (mTranslator != null) { - mSurface.setCompatibilityTranslator(mTranslator); - } - - int myWidth = mRequestedWidth; - if (myWidth <= 0) myWidth = getWidth(); - int myHeight = mRequestedHeight; - if (myHeight <= 0) myHeight = getHeight(); - - final boolean formatChanged = mFormat != mRequestedFormat; - final boolean visibleChanged = mVisible != mRequestedVisible; - final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged) - && mRequestedVisible; - final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight; - final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility; - boolean redrawNeeded = false; - - if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) { - getLocationInWindow(mLocation); - - if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " - + "Changes: creating=" + creating - + " format=" + formatChanged + " size=" + sizeChanged - + " visible=" + visibleChanged - + " left=" + (mWindowSpaceLeft != mLocation[0]) - + " top=" + (mWindowSpaceTop != mLocation[1])); - - try { - final boolean visible = mVisible = mRequestedVisible; - mWindowSpaceLeft = mLocation[0]; - mWindowSpaceTop = mLocation[1]; - mSurfaceWidth = myWidth; - mSurfaceHeight = myHeight; - mFormat = mRequestedFormat; - mLastWindowVisibility = mWindowVisibility; - - mScreenRect.left = mWindowSpaceLeft; - mScreenRect.top = mWindowSpaceTop; - mScreenRect.right = mWindowSpaceLeft + getWidth(); - mScreenRect.bottom = mWindowSpaceTop + getHeight(); - if (mTranslator != null) { - mTranslator.translateRectInAppWindowToScreen(mScreenRect); - } - - final Rect surfaceInsets = getParentSurfaceInsets(); - mScreenRect.offset(surfaceInsets.left, surfaceInsets.top); - - if (creating) { - mSurfaceSession = new SurfaceSession(viewRoot.mSurface); - mDeferredDestroySurfaceControl = mSurfaceControl; - - updateOpaqueFlag(); - final String name = "SurfaceView - " + viewRoot.getTitle().toString(); - - mSurfaceControl = new SurfaceControlWithBackground( - name, - (mSurfaceFlags & SurfaceControl.OPAQUE) != 0, - new SurfaceControl.Builder(mSurfaceSession) - .setSize(mSurfaceWidth, mSurfaceHeight) - .setFormat(mFormat) - .setFlags(mSurfaceFlags)); - } else if (mSurfaceControl == null) { - return; - } - - boolean realSizeChanged = false; - - mSurfaceLock.lock(); - try { - mDrawingStopped = !visible; - - if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " - + "Cur surface: " + mSurface); - - SurfaceControl.openTransaction(); - try { - mSurfaceControl.setLayer(mSubLayer); - if (mViewVisibility) { - mSurfaceControl.show(); - } else { - mSurfaceControl.hide(); - } - - // While creating the surface, we will set it's initial - // geometry. Outside of that though, we should generally - // leave it to the RenderThread. - // - // There is one more case when the buffer size changes we aren't yet - // prepared to sync (as even following the transaction applying - // we still need to latch a buffer). - // b/28866173 - if (sizeChanged || creating || !mRtHandlingPositionUpdates) { - mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top); - mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth, - 0.0f, 0.0f, - mScreenRect.height() / (float) mSurfaceHeight); - } - if (sizeChanged) { - mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight); - } - } finally { - SurfaceControl.closeTransaction(); - } - - if (sizeChanged || creating) { - redrawNeeded = true; - } - - mSurfaceFrame.left = 0; - mSurfaceFrame.top = 0; - if (mTranslator == null) { - mSurfaceFrame.right = mSurfaceWidth; - mSurfaceFrame.bottom = mSurfaceHeight; - } else { - float appInvertedScale = mTranslator.applicationInvertedScale; - mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f); - mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f); - } - - final int surfaceWidth = mSurfaceFrame.right; - final int surfaceHeight = mSurfaceFrame.bottom; - realSizeChanged = mLastSurfaceWidth != surfaceWidth - || mLastSurfaceHeight != surfaceHeight; - mLastSurfaceWidth = surfaceWidth; - mLastSurfaceHeight = surfaceHeight; - } finally { - mSurfaceLock.unlock(); - } - - try { - redrawNeeded |= visible && !mDrawFinished; - - SurfaceHolder.Callback callbacks[] = null; - - final boolean surfaceChanged = creating; - if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) { - mSurfaceCreated = false; - if (mSurface.isValid()) { - if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " - + "visibleChanged -- surfaceDestroyed"); - callbacks = getSurfaceCallbacks(); - for (SurfaceHolder.Callback c : callbacks) { - c.surfaceDestroyed(mSurfaceHolder); - } - // Since Android N the same surface may be reused and given to us - // again by the system server at a later point. However - // as we didn't do this in previous releases, clients weren't - // necessarily required to clean up properly in - // surfaceDestroyed. This leads to problems for example when - // clients don't destroy their EGL context, and try - // and create a new one on the same surface following reuse. - // Since there is no valid use of the surface in-between - // surfaceDestroyed and surfaceCreated, we force a disconnect, - // so the next connect will always work if we end up reusing - // the surface. - if (mSurface.isValid()) { - mSurface.forceScopedDisconnect(); - } - } - } - - if (creating) { - mSurface.copyFrom(mSurfaceControl); - } - - if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion - < Build.VERSION_CODES.O) { - // Some legacy applications use the underlying native {@link Surface} object - // as a key to whether anything has changed. In these cases, updates to the - // existing {@link Surface} will be ignored when the size changes. - // Therefore, we must explicitly recreate the {@link Surface} in these - // cases. - mSurface.createFrom(mSurfaceControl); - } - - if (visible && mSurface.isValid()) { - if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) { - mSurfaceCreated = true; - mIsCreating = true; - if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " - + "visibleChanged -- surfaceCreated"); - if (callbacks == null) { - callbacks = getSurfaceCallbacks(); - } - for (SurfaceHolder.Callback c : callbacks) { - c.surfaceCreated(mSurfaceHolder); - } - } - if (creating || formatChanged || sizeChanged - || visibleChanged || realSizeChanged) { - if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " - + "surfaceChanged -- format=" + mFormat - + " w=" + myWidth + " h=" + myHeight); - if (callbacks == null) { - callbacks = getSurfaceCallbacks(); - } - for (SurfaceHolder.Callback c : callbacks) { - c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight); - } - } - if (redrawNeeded) { - if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " - + "surfaceRedrawNeeded"); - if (callbacks == null) { - callbacks = getSurfaceCallbacks(); - } - - mPendingReportDraws++; - viewRoot.drawPending(); - SurfaceCallbackHelper sch = - new SurfaceCallbackHelper(this::onDrawFinished); - sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks); - } - } - } finally { - mIsCreating = false; - if (mSurfaceControl != null && !mSurfaceCreated) { - mSurface.release(); - // If we are not in the stopped state, then the destruction of the Surface - // represents a visual change we need to display, and we should go ahead - // and destroy the SurfaceControl. However if we are in the stopped state, - // we can just leave the Surface around so it can be a part of animations, - // and we let the life-time be tied to the parent surface. - if (!mWindowStopped) { - mSurfaceControl.destroy(); - mSurfaceControl = null; - } - } - } - } catch (Exception ex) { - Log.e(TAG, "Exception configuring surface", ex); - } - if (DEBUG) Log.v( - TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top - + " w=" + mScreenRect.width() + " h=" + mScreenRect.height() - + ", frame=" + mSurfaceFrame); - } else { - // Calculate the window position in case RT loses the window - // and we need to fallback to a UI-thread driven position update - getLocationInSurface(mLocation); - final boolean positionChanged = mWindowSpaceLeft != mLocation[0] - || mWindowSpaceTop != mLocation[1]; - final boolean layoutSizeChanged = getWidth() != mScreenRect.width() - || getHeight() != mScreenRect.height(); - if (positionChanged || layoutSizeChanged) { // Only the position has changed - mWindowSpaceLeft = mLocation[0]; - mWindowSpaceTop = mLocation[1]; - // For our size changed check, we keep mScreenRect.width() and mScreenRect.height() - // in view local space. - mLocation[0] = getWidth(); - mLocation[1] = getHeight(); - - mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop, - mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]); - - if (mTranslator != null) { - mTranslator.translateRectInAppWindowToScreen(mScreenRect); - } - - if (mSurfaceControl == null) { - return; - } - - if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) { - try { - if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " + - "postion = [%d, %d, %d, %d]", System.identityHashCode(this), - mScreenRect.left, mScreenRect.top, - mScreenRect.right, mScreenRect.bottom)); - setParentSpaceRectangle(mScreenRect, -1); - } catch (Exception ex) { - Log.e(TAG, "Exception configuring surface", ex); - } - } - } - } - } - - private void onDrawFinished() { - if (DEBUG) { - Log.i(TAG, System.identityHashCode(this) + " " - + "finishedDrawing"); - } - - if (mDeferredDestroySurfaceControl != null) { - mDeferredDestroySurfaceControl.destroy(); - mDeferredDestroySurfaceControl = null; - } - - runOnUiThread(() -> { - performDrawFinished(); - }); - } - - private void setParentSpaceRectangle(Rect position, long frameNumber) { - ViewRootImpl viewRoot = getViewRootImpl(); - - SurfaceControl.openTransaction(); - try { - if (frameNumber > 0) { - mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber); - } - mSurfaceControl.setPosition(position.left, position.top); - mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth, - 0.0f, 0.0f, - position.height() / (float) mSurfaceHeight); - } finally { - SurfaceControl.closeTransaction(); - } - } - - private Rect mRTLastReportedPosition = new Rect(); - - /** - * Called by native by a Rendering Worker thread to update the window position - * @hide - */ - public final void updateSurfacePosition_renderWorker(long frameNumber, - int left, int top, int right, int bottom) { - if (mSurfaceControl == null) { - return; - } - - // TODO: This is teensy bit racey in that a brand new SurfaceView moving on - // its 2nd frame if RenderThread is running slowly could potentially see - // this as false, enter the branch, get pre-empted, then this comes along - // and reports a new position, then the UI thread resumes and reports - // its position. This could therefore be de-sync'd in that interval, but - // the synchronization would violate the rule that RT must never block - // on the UI thread which would open up potential deadlocks. The risk of - // a single-frame desync is therefore preferable for now. - mRtHandlingPositionUpdates = true; - if (mRTLastReportedPosition.left == left - && mRTLastReportedPosition.top == top - && mRTLastReportedPosition.right == right - && mRTLastReportedPosition.bottom == bottom) { - return; - } - try { - if (DEBUG) { - Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " + - "postion = [%d, %d, %d, %d]", System.identityHashCode(this), - frameNumber, left, top, right, bottom)); - } - mRTLastReportedPosition.set(left, top, right, bottom); - setParentSpaceRectangle(mRTLastReportedPosition, frameNumber); - // Now overwrite mRTLastReportedPosition with our values - } catch (Exception ex) { - Log.e(TAG, "Exception from repositionChild", ex); - } - } - - /** - * Called by native on RenderThread to notify that the view is no longer in the - * draw tree. UI thread is blocked at this point. - * @hide - */ - public final void surfacePositionLost_uiRtSync(long frameNumber) { - if (DEBUG) { - Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d", - System.identityHashCode(this), frameNumber)); - } - mRTLastReportedPosition.setEmpty(); - - if (mSurfaceControl == null) { - return; - } - if (mRtHandlingPositionUpdates) { - mRtHandlingPositionUpdates = false; - // This callback will happen while the UI thread is blocked, so we can - // safely access other member variables at this time. - // So do what the UI thread would have done if RT wasn't handling position - // updates. - if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) { - try { - if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " + - "postion = [%d, %d, %d, %d]", System.identityHashCode(this), - mScreenRect.left, mScreenRect.top, - mScreenRect.right, mScreenRect.bottom)); - setParentSpaceRectangle(mScreenRect, frameNumber); - } catch (Exception ex) { - Log.e(TAG, "Exception configuring surface", ex); - } - } - } - } - - private SurfaceHolder.Callback[] getSurfaceCallbacks() { - SurfaceHolder.Callback callbacks[]; - synchronized (mCallbacks) { - callbacks = new SurfaceHolder.Callback[mCallbacks.size()]; - mCallbacks.toArray(callbacks); - } - return callbacks; - } - - /** - * This method still exists only for compatibility reasons because some applications have relied - * on this method via reflection. See Issue 36345857 for details. - * - * @deprecated No platform code is using this method anymore. - * @hide - */ - @Deprecated - public void setWindowType(int type) { - if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) { - throw new UnsupportedOperationException( - "SurfaceView#setWindowType() has never been a public API."); - } - - if (type == TYPE_APPLICATION_PANEL) { - Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) " - + "just to make the SurfaceView to be placed on top of its window, you must " - + "call setZOrderOnTop(true) instead.", new Throwable()); - setZOrderOnTop(true); - return; - } - Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. " - + "type=" + type, new Throwable()); - } - - private void runOnUiThread(Runnable runnable) { - Handler handler = getHandler(); - if (handler != null && handler.getLooper() != Looper.myLooper()) { - handler.post(runnable); - } else { - runnable.run(); - } - } - - /** - * Check to see if the surface has fixed size dimensions or if the surface's - * dimensions are dimensions are dependent on its current layout. - * - * @return true if the surface has dimensions that are fixed in size - * @hide - */ - public boolean isFixedSize() { - return (mRequestedWidth != -1 || mRequestedHeight != -1); - } - - private boolean isAboveParent() { - return mSubLayer >= 0; + public SurfaceHolder getHolder() { + return mSurfaceHolder; } - private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() { - private static final String LOG_TAG = "SurfaceHolder"; + private SurfaceHolder mSurfaceHolder = new SurfaceHolder() { @Override public boolean isCreating() { - return mIsCreating; + return false; } @Override public void addCallback(Callback callback) { - synchronized (mCallbacks) { - // This is a linear search, but in practice we'll - // have only a couple callbacks, so it doesn't matter. - if (mCallbacks.contains(callback) == false) { - mCallbacks.add(callback); - } - } } @Override public void removeCallback(Callback callback) { - synchronized (mCallbacks) { - mCallbacks.remove(callback); - } } @Override public void setFixedSize(int width, int height) { - if (mRequestedWidth != width || mRequestedHeight != height) { - mRequestedWidth = width; - mRequestedHeight = height; - requestLayout(); - } } @Override public void setSizeFromLayout() { - if (mRequestedWidth != -1 || mRequestedHeight != -1) { - mRequestedWidth = mRequestedHeight = -1; - requestLayout(); - } } @Override public void setFormat(int format) { - // for backward compatibility reason, OPAQUE always - // means 565 for SurfaceView - if (format == PixelFormat.OPAQUE) - format = PixelFormat.RGB_565; - - mRequestedFormat = format; - if (mSurfaceControl != null) { - updateSurface(); - } } - /** - * @deprecated setType is now ignored. - */ @Override - @Deprecated - public void setType(int type) { } + public void setType(int type) { + } @Override public void setKeepScreenOn(boolean screenOn) { - runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn)); } - /** - * Gets a {@link Canvas} for drawing into the SurfaceView's Surface - * - * After drawing into the provided {@link Canvas}, the caller must - * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface. - * - * The caller must redraw the entire surface. - * @return A canvas for drawing into the surface. - */ @Override public Canvas lockCanvas() { - return internalLockCanvas(null, false); - } - - /** - * Gets a {@link Canvas} for drawing into the SurfaceView's Surface - * - * After drawing into the provided {@link Canvas}, the caller must - * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface. - * - * @param inOutDirty A rectangle that represents the dirty region that the caller wants - * to redraw. This function may choose to expand the dirty rectangle if for example - * the surface has been resized or if the previous contents of the surface were - * not available. The caller must redraw the entire dirty region as represented - * by the contents of the inOutDirty rectangle upon return from this function. - * The caller may also pass null instead, in the case where the - * entire surface should be redrawn. - * @return A canvas for drawing into the surface. - */ - @Override - public Canvas lockCanvas(Rect inOutDirty) { - return internalLockCanvas(inOutDirty, false); + return null; } @Override - public Canvas lockHardwareCanvas() { - return internalLockCanvas(null, true); - } - - private Canvas internalLockCanvas(Rect dirty, boolean hardware) { - mSurfaceLock.lock(); - - if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped=" - + mDrawingStopped + ", surfaceControl=" + mSurfaceControl); - - Canvas c = null; - if (!mDrawingStopped && mSurfaceControl != null) { - try { - if (hardware) { - c = mSurface.lockHardwareCanvas(); - } else { - c = mSurface.lockCanvas(dirty); - } - } catch (Exception e) { - Log.e(LOG_TAG, "Exception locking surface", e); - } - } - - if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c); - if (c != null) { - mLastLockTime = SystemClock.uptimeMillis(); - return c; - } - - // If the Surface is not ready to be drawn, then return null, - // but throttle calls to this function so it isn't called more - // than every 100ms. - long now = SystemClock.uptimeMillis(); - long nextTime = mLastLockTime + 100; - if (nextTime > now) { - try { - Thread.sleep(nextTime-now); - } catch (InterruptedException e) { - } - now = SystemClock.uptimeMillis(); - } - mLastLockTime = now; - mSurfaceLock.unlock(); - + public Canvas lockCanvas(Rect dirty) { return null; } - /** - * Posts the new contents of the {@link Canvas} to the surface and - * releases the {@link Canvas}. - * - * @param canvas The canvas previously obtained from {@link #lockCanvas}. - */ @Override public void unlockCanvasAndPost(Canvas canvas) { - mSurface.unlockCanvasAndPost(canvas); - mSurfaceLock.unlock(); } @Override public Surface getSurface() { - return mSurface; + return null; } @Override public Rect getSurfaceFrame() { - return mSurfaceFrame; + return null; } }; - - class SurfaceControlWithBackground extends SurfaceControl { - private SurfaceControl mBackgroundControl; - private boolean mOpaque = true; - public boolean mVisible = false; - - public SurfaceControlWithBackground(String name, boolean opaque, SurfaceControl.Builder b) - throws Exception { - super(b.setName(name).build()); - - mBackgroundControl = b.setName("Background for -" + name) - .setFormat(OPAQUE) - .setColorLayer(true) - .build(); - mOpaque = opaque; - } - - @Override - public void setAlpha(float alpha) { - super.setAlpha(alpha); - mBackgroundControl.setAlpha(alpha); - } - - @Override - public void setLayer(int zorder) { - super.setLayer(zorder); - // -3 is below all other child layers as SurfaceView never goes below -2 - mBackgroundControl.setLayer(-3); - } - - @Override - public void setPosition(float x, float y) { - super.setPosition(x, y); - mBackgroundControl.setPosition(x, y); - } - - @Override - public void setSize(int w, int h) { - super.setSize(w, h); - mBackgroundControl.setSize(w, h); - } - - @Override - public void setWindowCrop(Rect crop) { - super.setWindowCrop(crop); - mBackgroundControl.setWindowCrop(crop); - } - - @Override - public void setFinalCrop(Rect crop) { - super.setFinalCrop(crop); - mBackgroundControl.setFinalCrop(crop); - } - - @Override - public void setLayerStack(int layerStack) { - super.setLayerStack(layerStack); - mBackgroundControl.setLayerStack(layerStack); - } - - @Override - public void setOpaque(boolean isOpaque) { - super.setOpaque(isOpaque); - mOpaque = isOpaque; - updateBackgroundVisibility(); - } - - @Override - public void setSecure(boolean isSecure) { - super.setSecure(isSecure); - } - - @Override - public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { - super.setMatrix(dsdx, dtdx, dsdy, dtdy); - mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy); - } - - @Override - public void hide() { - super.hide(); - mVisible = false; - updateBackgroundVisibility(); - } - - @Override - public void show() { - super.show(); - mVisible = true; - updateBackgroundVisibility(); - } - - @Override - public void destroy() { - super.destroy(); - mBackgroundControl.destroy(); - } - - @Override - public void release() { - super.release(); - mBackgroundControl.release(); - } - - @Override - public void setTransparentRegionHint(Region region) { - super.setTransparentRegionHint(region); - mBackgroundControl.setTransparentRegionHint(region); - } - - @Override - public void deferTransactionUntil(IBinder handle, long frame) { - super.deferTransactionUntil(handle, frame); - mBackgroundControl.deferTransactionUntil(handle, frame); - } - - @Override - public void deferTransactionUntil(Surface barrier, long frame) { - super.deferTransactionUntil(barrier, frame); - mBackgroundControl.deferTransactionUntil(barrier, frame); - } - - void updateBackgroundVisibility() { - if (mOpaque && mVisible) { - mBackgroundControl.show(); - } else { - mBackgroundControl.hide(); - } - } - } } + diff --git a/android/view/ThreadedRenderer.java b/android/view/ThreadedRenderer.java index 7c76bab2..6a8f8b12 100644 --- a/android/view/ThreadedRenderer.java +++ b/android/view/ThreadedRenderer.java @@ -190,6 +190,17 @@ public final class ThreadedRenderer { public static final String DEBUG_SHOW_NON_RECTANGULAR_CLIP_PROPERTY = "debug.hwui.show_non_rect_clip"; + /** + * Sets the FPS devisor to lower the FPS. + * + * Sets a positive integer as a divisor. 1 (the default value) menas the full FPS, and 2 + * means half the full FPS. + * + * + * @hide + */ + public static final String DEBUG_FPS_DIVISOR = "debug.hwui.fps_divisor"; + static { // Try to check OpenGL support early if possible. isAvailable(); @@ -333,8 +344,10 @@ public final class ThreadedRenderer { private static final int FLAG_DUMP_FRAMESTATS = 1 << 0; private static final int FLAG_DUMP_RESET = 1 << 1; - @IntDef(flag = true, value = { - FLAG_DUMP_FRAMESTATS, FLAG_DUMP_RESET }) + @IntDef(flag = true, prefix = { "FLAG_DUMP_" }, value = { + FLAG_DUMP_FRAMESTATS, + FLAG_DUMP_RESET + }) @Retention(RetentionPolicy.SOURCE) public @interface DumpFlags {} @@ -955,6 +968,9 @@ public final class ThreadedRenderer { if (mInitialized) return; mInitialized = true; mAppContext = context.getApplicationContext(); + + // b/68769804: For low FPS experiments. + setFPSDivisor(SystemProperties.getInt(DEBUG_FPS_DIVISOR, 1)); initSched(renderProxy); initGraphicsStats(); } @@ -1007,6 +1023,13 @@ public final class ThreadedRenderer { observer.mNative = null; } + /** b/68769804: For low FPS experiments. */ + public static void setFPSDivisor(int divisor) { + if (divisor <= 0) divisor = 1; + Choreographer.getInstance().setFPSDivisor(divisor); + nHackySetRTAnimationsEnabled(divisor == 1); + } + /** Not actually public - internal use only. This doc to make lint happy */ public static native void disableVsync(); @@ -1075,4 +1098,6 @@ public final class ThreadedRenderer { private static native Bitmap nCreateHardwareBitmap(long renderNode, int width, int height); private static native void nSetHighContrastText(boolean enabled); + // For temporary experimentation b/66945974 + private static native void nHackySetRTAnimationsEnabled(boolean enabled); } diff --git a/android/view/View.java b/android/view/View.java index 0525ab16..cc63a62c 100644 --- a/android/view/View.java +++ b/android/view/View.java @@ -893,6 +893,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static boolean sAutoFocusableOffUIThreadWontNotifyParents; + /** + * Prior to P things like setScaleX() allowed passing float values that were bogus such as + * Float.NaN. If the app is targetting P or later then passing these values will result in an + * exception being thrown. If the app is targetting an earlier SDK version, then we will + * silently clamp these values to avoid crashes elsewhere when the rendering code hits + * these bogus values. + */ + private static boolean sThrowOnInvalidFloatProperties; + /** @hide */ @IntDef({NOT_FOCUSABLE, FOCUSABLE, FOCUSABLE_AUTO}) @Retention(RetentionPolicy.SOURCE) @@ -1169,7 +1178,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private AutofillId mAutofillId; /** @hide */ - @IntDef({ + @IntDef(prefix = { "AUTOFILL_TYPE_" }, value = { AUTOFILL_TYPE_NONE, AUTOFILL_TYPE_TEXT, AUTOFILL_TYPE_TOGGLE, @@ -1240,7 +1249,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static final int AUTOFILL_TYPE_DATE = 4; /** @hide */ - @IntDef({ + @IntDef(prefix = { "IMPORTANT_FOR_AUTOFILL_" }, value = { IMPORTANT_FOR_AUTOFILL_AUTO, IMPORTANT_FOR_AUTOFILL_YES, IMPORTANT_FOR_AUTOFILL_NO, @@ -1291,9 +1300,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static final int IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS = 0x8; /** @hide */ - @IntDef( - flag = true, - value = {AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS}) + @IntDef(flag = true, prefix = { "AUTOFILL_FLAG_" }, value = { + AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS + }) @Retention(RetentionPolicy.SOURCE) public @interface AutofillFlags {} @@ -1443,7 +1452,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({DRAWING_CACHE_QUALITY_LOW, DRAWING_CACHE_QUALITY_HIGH, DRAWING_CACHE_QUALITY_AUTO}) + @IntDef(prefix = { "DRAWING_CACHE_QUALITY_" }, value = { + DRAWING_CACHE_QUALITY_LOW, + DRAWING_CACHE_QUALITY_HIGH, + DRAWING_CACHE_QUALITY_AUTO + }) public @interface DrawingCacheQuality {} /** @@ -1542,13 +1555,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int CONTEXT_CLICKABLE = 0x00800000; - /** @hide */ - @IntDef({ - SCROLLBARS_INSIDE_OVERLAY, - SCROLLBARS_INSIDE_INSET, - SCROLLBARS_OUTSIDE_OVERLAY, - SCROLLBARS_OUTSIDE_INSET + @IntDef(prefix = { "SCROLLBARS_" }, value = { + SCROLLBARS_INSIDE_OVERLAY, + SCROLLBARS_INSIDE_INSET, + SCROLLBARS_OUTSIDE_OVERLAY, + SCROLLBARS_OUTSIDE_INSET }) @Retention(RetentionPolicy.SOURCE) public @interface ScrollBarStyle {} @@ -1642,11 +1654,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, static final int TOOLTIP = 0x40000000; /** @hide */ - @IntDef(flag = true, - value = { - FOCUSABLES_ALL, - FOCUSABLES_TOUCH_MODE - }) + @IntDef(flag = true, prefix = { "FOCUSABLES_" }, value = { + FOCUSABLES_ALL, + FOCUSABLES_TOUCH_MODE + }) @Retention(RetentionPolicy.SOURCE) public @interface FocusableMode {} @@ -1663,7 +1674,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static final int FOCUSABLES_TOUCH_MODE = 0x00000001; /** @hide */ - @IntDef({ + @IntDef(prefix = { "FOCUS_" }, value = { FOCUS_BACKWARD, FOCUS_FORWARD, FOCUS_LEFT, @@ -1675,7 +1686,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public @interface FocusDirection {} /** @hide */ - @IntDef({ + @IntDef(prefix = { "FOCUS_" }, value = { FOCUS_LEFT, FOCUS_UP, FOCUS_RIGHT, @@ -2417,20 +2428,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, static final int PFLAG2_DRAG_HOVERED = 0x00000002; /** @hide */ - @IntDef({ - LAYOUT_DIRECTION_LTR, - LAYOUT_DIRECTION_RTL, - LAYOUT_DIRECTION_INHERIT, - LAYOUT_DIRECTION_LOCALE + @IntDef(prefix = { "LAYOUT_DIRECTION_" }, value = { + LAYOUT_DIRECTION_LTR, + LAYOUT_DIRECTION_RTL, + LAYOUT_DIRECTION_INHERIT, + LAYOUT_DIRECTION_LOCALE }) @Retention(RetentionPolicy.SOURCE) // Not called LayoutDirection to avoid conflict with android.util.LayoutDirection public @interface LayoutDir {} /** @hide */ - @IntDef({ - LAYOUT_DIRECTION_LTR, - LAYOUT_DIRECTION_RTL + @IntDef(prefix = { "LAYOUT_DIRECTION_" }, value = { + LAYOUT_DIRECTION_LTR, + LAYOUT_DIRECTION_RTL }) @Retention(RetentionPolicy.SOURCE) public @interface ResolvedLayoutDir {} @@ -2636,14 +2647,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, TEXT_DIRECTION_RESOLVED_DEFAULT << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT; /** @hide */ - @IntDef({ - TEXT_ALIGNMENT_INHERIT, - TEXT_ALIGNMENT_GRAVITY, - TEXT_ALIGNMENT_CENTER, - TEXT_ALIGNMENT_TEXT_START, - TEXT_ALIGNMENT_TEXT_END, - TEXT_ALIGNMENT_VIEW_START, - TEXT_ALIGNMENT_VIEW_END + @IntDef(prefix = { "TEXT_ALIGNMENT_" }, value = { + TEXT_ALIGNMENT_INHERIT, + TEXT_ALIGNMENT_GRAVITY, + TEXT_ALIGNMENT_CENTER, + TEXT_ALIGNMENT_TEXT_START, + TEXT_ALIGNMENT_TEXT_END, + TEXT_ALIGNMENT_VIEW_START, + TEXT_ALIGNMENT_VIEW_END }) @Retention(RetentionPolicy.SOURCE) public @interface TextAlignment {} @@ -3040,15 +3051,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, - value = { - SCROLL_INDICATOR_TOP, - SCROLL_INDICATOR_BOTTOM, - SCROLL_INDICATOR_LEFT, - SCROLL_INDICATOR_RIGHT, - SCROLL_INDICATOR_START, - SCROLL_INDICATOR_END, - }) + @IntDef(flag = true, prefix = { "SCROLL_INDICATOR_" }, value = { + SCROLL_INDICATOR_TOP, + SCROLL_INDICATOR_BOTTOM, + SCROLL_INDICATOR_LEFT, + SCROLL_INDICATOR_RIGHT, + SCROLL_INDICATOR_START, + SCROLL_INDICATOR_END, + }) public @interface ScrollIndicators {} /** @@ -3674,8 +3684,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; /** @hide */ - @IntDef(flag = true, - value = { FIND_VIEWS_WITH_TEXT, FIND_VIEWS_WITH_CONTENT_DESCRIPTION }) + @IntDef(flag = true, prefix = { "FIND_VIEWS_" }, value = { + FIND_VIEWS_WITH_TEXT, + FIND_VIEWS_WITH_CONTENT_DESCRIPTION + }) @Retention(RetentionPolicy.SOURCE) public @interface FindViewFlags {} @@ -4287,6 +4299,38 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ Runnable mShowTooltipRunnable; Runnable mHideTooltipRunnable; + + /** + * Hover move is ignored if it is within this distance in pixels from the previous one. + */ + int mHoverSlop; + + /** + * Update the anchor position if it significantly (that is by at least mHoverSlop) + * different from the previously stored position. Ignoring insignificant changes + * filters out the jitter which is typical for such input sources as stylus. + * + * @return True if the position has been updated. + */ + private boolean updateAnchorPos(MotionEvent event) { + final int newAnchorX = (int) event.getX(); + final int newAnchorY = (int) event.getY(); + if (Math.abs(newAnchorX - mAnchorX) <= mHoverSlop + && Math.abs(newAnchorY - mAnchorY) <= mHoverSlop) { + return false; + } + mAnchorX = newAnchorX; + mAnchorY = newAnchorY; + return true; + } + + /** + * Clear the anchor position to ensure that the next change is considered significant. + */ + private void clearAnchorPos() { + mAnchorX = Integer.MAX_VALUE; + mAnchorY = Integer.MAX_VALUE; + } } TooltipInfo mTooltipInfo; @@ -4752,6 +4796,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sUseDefaultFocusHighlight = context.getResources().getBoolean( com.android.internal.R.bool.config_useDefaultFocusHighlight); + sThrowOnInvalidFloatProperties = targetSdkVersion >= Build.VERSION_CODES.P; + sCompatibilityDone = true; } } @@ -7208,8 +7254,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param text The announcement text. */ public void announceForAccessibility(CharSequence text) { - if (AccessibilityManager.getInstance(mContext).isObservedEventType( - AccessibilityEvent.TYPE_ANNOUNCEMENT) && mParent != null) { + if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_ANNOUNCEMENT); onInitializeAccessibilityEvent(event); @@ -10968,8 +11013,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0) { mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED; invalidate(); - if (AccessibilityManager.getInstance(mContext).isObservedEventType( - AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED)) { + if (AccessibilityManager.getInstance(mContext).isEnabled()) { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); event.setAction(action); @@ -11794,8 +11838,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private void sendViewTextTraversedAtGranularityEvent(int action, int granularity, int fromIndex, int toIndex) { - if (mParent == null || !AccessibilityManager.getInstance(mContext).isObservedEventType( - AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY)) { + if (mParent == null) { return; } AccessibilityEvent event = AccessibilityEvent.obtain( @@ -14275,7 +14318,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public void setScaleX(float scaleX) { if (scaleX != getScaleX()) { - requireIsFinite(scaleX, "scaleX"); + scaleX = sanitizeFloatPropertyValue(scaleX, "scaleX"); invalidateViewProperty(true, false); mRenderNode.setScaleX(scaleX); invalidateViewProperty(false, true); @@ -14312,7 +14355,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public void setScaleY(float scaleY) { if (scaleY != getScaleY()) { - requireIsFinite(scaleY, "scaleY"); + scaleY = sanitizeFloatPropertyValue(scaleY, "scaleY"); invalidateViewProperty(true, false); mRenderNode.setScaleY(scaleY); invalidateViewProperty(false, true); @@ -14862,13 +14905,41 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - private static void requireIsFinite(float transform, String propertyName) { - if (Float.isNaN(transform)) { - throw new IllegalArgumentException("Cannot set '" + propertyName + "' to Float.NaN"); + private static float sanitizeFloatPropertyValue(float value, String propertyName) { + return sanitizeFloatPropertyValue(value, propertyName, -Float.MAX_VALUE, Float.MAX_VALUE); + } + + private static float sanitizeFloatPropertyValue(float value, String propertyName, + float min, float max) { + // The expected "nothing bad happened" path + if (value >= min && value <= max) return value; + + if (value < min || value == Float.NEGATIVE_INFINITY) { + if (sThrowOnInvalidFloatProperties) { + throw new IllegalArgumentException("Cannot set '" + propertyName + "' to " + + value + ", the value must be >= " + min); + } + return min; + } + + if (value > max || value == Float.POSITIVE_INFINITY) { + if (sThrowOnInvalidFloatProperties) { + throw new IllegalArgumentException("Cannot set '" + propertyName + "' to " + + value + ", the value must be <= " + max); + } + return max; } - if (Float.isInfinite(transform)) { - throw new IllegalArgumentException("Cannot set '" + propertyName + "' to infinity"); + + if (Float.isNaN(value)) { + if (sThrowOnInvalidFloatProperties) { + throw new IllegalArgumentException( + "Cannot set '" + propertyName + "' to Float.NaN"); + } + return 0; // Unclear which direction this NaN went so... 0? } + + // Shouldn't be possible to reach this. + throw new IllegalStateException("How do you get here?? " + value); } /** @@ -14957,7 +15028,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public void setElevation(float elevation) { if (elevation != getElevation()) { - requireIsFinite(elevation, "elevation"); + elevation = sanitizeFloatPropertyValue(elevation, "elevation"); invalidateViewProperty(true, false); mRenderNode.setElevation(elevation); invalidateViewProperty(false, true); @@ -15050,7 +15121,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public void setTranslationZ(float translationZ) { if (translationZ != getTranslationZ()) { - requireIsFinite(translationZ, "translationZ"); + translationZ = sanitizeFloatPropertyValue(translationZ, "translationZ"); invalidateViewProperty(true, false); mRenderNode.setTranslationZ(translationZ); invalidateViewProperty(false, true); @@ -25721,6 +25792,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ final Rect mStableInsets = new Rect(); + final DisplayCutout.ParcelableWrapper mDisplayCutout = + new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT); + /** * For windows that include areas that are not covered by real surface these are the outsets * for real surface. @@ -26185,8 +26259,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @Override public void run() { - if (AccessibilityManager.getInstance(mContext).isObservedEventType( - AccessibilityEvent.TYPE_VIEW_SCROLLED)) { + if (AccessibilityManager.getInstance(mContext).isEnabled()) { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_VIEW_SCROLLED); event.setScrollDeltaX(mDeltaX); @@ -26775,6 +26848,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mTooltipInfo = new TooltipInfo(); mTooltipInfo.mShowTooltipRunnable = this::showHoverTooltip; mTooltipInfo.mHideTooltipRunnable = this::hideTooltip; + mTooltipInfo.mHoverSlop = ViewConfiguration.get(mContext).getScaledHoverSlop(); + mTooltipInfo.clearAnchorPos(); } mTooltipInfo.mTooltipText = tooltipText; } @@ -26815,7 +26890,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mAttachInfo == null || mTooltipInfo == null) { return false; } - if ((mViewFlags & ENABLED_MASK) != ENABLED) { + if (fromLongClick && (mViewFlags & ENABLED_MASK) != ENABLED) { return false; } if (TextUtils.isEmpty(mTooltipInfo.mTooltipText)) { @@ -26841,6 +26916,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mTooltipInfo.mTooltipPopup.hide(); mTooltipInfo.mTooltipPopup = null; mTooltipInfo.mTooltipFromLongClick = false; + mTooltipInfo.clearAnchorPos(); if (mAttachInfo != null) { mAttachInfo.mTooltipHost = null; } @@ -26862,14 +26938,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } switch(event.getAction()) { case MotionEvent.ACTION_HOVER_MOVE: - if ((mViewFlags & TOOLTIP) != TOOLTIP || (mViewFlags & ENABLED_MASK) != ENABLED) { + if ((mViewFlags & TOOLTIP) != TOOLTIP) { break; } - if (!mTooltipInfo.mTooltipFromLongClick) { + if (!mTooltipInfo.mTooltipFromLongClick && mTooltipInfo.updateAnchorPos(event)) { if (mTooltipInfo.mTooltipPopup == null) { // Schedule showing the tooltip after a timeout. - mTooltipInfo.mAnchorX = (int) event.getX(); - mTooltipInfo.mAnchorY = (int) event.getY(); removeCallbacks(mTooltipInfo.mShowTooltipRunnable); postDelayed(mTooltipInfo.mShowTooltipRunnable, ViewConfiguration.getHoverTooltipShowTimeout()); @@ -26891,6 +26965,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return true; case MotionEvent.ACTION_HOVER_EXIT: + mTooltipInfo.clearAnchorPos(); if (!mTooltipInfo.mTooltipFromLongClick) { hideTooltip(); } diff --git a/android/view/ViewConfiguration.java b/android/view/ViewConfiguration.java index c44c8dda..c5a94daa 100644 --- a/android/view/ViewConfiguration.java +++ b/android/view/ViewConfiguration.java @@ -290,6 +290,7 @@ public class ViewConfiguration { private final int mMaximumFlingVelocity; private final int mScrollbarSize; private final int mTouchSlop; + private final int mHoverSlop; private final int mMinScrollbarTouchTarget; private final int mDoubleTapTouchSlop; private final int mPagingTouchSlop; @@ -320,6 +321,7 @@ public class ViewConfiguration { mMaximumFlingVelocity = MAXIMUM_FLING_VELOCITY; mScrollbarSize = SCROLL_BAR_SIZE; mTouchSlop = TOUCH_SLOP; + mHoverSlop = TOUCH_SLOP / 2; mMinScrollbarTouchTarget = MIN_SCROLLBAR_TOUCH_TARGET; mDoubleTapTouchSlop = DOUBLE_TAP_TOUCH_SLOP; mPagingTouchSlop = PAGING_TOUCH_SLOP; @@ -407,6 +409,8 @@ public class ViewConfiguration { com.android.internal.R.bool.config_ui_enableFadingMarquee); mTouchSlop = res.getDimensionPixelSize( com.android.internal.R.dimen.config_viewConfigurationTouchSlop); + mHoverSlop = res.getDimensionPixelSize( + com.android.internal.R.dimen.config_viewConfigurationHoverSlop); mMinScrollbarTouchTarget = res.getDimensionPixelSize( com.android.internal.R.dimen.config_minScrollbarTouchTarget); mPagingTouchSlop = mTouchSlop * 2; @@ -639,6 +643,14 @@ public class ViewConfiguration { return mTouchSlop; } + /** + * @return Distance in pixels a hover can wander while it is still considered "stationary". + * + */ + public int getScaledHoverSlop() { + return mHoverSlop; + } + /** * @return Distance in pixels the first touch can wander before we do not consider this a * potential double tap event diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java index 1c9d8639..6c5091c2 100644 --- a/android/view/ViewRootImpl.java +++ b/android/view/ViewRootImpl.java @@ -20,6 +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.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; @@ -142,10 +143,11 @@ public final class ViewRootImpl implements ViewParent, private static final boolean DEBUG_KEEP_SCREEN_ON = false || LOCAL_LOGV; /** - * Set to false if we do not want to use the multi threaded renderer. Note that by disabling + * Set to false if we do not want to use the multi threaded renderer even though + * threaded renderer (aka hardware renderering) is used. Note that by disabling * this, WindowCallbacks will not fire. */ - private static final boolean USE_MT_RENDERER = true; + private static final boolean MT_RENDERER_AVAILABLE = true; /** * Set this system property to true to force the view hierarchy to render @@ -302,6 +304,7 @@ public final class ViewRootImpl implements ViewParent, Rect mDirty; public boolean mIsAnimating; + private boolean mUseMTRenderer; private boolean mDragResizing; private boolean mInvalidateRootRequested; private int mResizeMode; @@ -321,6 +324,15 @@ public final class ViewRootImpl implements ViewParent, final Rect mTempRect; // used in the transaction to not thrash the heap. final Rect mVisRect; // used to retrieve visible rect of focused view. + // This is used to reduce the race between window focus changes being dispatched from + // the window manager and input events coming through the input system. + @GuardedBy("this") + boolean mWindowFocusChanged; + @GuardedBy("this") + boolean mUpcomingWindowFocus; + @GuardedBy("this") + boolean mUpcomingInTouchMode; + public boolean mTraversalScheduled; int mTraversalBarrier; boolean mWillDrawSoon; @@ -384,12 +396,15 @@ public final class ViewRootImpl implements ViewParent, final Rect mPendingContentInsets = new Rect(); final Rect mPendingOutsets = new Rect(); final Rect mPendingBackDropFrame = new Rect(); + final DisplayCutout.ParcelableWrapper mPendingDisplayCutout = + new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT); boolean mPendingAlwaysConsumeNavBar; final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets = new ViewTreeObserver.InternalInsetsInfo(); final Rect mDispatchContentInsets = new Rect(); final Rect mDispatchStableInsets = new Rect(); + DisplayCutout mDispatchDisplayCutout = DisplayCutout.NO_CUTOUT; private WindowInsets mLastWindowInsets; @@ -545,18 +560,14 @@ public final class ViewRootImpl implements ViewParent, } public void addWindowCallbacks(WindowCallbacks callback) { - if (USE_MT_RENDERER) { - synchronized (mWindowCallbacks) { - mWindowCallbacks.add(callback); - } + synchronized (mWindowCallbacks) { + mWindowCallbacks.add(callback); } } public void removeWindowCallbacks(WindowCallbacks callback) { - if (USE_MT_RENDERER) { - synchronized (mWindowCallbacks) { - mWindowCallbacks.remove(callback); - } + synchronized (mWindowCallbacks) { + mWindowCallbacks.remove(callback); } } @@ -682,7 +693,17 @@ public final class ViewRootImpl implements ViewParent, // If the application owns the surface, don't enable hardware acceleration if (mSurfaceHolder == null) { + // While this is supposed to enable only, it can effectively disable + // the acceleration too. enableHardwareAcceleration(attrs); + final boolean useMTRenderer = MT_RENDERER_AVAILABLE + && mAttachInfo.mThreadedRenderer != null; + if (mUseMTRenderer != useMTRenderer) { + // Shouldn't be resizing, as it's done only in window setup, + // but end just in case. + endDragResizing(); + mUseMTRenderer = useMTRenderer; + } } boolean restore = false; @@ -730,7 +751,7 @@ public final class ViewRootImpl implements ViewParent, res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, - mAttachInfo.mOutsets, mInputChannel); + mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; @@ -752,6 +773,7 @@ public final class ViewRootImpl implements ViewParent, mPendingOverscanInsets.set(0, 0, 0, 0); mPendingContentInsets.set(mAttachInfo.mContentInsets); mPendingStableInsets.set(mAttachInfo.mStableInsets); + mPendingDisplayCutout.set(mAttachInfo.mDisplayCutout); mPendingVisibleInsets.set(0, 0, 0, 0); mAttachInfo.mAlwaysConsumeNavBar = (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR) != 0; @@ -1544,15 +1566,20 @@ public final class ViewRootImpl implements ViewParent, if (mLastWindowInsets == null || forceConstruct) { mDispatchContentInsets.set(mAttachInfo.mContentInsets); mDispatchStableInsets.set(mAttachInfo.mStableInsets); + mDispatchDisplayCutout = mAttachInfo.mDisplayCutout.get(); + Rect contentInsets = mDispatchContentInsets; Rect stableInsets = mDispatchStableInsets; + DisplayCutout displayCutout = mDispatchDisplayCutout; // For dispatch we preserve old logic, but for direct requests from Views we allow to // immediately use pending insets. if (!forceConstruct && (!mPendingContentInsets.equals(contentInsets) || - !mPendingStableInsets.equals(stableInsets))) { + !mPendingStableInsets.equals(stableInsets) || + !mPendingDisplayCutout.get().equals(displayCutout))) { contentInsets = mPendingContentInsets; stableInsets = mPendingStableInsets; + displayCutout = mPendingDisplayCutout.get(); } Rect outsets = mAttachInfo.mOutsets; if (outsets.left > 0 || outsets.top > 0 || outsets.right > 0 || outsets.bottom > 0) { @@ -1563,13 +1590,21 @@ public final class ViewRootImpl implements ViewParent, mLastWindowInsets = new WindowInsets(contentInsets, null /* windowDecorInsets */, stableInsets, mContext.getResources().getConfiguration().isScreenRound(), - mAttachInfo.mAlwaysConsumeNavBar, null /* displayCutout */); + mAttachInfo.mAlwaysConsumeNavBar, displayCutout); } return mLastWindowInsets; } void dispatchApplyInsets(View host) { - host.dispatchApplyWindowInsets(getWindowInsets(true /* forceConstruct */)); + WindowInsets insets = getWindowInsets(true /* forceConstruct */); + final boolean layoutInCutout = + (mWindowAttributes.flags2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0; + if (!layoutInCutout) { + // 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(); + } + host.dispatchApplyWindowInsets(insets); } private static boolean shouldUseDisplaySize(final WindowManager.LayoutParams lp) { @@ -1730,6 +1765,9 @@ public final class ViewRootImpl implements ViewParent, if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) { insetsChanged = true; } + if (!mPendingDisplayCutout.equals(mAttachInfo.mDisplayCutout)) { + insetsChanged = true; + } if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) { mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: " @@ -1906,7 +1944,8 @@ public final class ViewRootImpl implements ViewParent, + " overscan=" + mPendingOverscanInsets.toShortString() + " content=" + mPendingContentInsets.toShortString() + " visible=" + mPendingVisibleInsets.toShortString() - + " visible=" + mPendingStableInsets.toShortString() + + " stable=" + mPendingStableInsets.toShortString() + + " cutout=" + mPendingDisplayCutout.get().toString() + " outsets=" + mPendingOutsets.toShortString() + " surface=" + mSurface); @@ -1931,6 +1970,8 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mVisibleInsets); final boolean stableInsetsChanged = !mPendingStableInsets.equals( mAttachInfo.mStableInsets); + final boolean cutoutChanged = !mPendingDisplayCutout.equals( + mAttachInfo.mDisplayCutout); final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets); final boolean surfaceSizeChanged = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0; @@ -1955,6 +1996,14 @@ public final class ViewRootImpl implements ViewParent, // Need to relayout with content insets. contentInsetsChanged = true; } + if (cutoutChanged) { + mAttachInfo.mDisplayCutout.set(mPendingDisplayCutout); + if (DEBUG_LAYOUT) { + Log.v(mTag, "DisplayCutout changing to: " + mAttachInfo.mDisplayCutout); + } + // Need to relayout with content insets. + contentInsetsChanged = true; + } if (alwaysConsumeNavBarChanged) { mAttachInfo.mAlwaysConsumeNavBar = mPendingAlwaysConsumeNavBar; contentInsetsChanged = true; @@ -2056,6 +2105,7 @@ public final class ViewRootImpl implements ViewParent, mResizeMode = freeformResizing ? RESIZE_MODE_FREEFORM : RESIZE_MODE_DOCKED_DIVIDER; + // TODO: Need cutout? startDragResizing(mPendingBackDropFrame, mWinFrame.equals(mPendingBackDropFrame), mPendingVisibleInsets, mPendingStableInsets, mResizeMode); @@ -2064,7 +2114,7 @@ public final class ViewRootImpl implements ViewParent, endDragResizing(); } } - if (!USE_MT_RENDERER) { + if (!mUseMTRenderer) { if (dragResizing) { mCanvasOffsetX = mWinFrame.left; mCanvasOffsetY = mWinFrame.top; @@ -2420,6 +2470,93 @@ public final class ViewRootImpl implements ViewParent, } } + private void handleWindowFocusChanged() { + final boolean hasWindowFocus; + final boolean inTouchMode; + synchronized (this) { + if (!mWindowFocusChanged) { + return; + } + mWindowFocusChanged = false; + hasWindowFocus = mUpcomingWindowFocus; + inTouchMode = mUpcomingInTouchMode; + } + + if (mAdded) { + profileRendering(hasWindowFocus); + + if (hasWindowFocus) { + ensureTouchModeLocally(inTouchMode); + if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) { + mFullRedrawNeeded = true; + try { + final WindowManager.LayoutParams lp = mWindowAttributes; + final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null; + mAttachInfo.mThreadedRenderer.initializeIfNeeded( + mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets); + } catch (OutOfResourcesException e) { + Log.e(mTag, "OutOfResourcesException locking surface", e); + try { + if (!mWindowSession.outOfMemory(mWindow)) { + Slog.w(mTag, "No processes killed for memory;" + + " killing self"); + Process.killProcess(Process.myPid()); + } + } catch (RemoteException ex) { + } + // Retry in a bit. + mHandler.sendMessageDelayed(mHandler.obtainMessage( + MSG_WINDOW_FOCUS_CHANGED), 500); + return; + } + } + } + + mAttachInfo.mHasWindowFocus = hasWindowFocus; + + mLastWasImTarget = WindowManager.LayoutParams + .mayUseInputMethod(mWindowAttributes.flags); + + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { + imm.onPreWindowFocus(mView, hasWindowFocus); + } + if (mView != null) { + mAttachInfo.mKeyDispatchState.reset(); + mView.dispatchWindowFocusChanged(hasWindowFocus); + mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); + + if (mAttachInfo.mTooltipHost != null) { + mAttachInfo.mTooltipHost.hideTooltip(); + } + } + + // Note: must be done after the focus change callbacks, + // so all of the view state is set up correctly. + if (hasWindowFocus) { + if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { + imm.onPostWindowFocus(mView, mView.findFocus(), + mWindowAttributes.softInputMode, + !mHasHadWindowFocus, mWindowAttributes.flags); + } + // Clear the forward bit. We can just do this directly, since + // the window manager doesn't care about it. + mWindowAttributes.softInputMode &= + ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; + ((WindowManager.LayoutParams) mView.getLayoutParams()) + .softInputMode &= + ~WindowManager.LayoutParams + .SOFT_INPUT_IS_FORWARD_NAVIGATION; + mHasHadWindowFocus = true; + } else { + if (mPointerCapture) { + handlePointerCaptureChanged(false); + } + } + } + mFirstInputStage.onWindowFocusChanged(hasWindowFocus); + } + private void handleOutOfResourcesException(Surface.OutOfResourcesException e) { Log.e(mTag, "OutOfResourcesException initializing HW surface", e); try { @@ -2702,8 +2839,10 @@ public final class ViewRootImpl implements ViewParent, @Override public void onPostDraw(DisplayListCanvas canvas) { drawAccessibilityFocusedDrawableIfNeeded(canvas); - for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) { - mWindowCallbacks.get(i).onPostDraw(canvas); + if (mUseMTRenderer) { + for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) { + mWindowCallbacks.get(i).onPostDraw(canvas); + } } } @@ -3034,7 +3173,8 @@ public final class ViewRootImpl implements ViewParent, return; } - if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { + if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, + scalingRequired, dirty, surfaceInsets)) { return; } } @@ -3050,11 +3190,22 @@ public final class ViewRootImpl implements ViewParent, * @return true if drawing was successful, false if an error occurred */ private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, - boolean scalingRequired, Rect dirty) { + boolean scalingRequired, Rect dirty, Rect surfaceInsets) { // Draw with software renderer. final Canvas canvas; + + // We already have the offset of surfaceInsets in xoff, yoff and dirty region, + // therefore we need to add it back when moving the dirty region. + int dirtyXOffset = xoff; + int dirtyYOffset = yoff; + if (surfaceInsets != null) { + dirtyXOffset += surfaceInsets.left; + dirtyYOffset += surfaceInsets.top; + } + try { + dirty.offset(-dirtyXOffset, -dirtyYOffset); final int left = dirty.left; final int top = dirty.top; final int right = dirty.right; @@ -3081,6 +3232,8 @@ public final class ViewRootImpl implements ViewParent, // kill stuff (or ourself) for no reason. mLayoutRequested = true; // ask wm for a new surface next time. return false; + } finally { + dirty.offset(dirtyXOffset, dirtyYOffset); // Reset to the original value. } try { @@ -3776,6 +3929,7 @@ public final class ViewRootImpl implements ViewParent, && mPendingOverscanInsets.equals(args.arg5) && mPendingContentInsets.equals(args.arg2) && mPendingStableInsets.equals(args.arg6) + && mPendingDisplayCutout.get().equals(args.arg9) && mPendingVisibleInsets.equals(args.arg3) && mPendingOutsets.equals(args.arg7) && mPendingBackDropFrame.equals(args.arg8) @@ -3808,6 +3962,7 @@ public final class ViewRootImpl implements ViewParent, || !mPendingOverscanInsets.equals(args.arg5) || !mPendingContentInsets.equals(args.arg2) || !mPendingStableInsets.equals(args.arg6) + || !mPendingDisplayCutout.get().equals(args.arg9) || !mPendingVisibleInsets.equals(args.arg3) || !mPendingOutsets.equals(args.arg7); @@ -3815,6 +3970,7 @@ public final class ViewRootImpl implements ViewParent, mPendingOverscanInsets.set((Rect) args.arg5); mPendingContentInsets.set((Rect) args.arg2); mPendingStableInsets.set((Rect) args.arg6); + mPendingDisplayCutout.set((DisplayCutout) args.arg9); mPendingVisibleInsets.set((Rect) args.arg3); mPendingOutsets.set((Rect) args.arg7); mPendingBackDropFrame.set((Rect) args.arg8); @@ -3849,81 +4005,7 @@ public final class ViewRootImpl implements ViewParent, } break; case MSG_WINDOW_FOCUS_CHANGED: { - final boolean hasWindowFocus = msg.arg1 != 0; - if (mAdded) { - mAttachInfo.mHasWindowFocus = hasWindowFocus; - - profileRendering(hasWindowFocus); - - if (hasWindowFocus) { - boolean inTouchMode = msg.arg2 != 0; - ensureTouchModeLocally(inTouchMode); - if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) { - mFullRedrawNeeded = true; - try { - final WindowManager.LayoutParams lp = mWindowAttributes; - final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null; - mAttachInfo.mThreadedRenderer.initializeIfNeeded( - mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets); - } catch (OutOfResourcesException e) { - Log.e(mTag, "OutOfResourcesException locking surface", e); - try { - if (!mWindowSession.outOfMemory(mWindow)) { - Slog.w(mTag, "No processes killed for memory;" - + " killing self"); - Process.killProcess(Process.myPid()); - } - } catch (RemoteException ex) { - } - // Retry in a bit. - sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), - 500); - return; - } - } - } - - mLastWasImTarget = WindowManager.LayoutParams - .mayUseInputMethod(mWindowAttributes.flags); - - InputMethodManager imm = InputMethodManager.peekInstance(); - if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { - imm.onPreWindowFocus(mView, hasWindowFocus); - } - if (mView != null) { - mAttachInfo.mKeyDispatchState.reset(); - mView.dispatchWindowFocusChanged(hasWindowFocus); - mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); - - if (mAttachInfo.mTooltipHost != null) { - mAttachInfo.mTooltipHost.hideTooltip(); - } - } - - // Note: must be done after the focus change callbacks, - // so all of the view state is set up correctly. - if (hasWindowFocus) { - if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { - imm.onPostWindowFocus(mView, mView.findFocus(), - mWindowAttributes.softInputMode, - !mHasHadWindowFocus, mWindowAttributes.flags); - } - // Clear the forward bit. We can just do this directly, since - // the window manager doesn't care about it. - mWindowAttributes.softInputMode &= - ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; - ((WindowManager.LayoutParams) mView.getLayoutParams()) - .softInputMode &= - ~WindowManager.LayoutParams - .SOFT_INPUT_IS_FORWARD_NAVIGATION; - mHasHadWindowFocus = true; - } else { - if (mPointerCapture) { - handlePointerCaptureChanged(false); - } - } - } - mFirstInputStage.onWindowFocusChanged(hasWindowFocus); + handleWindowFocusChanged(); } break; case MSG_DIE: doDie(); @@ -6258,7 +6340,7 @@ public final class ViewRootImpl implements ViewParent, (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets, - mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, + mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout, mPendingMergedConfiguration, mSurface); mPendingAlwaysConsumeNavBar = @@ -6541,7 +6623,8 @@ public final class ViewRootImpl implements ViewParent, private void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout, - boolean alwaysConsumeNavBar, int displayId) { + boolean alwaysConsumeNavBar, int displayId, + DisplayCutout.ParcelableWrapper displayCutout) { if (DEBUG_LAYOUT) Log.v(mTag, "Resizing " + this + ": frame=" + frame.toShortString() + " contentInsets=" + contentInsets.toShortString() + " visibleInsets=" + visibleInsets.toShortString() @@ -6550,7 +6633,7 @@ public final class ViewRootImpl implements ViewParent, // Tell all listeners that we are resizing the window so that the chrome can get // updated as fast as possible on a separate thread, - if (mDragResizing) { + if (mDragResizing && mUseMTRenderer) { boolean fullscreen = frame.equals(backDropFrame); synchronized (mWindowCallbacks) { for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) { @@ -6578,6 +6661,7 @@ public final class ViewRootImpl implements ViewParent, args.arg6 = sameProcessCall ? new Rect(stableInsets) : stableInsets; args.arg7 = sameProcessCall ? new Rect(outsets) : outsets; args.arg8 = sameProcessCall ? new Rect(backDropFrame) : backDropFrame; + args.arg9 = displayCutout.get(); // DisplayCutout is immutable. args.argi1 = forceLayout ? 1 : 0; args.argi2 = alwaysConsumeNavBar ? 1 : 0; args.argi3 = displayId; @@ -6792,6 +6876,7 @@ public final class ViewRootImpl implements ViewParent, } if (stage != null) { + handleWindowFocusChanged(); stage.deliver(q); } else { finishInputEvent(q); @@ -7097,10 +7182,13 @@ public final class ViewRootImpl implements ViewParent, } public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { + synchronized (this) { + mWindowFocusChanged = true; + mUpcomingWindowFocus = hasFocus; + mUpcomingInTouchMode = inTouchMode; + } Message msg = Message.obtain(); msg.what = MSG_WINDOW_FOCUS_CHANGED; - msg.arg1 = hasFocus ? 1 : 0; - msg.arg2 = inTouchMode ? 1 : 0; mHandler.sendMessage(msg); } @@ -7610,12 +7698,13 @@ public final class ViewRootImpl implements ViewParent, public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout, - boolean alwaysConsumeNavBar, int displayId) { + boolean alwaysConsumeNavBar, int displayId, + DisplayCutout.ParcelableWrapper displayCutout) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.dispatchResized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets, reportDraw, mergedConfiguration, - backDropFrame, forceLayout, alwaysConsumeNavBar, displayId); + backDropFrame, forceLayout, alwaysConsumeNavBar, displayId, displayCutout); } } @@ -7798,9 +7887,11 @@ public final class ViewRootImpl implements ViewParent, Rect stableInsets, int resizeMode) { if (!mDragResizing) { mDragResizing = true; - for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) { - mWindowCallbacks.get(i).onWindowDragResizeStart(initialBounds, fullscreen, - systemInsets, stableInsets, resizeMode); + if (mUseMTRenderer) { + for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) { + mWindowCallbacks.get(i).onWindowDragResizeStart( + initialBounds, fullscreen, systemInsets, stableInsets, resizeMode); + } } mFullRedrawNeeded = true; } @@ -7812,8 +7903,10 @@ public final class ViewRootImpl implements ViewParent, private void endDragResizing() { if (mDragResizing) { mDragResizing = false; - for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) { - mWindowCallbacks.get(i).onWindowDragResizeEnd(); + if (mUseMTRenderer) { + for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) { + mWindowCallbacks.get(i).onWindowDragResizeEnd(); + } } mFullRedrawNeeded = true; } @@ -7821,19 +7914,21 @@ public final class ViewRootImpl implements ViewParent, private boolean updateContentDrawBounds() { boolean updated = false; - for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) { - updated |= mWindowCallbacks.get(i).onContentDrawn( - mWindowAttributes.surfaceInsets.left, - mWindowAttributes.surfaceInsets.top, - mWidth, mHeight); + if (mUseMTRenderer) { + for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) { + updated |= + mWindowCallbacks.get(i).onContentDrawn(mWindowAttributes.surfaceInsets.left, + mWindowAttributes.surfaceInsets.top, mWidth, mHeight); + } } return updated | (mDragResizing && mReportNextDraw); } private void requestDrawWindow() { - if (mReportNextDraw) { - mWindowDrawCountDown = new CountDownLatch(mWindowCallbacks.size()); + if (!mUseMTRenderer) { + return; } + mWindowDrawCountDown = new CountDownLatch(mWindowCallbacks.size()); for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) { mWindowCallbacks.get(i).onRequestDraw(mReportNextDraw); } @@ -7877,6 +7972,7 @@ public final class ViewRootImpl implements ViewParent, if (!registered) { mAttachInfo.mAccessibilityWindowId = mAccessibilityManager.addAccessibilityInteractionConnection(mWindow, + mContext.getPackageName(), new AccessibilityInteractionConnection(ViewRootImpl.this)); } } diff --git a/android/view/View_Delegate.java b/android/view/View_Delegate.java index 408ec549..5d39e4c9 100644 --- a/android/view/View_Delegate.java +++ b/android/view/View_Delegate.java @@ -16,10 +16,13 @@ package android.view; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.content.Context; +import android.graphics.Canvas; import android.os.IBinder; /** @@ -44,4 +47,50 @@ public class View_Delegate { } return null; } + + @LayoutlibDelegate + /*package*/ static void draw(View thisView, Canvas canvas) { + try { + // This code is run within a catch to prevent misbehaving components from breaking + // all the layout. + thisView.draw_Original(canvas); + } catch (Throwable t) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, "View draw failed", t, null); + } + } + + @LayoutlibDelegate + /*package*/ static boolean draw( + View thisView, Canvas canvas, ViewGroup parent, long drawingTime) { + try { + // This code is run within a catch to prevent misbehaving components from breaking + // all the layout. + return thisView.draw_Original(canvas, parent, drawingTime); + } catch (Throwable t) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, "View draw failed", t, null); + } + return false; + } + + @LayoutlibDelegate + /*package*/ static void measure(View thisView, int widthMeasureSpec, int heightMeasureSpec) { + try { + // This code is run within a catch to prevent misbehaving components from breaking + // all the layout. + thisView.measure_Original(widthMeasureSpec, heightMeasureSpec); + } catch (Throwable t) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, "View measure failed", t, null); + } + } + + @LayoutlibDelegate + /*package*/ static void layout(View thisView, int l, int t, int r, int b) { + try { + // This code is run within a catch to prevent misbehaving components from breaking + // all the layout. + thisView.layout_Original(l, t, r, b); + } catch (Throwable th) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, "View layout failed", th, null); + } + } } diff --git a/android/view/WindowInsets.java b/android/view/WindowInsets.java index df124ac5..e5cbe96b 100644 --- a/android/view/WindowInsets.java +++ b/android/view/WindowInsets.java @@ -17,7 +17,7 @@ package android.view; -import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Rect; /** @@ -49,7 +49,7 @@ public final class WindowInsets { private boolean mSystemWindowInsetsConsumed = false; private boolean mWindowDecorInsetsConsumed = false; private boolean mStableInsetsConsumed = false; - private boolean mCutoutConsumed = false; + private boolean mDisplayCutoutConsumed = false; private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0); @@ -80,8 +80,9 @@ public final class WindowInsets { mIsRound = isRound; mAlwaysConsumeNavBar = alwaysConsumeNavBar; - mCutoutConsumed = displayCutout == null; - mDisplayCutout = mCutoutConsumed ? DisplayCutout.NO_CUTOUT : displayCutout; + mDisplayCutoutConsumed = displayCutout == null; + mDisplayCutout = (mDisplayCutoutConsumed || displayCutout.isEmpty()) + ? null : displayCutout; } /** @@ -99,7 +100,7 @@ public final class WindowInsets { mIsRound = src.mIsRound; mAlwaysConsumeNavBar = src.mAlwaysConsumeNavBar; mDisplayCutout = src.mDisplayCutout; - mCutoutConsumed = src.mCutoutConsumed; + mDisplayCutoutConsumed = src.mDisplayCutoutConsumed; } /** @hide */ @@ -269,15 +270,16 @@ public final class WindowInsets { */ public boolean hasInsets() { return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets() - || mDisplayCutout.hasCutout(); + || mDisplayCutout != null; } /** - * @return the display cutout + * Returns the display cutout if there is one. + * + * @return the display cutout or null if there is none * @see DisplayCutout - * @hide pending API */ - @NonNull + @Nullable public DisplayCutout getDisplayCutout() { return mDisplayCutout; } @@ -286,12 +288,11 @@ public final class WindowInsets { * Returns a copy of this WindowInsets with the cutout fully consumed. * * @return A modified copy of this WindowInsets - * @hide pending API */ - public WindowInsets consumeCutout() { + public WindowInsets consumeDisplayCutout() { final WindowInsets result = new WindowInsets(this); - result.mDisplayCutout = DisplayCutout.NO_CUTOUT; - result.mCutoutConsumed = true; + result.mDisplayCutout = null; + result.mDisplayCutoutConsumed = true; return result; } @@ -311,7 +312,7 @@ public final class WindowInsets { */ public boolean isConsumed() { return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed - && mCutoutConsumed; + && mDisplayCutoutConsumed; } /** @@ -530,7 +531,7 @@ public final class WindowInsets { return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets + " windowDecorInsets=" + mWindowDecorInsets + " stableInsets=" + mStableInsets - + (mDisplayCutout.hasCutout() ? " cutout=" + mDisplayCutout : "") + + (mDisplayCutout != null ? " cutout=" + mDisplayCutout : "") + (isRound() ? " round" : "") + "}"; } diff --git a/android/view/WindowManager.java b/android/view/WindowManager.java index 905c0715..cbe012af 100644 --- a/android/view/WindowManager.java +++ b/android/view/WindowManager.java @@ -20,6 +20,7 @@ import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT; import android.Manifest.permission; import android.annotation.IntDef; +import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; @@ -1268,6 +1269,33 @@ 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. + * + *

+ * 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 @@ -1642,12 +1670,20 @@ public interface WindowManager extends ViewManager { * Visibility state for {@link #softInputMode}: please show the soft * input area when normally appropriate (when the user is navigating * forward to your window). + * + *

Applications that target {@link android.os.Build.VERSION_CODES#P} and later, this flag + * is ignored unless there is a focused view that returns {@code true} from + * {@link View#isInEditMode()} when the window is focused.

*/ public static final int SOFT_INPUT_STATE_VISIBLE = 4; /** * Visibility state for {@link #softInputMode}: please always make the * soft input area visible when this window receives input focus. + * + *

Applications that target {@link android.os.Build.VERSION_CODES#P} and later, this flag + * is ignored unless there is a focused view that returns {@code true} from + * {@link View#isInEditMode()} when the window is focused.

*/ public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5; @@ -1708,7 +1744,7 @@ public interface WindowManager extends ViewManager { * @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = { + @IntDef(flag = true, prefix = { "SOFT_INPUT_" }, value = { SOFT_INPUT_STATE_UNSPECIFIED, SOFT_INPUT_STATE_UNCHANGED, SOFT_INPUT_STATE_HIDDEN, @@ -2211,6 +2247,7 @@ 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(gravity); @@ -2266,6 +2303,7 @@ public interface WindowManager extends ViewManager { y = in.readInt(); type = in.readInt(); flags = in.readInt(); + flags2 = in.readLong(); privateFlags = in.readInt(); softInputMode = in.readInt(); gravity = in.readInt(); @@ -2398,6 +2436,10 @@ 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; @@ -2651,6 +2693,11 @@ 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( diff --git a/android/view/accessibility/AccessibilityInteractionClient.java b/android/view/accessibility/AccessibilityInteractionClient.java index d890f329..72af203e 100644 --- a/android/view/accessibility/AccessibilityInteractionClient.java +++ b/android/view/accessibility/AccessibilityInteractionClient.java @@ -29,6 +29,7 @@ import android.util.LongSparseArray; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import java.util.ArrayList; import java.util.Collections; @@ -213,7 +214,7 @@ public final class AccessibilityInteractionClient * * @param connectionId The id of a connection for interacting with the system. * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} + * {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID} * to query the currently active window. * @return The {@link AccessibilityWindowInfo}. */ @@ -299,7 +300,7 @@ public final class AccessibilityInteractionClient * * @param connectionId The id of a connection for interacting with the system. * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} + * {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID} * to query the currently active window. * @param accessibilityNodeId A unique view id or virtual descendant id from * where to start the search. Use @@ -335,18 +336,19 @@ public final class AccessibilityInteractionClient } final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success; + final String[] packageNames; try { - success = connection.findAccessibilityNodeInfoByAccessibilityId( + packageNames = connection.findAccessibilityNodeInfoByAccessibilityId( accessibilityWindowId, accessibilityNodeId, interactionId, this, prefetchFlags, Thread.currentThread().getId(), arguments); } finally { Binder.restoreCallingIdentity(identityToken); } - if (success) { + if (packageNames != null) { List infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, bypassCache); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, + bypassCache, packageNames); if (infos != null && !infos.isEmpty()) { for (int i = 1; i < infos.size(); i++) { infos.get(i).recycle(); @@ -373,7 +375,7 @@ public final class AccessibilityInteractionClient * * @param connectionId The id of a connection for interacting with the system. * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} + * {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID} * to query the currently active window. * @param accessibilityNodeId A unique view id or virtual descendant id from * where to start the search. Use @@ -389,20 +391,21 @@ public final class AccessibilityInteractionClient if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success; + final String[] packageNames; try { - success = connection.findAccessibilityNodeInfosByViewId( + packageNames = connection.findAccessibilityNodeInfosByViewId( accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this, Thread.currentThread().getId()); } finally { Binder.restoreCallingIdentity(identityToken); } - if (success) { + if (packageNames != null) { List infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); if (infos != null) { - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, false); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, + false, packageNames); return infos; } } @@ -426,7 +429,7 @@ public final class AccessibilityInteractionClient * * @param connectionId The id of a connection for interacting with the system. * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} + * {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID} * to query the currently active window. * @param accessibilityNodeId A unique view id or virtual descendant id from * where to start the search. Use @@ -442,20 +445,21 @@ public final class AccessibilityInteractionClient if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success; + final String[] packageNames; try { - success = connection.findAccessibilityNodeInfosByText( + packageNames = connection.findAccessibilityNodeInfosByText( accessibilityWindowId, accessibilityNodeId, text, interactionId, this, Thread.currentThread().getId()); } finally { Binder.restoreCallingIdentity(identityToken); } - if (success) { + if (packageNames != null) { List infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); if (infos != null) { - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, false); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, + false, packageNames); return infos; } } @@ -478,7 +482,7 @@ public final class AccessibilityInteractionClient * * @param connectionId The id of a connection for interacting with the system. * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} + * {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID} * to query the currently active window. * @param accessibilityNodeId A unique view id or virtual descendant id from * where to start the search. Use @@ -494,19 +498,19 @@ public final class AccessibilityInteractionClient if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success; + final String[] packageNames; try { - success = connection.findFocus(accessibilityWindowId, + packageNames = connection.findFocus(accessibilityWindowId, accessibilityNodeId, focusType, interactionId, this, Thread.currentThread().getId()); } finally { Binder.restoreCallingIdentity(identityToken); } - if (success) { + if (packageNames != null) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames); return info; } } else { @@ -527,7 +531,7 @@ public final class AccessibilityInteractionClient * * @param connectionId The id of a connection for interacting with the system. * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} + * {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID} * to query the currently active window. * @param accessibilityNodeId A unique view id or virtual descendant id from * where to start the search. Use @@ -543,19 +547,19 @@ public final class AccessibilityInteractionClient if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success; + final String[] packageNames; try { - success = connection.focusSearch(accessibilityWindowId, + packageNames = connection.focusSearch(accessibilityWindowId, accessibilityNodeId, direction, interactionId, this, Thread.currentThread().getId()); } finally { Binder.restoreCallingIdentity(identityToken); } - if (success) { + if (packageNames != null) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false, packageNames); return info; } } else { @@ -574,7 +578,7 @@ public final class AccessibilityInteractionClient * * @param connectionId The id of a connection for interacting with the system. * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} + * {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID} * to query the currently active window. * @param accessibilityNodeId A unique view id or virtual descendant id from * where to start the search. Use @@ -661,7 +665,7 @@ public final class AccessibilityInteractionClient int interactionId) { synchronized (mInstanceLock) { final boolean success = waitForResultTimedLocked(interactionId); - List result = null; + final List result; if (success) { result = mFindAccessibilityNodeInfosResult; } else { @@ -779,11 +783,22 @@ public final class AccessibilityInteractionClient * @param connectionId The id of the connection to the system. * @param bypassCache Whether or not to bypass the cache. The node is added to the cache if * this value is {@code false} + * @param packageNames The valid package names a node can come from. */ private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, - int connectionId, boolean bypassCache) { + int connectionId, boolean bypassCache, String[] packageNames) { if (info != null) { info.setConnectionId(connectionId); + // Empty array means any package name is Okay + if (!ArrayUtils.isEmpty(packageNames)) { + CharSequence packageName = info.getPackageName(); + if (packageName == null + || !ArrayUtils.contains(packageNames, packageName.toString())) { + // If the node package not one of the valid ones, pick the top one - this + // is one of the packages running in the introspected UID. + info.setPackageName(packageNames[0]); + } + } info.setSealed(true); if (!bypassCache) { sAccessibilityCache.add(info); @@ -798,14 +813,16 @@ public final class AccessibilityInteractionClient * @param connectionId The id of the connection to the system. * @param bypassCache Whether or not to bypass the cache. The nodes are added to the cache if * this value is {@code false} + * @param packageNames The valid package names a node can come from. */ private void finalizeAndCacheAccessibilityNodeInfos(List infos, - int connectionId, boolean bypassCache) { + int connectionId, boolean bypassCache, String[] packageNames) { if (infos != null) { final int infosCount = infos.size(); for (int i = 0; i < infosCount; i++) { AccessibilityNodeInfo info = infos.get(i); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId, bypassCache); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, + bypassCache, packageNames); } } } diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java index 0375635f..dd8ba556 100644 --- a/android/view/accessibility/AccessibilityManager.java +++ b/android/view/accessibility/AccessibilityManager.java @@ -16,153 +16,46 @@ package android.view.accessibility; -import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME; - -import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SdkConstant; -import android.annotation.SystemService; -import android.annotation.TestApi; -import android.content.ComponentName; import android.content.Context; -import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; -import android.content.res.Resources; -import android.os.Binder; import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.Process; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.os.UserHandle; -import android.util.ArrayMap; -import android.util.Log; -import android.util.SparseArray; import android.view.IWindow; import android.view.View; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.IntPair; - -import java.util.ArrayList; import java.util.Collections; import java.util.List; /** - * System level service that serves as an event dispatch for {@link AccessibilityEvent}s, - * and provides facilities for querying the accessibility state of the system. - * Accessibility events are generated when something notable happens in the user interface, + * System level service that serves as an event dispatch for {@link AccessibilityEvent}s. + * Such events are generated when something notable happens in the user interface, * for example an {@link android.app.Activity} starts, the focus or selection of a * {@link android.view.View} changes etc. Parties interested in handling accessibility * events implement and register an accessibility service which extends - * {@link android.accessibilityservice.AccessibilityService}. + * {@code android.accessibilityservice.AccessibilityService}. * * @see AccessibilityEvent - * @see AccessibilityNodeInfo - * @see android.accessibilityservice.AccessibilityService - * @see Context#getSystemService - * @see Context#ACCESSIBILITY_SERVICE + * @see android.content.Context#getSystemService */ -@SystemService(Context.ACCESSIBILITY_SERVICE) +@SuppressWarnings("UnusedDeclaration") public final class AccessibilityManager { - private static final boolean DEBUG = false; - - private static final String LOG_TAG = "AccessibilityManager"; - - /** @hide */ - public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001; - - /** @hide */ - public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002; - - /** @hide */ - public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004; - - /** @hide */ - public static final int DALTONIZER_DISABLED = -1; - /** @hide */ - public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0; + private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0); - /** @hide */ - public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12; - - /** @hide */ - public static final int AUTOCLICK_DELAY_DEFAULT = 600; /** - * Activity action: Launch UI to manage which accessibility service or feature is assigned - * to the navigation bar Accessibility button. - *

- * Input: Nothing. - *

- *

- * Output: Nothing. - *

- * - * @hide - */ - @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON = - "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON"; - - static final Object sInstanceSync = new Object(); - - private static AccessibilityManager sInstance; - - private final Object mLock = new Object(); - - private IAccessibilityManager mService; - - final int mUserId; - - final Handler mHandler; - - final Handler.Callback mCallback; - - boolean mIsEnabled; - - int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK; - - boolean mIsTouchExplorationEnabled; - - boolean mIsHighTextContrastEnabled; - - private final ArrayMap - mAccessibilityStateChangeListeners = new ArrayMap<>(); - - private final ArrayMap - mTouchExplorationStateChangeListeners = new ArrayMap<>(); - - private final ArrayMap - mHighTextContrastStateChangeListeners = new ArrayMap<>(); - - private final ArrayMap - mServicesStateChangeListeners = new ArrayMap<>(); - - /** - * Map from a view's accessibility id to the list of request preparers set for that view - */ - private SparseArray> mRequestPreparerLists; - - /** - * Listener for the system accessibility state. To listen for changes to the - * accessibility state on the device, implement this interface and register - * it with the system by calling {@link #addAccessibilityStateChangeListener}. + * Listener for the accessibility state. */ public interface AccessibilityStateChangeListener { /** - * Called when the accessibility enabled state changes. + * Called back on change in the accessibility state. * * @param enabled Whether accessibility is enabled. */ - void onAccessibilityStateChanged(boolean enabled); + public void onAccessibilityStateChanged(boolean enabled); } /** @@ -178,25 +71,7 @@ public final class AccessibilityManager { * * @param enabled Whether touch exploration is enabled. */ - void onTouchExplorationStateChanged(boolean enabled); - } - - /** - * Listener for changes to the state of accessibility services. Changes include services being - * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service. - * {@see #addAccessibilityServicesStateChangeListener}. - * - * @hide - */ - @TestApi - public interface AccessibilityServicesStateChangeListener { - - /** - * Called when the state of accessibility services changes. - * - * @param manager The manager that is calling back - */ - void onAccessibilityServicesStateChanged(AccessibilityManager manager); + public void onTouchExplorationStateChanged(boolean enabled); } /** @@ -204,8 +79,6 @@ public final class AccessibilityManager { * the high text contrast state on the device, implement this interface and * register it with the system by calling * {@link #addHighTextContrastStateChangeListener}. - * - * @hide */ public interface HighTextContrastChangeListener { @@ -214,72 +87,26 @@ public final class AccessibilityManager { * * @param enabled Whether high text contrast is enabled. */ - void onHighTextContrastStateChanged(boolean enabled); + public void onHighTextContrastStateChanged(boolean enabled); } private final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() { - @Override - public void setState(int state) { - // We do not want to change this immediately as the application may - // have already checked that accessibility is on and fired an event, - // that is now propagating up the view tree, Hence, if accessibility - // is now off an exception will be thrown. We want to have the exception - // enforcement to guard against apps that fire unnecessary accessibility - // events when accessibility is off. - mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget(); - } - - @Override - public void notifyServicesStateChanged() { - final ArrayMap listeners; - synchronized (mLock) { - if (mServicesStateChangeListeners.isEmpty()) { - return; + public void setState(int state) { } - listeners = new ArrayMap<>(mServicesStateChangeListeners); - } - int numListeners = listeners.size(); - for (int i = 0; i < numListeners; i++) { - final AccessibilityServicesStateChangeListener listener = - mServicesStateChangeListeners.keyAt(i); - mServicesStateChangeListeners.valueAt(i).post(() -> listener - .onAccessibilityServicesStateChanged(AccessibilityManager.this)); - } - } + public void notifyServicesStateChanged() { + } - @Override - public void setRelevantEventTypes(int eventTypes) { - mRelevantEventTypes = eventTypes; - } - }; + public void setRelevantEventTypes(int eventTypes) { + } + }; /** * Get an AccessibilityManager instance (create one if necessary). * - * @param context Context in which this manager operates. - * - * @hide */ public static AccessibilityManager getInstance(Context context) { - synchronized (sInstanceSync) { - if (sInstance == null) { - final int userId; - if (Binder.getCallingUid() == Process.SYSTEM_UID - || context.checkCallingOrSelfPermission( - Manifest.permission.INTERACT_ACROSS_USERS) - == PackageManager.PERMISSION_GRANTED - || context.checkCallingOrSelfPermission( - Manifest.permission.INTERACT_ACROSS_USERS_FULL) - == PackageManager.PERMISSION_GRANTED) { - userId = UserHandle.USER_CURRENT; - } else { - userId = UserHandle.myUserId(); - } - sInstance = new AccessibilityManager(context, null, userId); - } - } return sInstance; } @@ -287,68 +114,21 @@ public final class AccessibilityManager { * Create an instance. * * @param context A {@link Context}. - * @param service An interface to the backing service. - * @param userId User id under which to run. - * - * @hide */ public AccessibilityManager(Context context, IAccessibilityManager service, int userId) { - // Constructor can't be chained because we can't create an instance of an inner class - // before calling another constructor. - mCallback = new MyCallback(); - mHandler = new Handler(context.getMainLooper(), mCallback); - mUserId = userId; - synchronized (mLock) { - tryConnectToServiceLocked(service); - } } - /** - * Create an instance. - * - * @param handler The handler to use - * @param service An interface to the backing service. - * @param userId User id under which to run. - * - * @hide - */ - public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) { - mCallback = new MyCallback(); - mHandler = handler; - mUserId = userId; - synchronized (mLock) { - tryConnectToServiceLocked(service); - } - } - - /** - * @hide - */ public IAccessibilityManagerClient getClient() { return mClient; } /** - * @hide - */ - @VisibleForTesting - public Handler.Callback getCallback() { - return mCallback; - } - - /** - * Returns if the accessibility in the system is enabled. + * Returns if the {@link AccessibilityManager} is enabled. * - * @return True if accessibility is enabled, false otherwise. + * @return True if this {@link AccessibilityManager} is enabled, false otherwise. */ public boolean isEnabled() { - synchronized (mLock) { - IAccessibilityManager service = getServiceLocked(); - if (service == null) { - return false; - } - return mIsEnabled; - } + return false; } /** @@ -357,13 +137,7 @@ public final class AccessibilityManager { * @return True if touch exploration is enabled, false otherwise. */ public boolean isTouchExplorationEnabled() { - synchronized (mLock) { - IAccessibilityManager service = getServiceLocked(); - if (service == null) { - return false; - } - return mIsTouchExplorationEnabled; - } + return true; } /** @@ -373,84 +147,15 @@ public final class AccessibilityManager { * doing its own rendering and does not rely on the platform rendering pipeline. *

* - * @return True if high text contrast is enabled, false otherwise. - * - * @hide */ public boolean isHighTextContrastEnabled() { - synchronized (mLock) { - IAccessibilityManager service = getServiceLocked(); - if (service == null) { - return false; - } - return mIsHighTextContrastEnabled; - } + return false; } /** * Sends an {@link AccessibilityEvent}. - * - * @param event The event to send. - * - * @throws IllegalStateException if accessibility is not enabled. - * - * Note: The preferred mechanism for sending custom accessibility - * events is through calling - * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)} - * instead of this method to allow predecessors to augment/filter events sent by - * their descendants. */ public void sendAccessibilityEvent(AccessibilityEvent event) { - final IAccessibilityManager service; - final int userId; - synchronized (mLock) { - service = getServiceLocked(); - if (service == null) { - return; - } - if (!mIsEnabled) { - Looper myLooper = Looper.myLooper(); - if (myLooper == Looper.getMainLooper()) { - throw new IllegalStateException( - "Accessibility off. Did you forget to check that?"); - } else { - // If we're not running on the thread with the main looper, it's possible for - // the state of accessibility to change between checking isEnabled and - // calling this method. So just log the error rather than throwing the - // exception. - Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled"); - return; - } - } - if ((event.getEventType() & mRelevantEventTypes) == 0) { - if (DEBUG) { - Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event - + " that is not among " - + AccessibilityEvent.eventTypeToString(mRelevantEventTypes)); - } - return; - } - userId = mUserId; - } - try { - event.setEventTime(SystemClock.uptimeMillis()); - // it is possible that this manager is in the same process as the service but - // client using it is called through Binder from another process. Example: MMS - // app adds a SMS notification and the NotificationManagerService calls this method - long identityToken = Binder.clearCallingIdentity(); - try { - service.sendAccessibilityEvent(event, userId); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - if (DEBUG) { - Log.i(LOG_TAG, event + " sent"); - } - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error during sending " + event + " ", re); - } finally { - event.recycle(); - } } /** @@ -462,95 +167,27 @@ public final class AccessibilityManager { * @return Whether the event is being observed. */ public boolean isObservedEventType(@AccessibilityEvent.EventType int type) { - return mIsEnabled && (mRelevantEventTypes & type) != 0; + return false; } /** - * Requests feedback interruption from all accessibility services. + * Requests interruption of the accessibility feedback from all accessibility services. */ public void interrupt() { - final IAccessibilityManager service; - final int userId; - synchronized (mLock) { - service = getServiceLocked(); - if (service == null) { - return; - } - if (!mIsEnabled) { - Looper myLooper = Looper.myLooper(); - if (myLooper == Looper.getMainLooper()) { - throw new IllegalStateException( - "Accessibility off. Did you forget to check that?"); - } else { - // If we're not running on the thread with the main looper, it's possible for - // the state of accessibility to change between checking isEnabled and - // calling this method. So just log the error rather than throwing the - // exception. - Log.e(LOG_TAG, "Interrupt called with accessibility disabled"); - return; - } - } - userId = mUserId; - } - try { - service.interrupt(userId); - if (DEBUG) { - Log.i(LOG_TAG, "Requested interrupt from all services"); - } - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re); - } } /** * Returns the {@link ServiceInfo}s of the installed accessibility services. * * @return An unmodifiable list with {@link ServiceInfo}s. - * - * @deprecated Use {@link #getInstalledAccessibilityServiceList()} */ @Deprecated public List getAccessibilityServiceList() { - List infos = getInstalledAccessibilityServiceList(); - List services = new ArrayList<>(); - final int infoCount = infos.size(); - for (int i = 0; i < infoCount; i++) { - AccessibilityServiceInfo info = infos.get(i); - services.add(info.getResolveInfo().serviceInfo); - } - return Collections.unmodifiableList(services); + return Collections.emptyList(); } - /** - * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services. - * - * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. - */ public List getInstalledAccessibilityServiceList() { - final IAccessibilityManager service; - final int userId; - synchronized (mLock) { - service = getServiceLocked(); - if (service == null) { - return Collections.emptyList(); - } - userId = mUserId; - } - - List services = null; - try { - services = service.getInstalledAccessibilityServiceList(userId); - if (DEBUG) { - Log.i(LOG_TAG, "Installed AccessibilityServices " + services); - } - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); - } - if (services != null) { - return Collections.unmodifiableList(services); - } else { - return Collections.emptyList(); - } + return Collections.emptyList(); } /** @@ -565,48 +202,21 @@ public final class AccessibilityManager { * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN * @see AccessibilityServiceInfo#FEEDBACK_VISUAL - * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE */ public List getEnabledAccessibilityServiceList( int feedbackTypeFlags) { - final IAccessibilityManager service; - final int userId; - synchronized (mLock) { - service = getServiceLocked(); - if (service == null) { - return Collections.emptyList(); - } - userId = mUserId; - } - - List services = null; - try { - services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId); - if (DEBUG) { - Log.i(LOG_TAG, "Installed AccessibilityServices " + services); - } - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); - } - if (services != null) { - return Collections.unmodifiableList(services); - } else { - return Collections.emptyList(); - } + return Collections.emptyList(); } /** * Registers an {@link AccessibilityStateChangeListener} for changes in - * the global accessibility state of the system. Equivalent to calling - * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)} - * with a null handler. + * the global accessibility state of the system. * * @param listener The listener. - * @return Always returns {@code true}. + * @return True if successfully registered. */ public boolean addAccessibilityStateChangeListener( - @NonNull AccessibilityStateChangeListener listener) { - addAccessibilityStateChangeListener(listener, null); + AccessibilityStateChangeListener listener) { return true; } @@ -620,40 +230,22 @@ public final class AccessibilityManager { * for a callback on the process's main handler. */ public void addAccessibilityStateChangeListener( - @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) { - synchronized (mLock) { - mAccessibilityStateChangeListeners - .put(listener, (handler == null) ? mHandler : handler); - } - } + @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {} - /** - * Unregisters an {@link AccessibilityStateChangeListener}. - * - * @param listener The listener. - * @return True if the listener was previously registered. - */ public boolean removeAccessibilityStateChangeListener( - @NonNull AccessibilityStateChangeListener listener) { - synchronized (mLock) { - int index = mAccessibilityStateChangeListeners.indexOfKey(listener); - mAccessibilityStateChangeListeners.remove(listener); - return (index >= 0); - } + AccessibilityStateChangeListener listener) { + return true; } /** * Registers a {@link TouchExplorationStateChangeListener} for changes in - * the global touch exploration state of the system. Equivalent to calling - * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)} - * with a null handler. + * the global touch exploration state of the system. * * @param listener The listener. - * @return Always returns {@code true}. + * @return True if successfully registered. */ public boolean addTouchExplorationStateChangeListener( @NonNull TouchExplorationStateChangeListener listener) { - addTouchExplorationStateChangeListener(listener, null); return true; } @@ -667,105 +259,17 @@ public final class AccessibilityManager { * for a callback on the process's main handler. */ public void addTouchExplorationStateChangeListener( - @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) { - synchronized (mLock) { - mTouchExplorationStateChangeListeners - .put(listener, (handler == null) ? mHandler : handler); - } - } + @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {} /** * Unregisters a {@link TouchExplorationStateChangeListener}. * * @param listener The listener. - * @return True if listener was previously registered. + * @return True if successfully unregistered. */ public boolean removeTouchExplorationStateChangeListener( @NonNull TouchExplorationStateChangeListener listener) { - synchronized (mLock) { - int index = mTouchExplorationStateChangeListeners.indexOfKey(listener); - mTouchExplorationStateChangeListeners.remove(listener); - return (index >= 0); - } - } - - /** - * Registers a {@link AccessibilityServicesStateChangeListener}. - * - * @param listener The listener. - * @param handler The handler on which the listener should be called back, or {@code null} - * for a callback on the process's main handler. - * @hide - */ - @TestApi - public void addAccessibilityServicesStateChangeListener( - @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) { - synchronized (mLock) { - mServicesStateChangeListeners - .put(listener, (handler == null) ? mHandler : handler); - } - } - - /** - * Unregisters a {@link AccessibilityServicesStateChangeListener}. - * - * @param listener The listener. - * - * @hide - */ - @TestApi - public void removeAccessibilityServicesStateChangeListener( - @NonNull AccessibilityServicesStateChangeListener listener) { - // Final CopyOnWriteArrayList - no lock needed. - mServicesStateChangeListeners.remove(listener); - } - - /** - * Registers a {@link AccessibilityRequestPreparer}. - */ - public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) { - if (mRequestPreparerLists == null) { - mRequestPreparerLists = new SparseArray<>(1); - } - int id = preparer.getView().getAccessibilityViewId(); - List requestPreparerList = mRequestPreparerLists.get(id); - if (requestPreparerList == null) { - requestPreparerList = new ArrayList<>(1); - mRequestPreparerLists.put(id, requestPreparerList); - } - requestPreparerList.add(preparer); - } - - /** - * Unregisters a {@link AccessibilityRequestPreparer}. - */ - public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) { - if (mRequestPreparerLists == null) { - return; - } - int viewId = preparer.getView().getAccessibilityViewId(); - List requestPreparerList = mRequestPreparerLists.get(viewId); - if (requestPreparerList != null) { - requestPreparerList.remove(preparer); - if (requestPreparerList.isEmpty()) { - mRequestPreparerLists.remove(viewId); - } - } - } - - /** - * Get the preparers that are registered for an accessibility ID - * - * @param id The ID of interest - * @return The list of preparers, or {@code null} if there are none. - * - * @hide - */ - public List getRequestPreparersForAccessibilityId(int id) { - if (mRequestPreparerLists == null) { - return null; - } - return mRequestPreparerLists.get(id); + return true; } /** @@ -777,12 +281,7 @@ public final class AccessibilityManager { * @hide */ public void addHighTextContrastStateChangeListener( - @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) { - synchronized (mLock) { - mHighTextContrastStateChangeListeners - .put(listener, (handler == null) ? mHandler : handler); - } - } + @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {} /** * Unregisters a {@link HighTextContrastChangeListener}. @@ -792,51 +291,7 @@ public final class AccessibilityManager { * @hide */ public void removeHighTextContrastStateChangeListener( - @NonNull HighTextContrastChangeListener listener) { - synchronized (mLock) { - mHighTextContrastStateChangeListeners.remove(listener); - } - } - - /** - * Check if the accessibility volume stream is active. - * - * @return True if accessibility volume is active (i.e. some service has requested it). False - * otherwise. - * @hide - */ - public boolean isAccessibilityVolumeStreamActive() { - List serviceInfos = - getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK); - for (int i = 0; i < serviceInfos.size(); i++) { - if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) { - return true; - } - } - return false; - } - - /** - * Report a fingerprint gesture to accessibility. Only available for the system process. - * - * @param keyCode The key code of the gesture - * @return {@code true} if accessibility consumes the event. {@code false} if not. - * @hide - */ - public boolean sendFingerprintGesture(int keyCode) { - final IAccessibilityManager service; - synchronized (mLock) { - service = getServiceLocked(); - if (service == null) { - return false; - } - } - try { - return service.sendFingerprintGesture(keyCode); - } catch (RemoteException e) { - return false; - } - } + @NonNull HighTextContrastChangeListener listener) {} /** * Sets the current state and notifies listeners, if necessary. @@ -844,314 +299,14 @@ public final class AccessibilityManager { * @param stateFlags The state flags. */ private void setStateLocked(int stateFlags) { - final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; - final boolean touchExplorationEnabled = - (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0; - final boolean highTextContrastEnabled = - (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0; - - final boolean wasEnabled = mIsEnabled; - final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled; - final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled; - - // Ensure listeners get current state from isZzzEnabled() calls. - mIsEnabled = enabled; - mIsTouchExplorationEnabled = touchExplorationEnabled; - mIsHighTextContrastEnabled = highTextContrastEnabled; - - if (wasEnabled != enabled) { - notifyAccessibilityStateChanged(); - } - - if (wasTouchExplorationEnabled != touchExplorationEnabled) { - notifyTouchExplorationStateChanged(); - } - - if (wasHighTextContrastEnabled != highTextContrastEnabled) { - notifyHighTextContrastStateChanged(); - } } - /** - * Find an installed service with the specified {@link ComponentName}. - * - * @param componentName The name to match to the service. - * - * @return The info corresponding to the installed service, or {@code null} if no such service - * is installed. - * @hide - */ - public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName( - ComponentName componentName) { - final List installedServiceInfos = - getInstalledAccessibilityServiceList(); - if ((installedServiceInfos == null) || (componentName == null)) { - return null; - } - for (int i = 0; i < installedServiceInfos.size(); i++) { - if (componentName.equals(installedServiceInfos.get(i).getComponentName())) { - return installedServiceInfos.get(i); - } - } - return null; - } - - /** - * Adds an accessibility interaction connection interface for a given window. - * @param windowToken The window token to which a connection is added. - * @param connection The connection. - * - * @hide - */ public int addAccessibilityInteractionConnection(IWindow windowToken, IAccessibilityInteractionConnection connection) { - final IAccessibilityManager service; - final int userId; - synchronized (mLock) { - service = getServiceLocked(); - if (service == null) { - return View.NO_ID; - } - userId = mUserId; - } - try { - return service.addAccessibilityInteractionConnection(windowToken, connection, userId); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); - } return View.NO_ID; } - /** - * Removed an accessibility interaction connection interface for a given window. - * @param windowToken The window token to which a connection is removed. - * - * @hide - */ public void removeAccessibilityInteractionConnection(IWindow windowToken) { - final IAccessibilityManager service; - synchronized (mLock) { - service = getServiceLocked(); - if (service == null) { - return; - } - } - try { - service.removeAccessibilityInteractionConnection(windowToken); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re); - } - } - - /** - * Perform the accessibility shortcut if the caller has permission. - * - * @hide - */ - public void performAccessibilityShortcut() { - final IAccessibilityManager service; - synchronized (mLock) { - service = getServiceLocked(); - if (service == null) { - return; - } - } - try { - service.performAccessibilityShortcut(); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re); - } - } - - /** - * Notifies that the accessibility button in the system's navigation area has been clicked - * - * @hide - */ - public void notifyAccessibilityButtonClicked() { - final IAccessibilityManager service; - synchronized (mLock) { - service = getServiceLocked(); - if (service == null) { - return; - } - } - try { - service.notifyAccessibilityButtonClicked(); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error while dispatching accessibility button click", re); - } - } - - /** - * Notifies that the visibility of the accessibility button in the system's navigation area - * has changed. - * - * @param shown {@code true} if the accessibility button is visible within the system - * navigation area, {@code false} otherwise - * @hide - */ - public void notifyAccessibilityButtonVisibilityChanged(boolean shown) { - final IAccessibilityManager service; - synchronized (mLock) { - service = getServiceLocked(); - if (service == null) { - return; - } - } - try { - service.notifyAccessibilityButtonVisibilityChanged(shown); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re); - } - } - - /** - * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture - * window. Intended for use by the System UI only. - * - * @param connection The connection to handle the actions. Set to {@code null} to avoid - * affecting the actions. - * - * @hide - */ - public void setPictureInPictureActionReplacingConnection( - @Nullable IAccessibilityInteractionConnection connection) { - final IAccessibilityManager service; - synchronized (mLock) { - service = getServiceLocked(); - if (service == null) { - return; - } - } - try { - service.setPictureInPictureActionReplacingConnection(connection); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error setting picture in picture action replacement", re); - } } - private IAccessibilityManager getServiceLocked() { - if (mService == null) { - tryConnectToServiceLocked(null); - } - return mService; - } - - private void tryConnectToServiceLocked(IAccessibilityManager service) { - if (service == null) { - IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); - if (iBinder == null) { - return; - } - service = IAccessibilityManager.Stub.asInterface(iBinder); - } - - try { - final long userStateAndRelevantEvents = service.addClient(mClient, mUserId); - setStateLocked(IntPair.first(userStateAndRelevantEvents)); - mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents); - mService = service; - } catch (RemoteException re) { - Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); - } - } - - /** - * Notifies the registered {@link AccessibilityStateChangeListener}s. - */ - private void notifyAccessibilityStateChanged() { - final boolean isEnabled; - final ArrayMap listeners; - synchronized (mLock) { - if (mAccessibilityStateChangeListeners.isEmpty()) { - return; - } - isEnabled = mIsEnabled; - listeners = new ArrayMap<>(mAccessibilityStateChangeListeners); - } - - int numListeners = listeners.size(); - for (int i = 0; i < numListeners; i++) { - final AccessibilityStateChangeListener listener = - mAccessibilityStateChangeListeners.keyAt(i); - mAccessibilityStateChangeListeners.valueAt(i) - .post(() -> listener.onAccessibilityStateChanged(isEnabled)); - } - } - - /** - * Notifies the registered {@link TouchExplorationStateChangeListener}s. - */ - private void notifyTouchExplorationStateChanged() { - final boolean isTouchExplorationEnabled; - final ArrayMap listeners; - synchronized (mLock) { - if (mTouchExplorationStateChangeListeners.isEmpty()) { - return; - } - isTouchExplorationEnabled = mIsTouchExplorationEnabled; - listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners); - } - - int numListeners = listeners.size(); - for (int i = 0; i < numListeners; i++) { - final TouchExplorationStateChangeListener listener = - mTouchExplorationStateChangeListeners.keyAt(i); - mTouchExplorationStateChangeListeners.valueAt(i) - .post(() -> listener.onTouchExplorationStateChanged(isTouchExplorationEnabled)); - } - } - - /** - * Notifies the registered {@link HighTextContrastChangeListener}s. - */ - private void notifyHighTextContrastStateChanged() { - final boolean isHighTextContrastEnabled; - final ArrayMap listeners; - synchronized (mLock) { - if (mHighTextContrastStateChangeListeners.isEmpty()) { - return; - } - isHighTextContrastEnabled = mIsHighTextContrastEnabled; - listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners); - } - - int numListeners = listeners.size(); - for (int i = 0; i < numListeners; i++) { - final HighTextContrastChangeListener listener = - mHighTextContrastStateChangeListeners.keyAt(i); - mHighTextContrastStateChangeListeners.valueAt(i) - .post(() -> listener.onHighTextContrastStateChanged(isHighTextContrastEnabled)); - } - } - - /** - * Determines if the accessibility button within the system navigation area is supported. - * - * @return {@code true} if the accessibility button is supported on this device, - * {@code false} otherwise - */ - public static boolean isAccessibilityButtonSupported() { - final Resources res = Resources.getSystem(); - return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar); - } - - private final class MyCallback implements Handler.Callback { - public static final int MSG_SET_STATE = 1; - - @Override - public boolean handleMessage(Message message) { - switch (message.what) { - case MSG_SET_STATE: { - // See comment at mClient - final int state = message.arg1; - synchronized (mLock) { - setStateLocked(state); - } - } break; - } - return true; - } - } } diff --git a/android/view/accessibility/AccessibilityNodeInfo.java b/android/view/accessibility/AccessibilityNodeInfo.java index faea9200..28ef6978 100644 --- a/android/view/accessibility/AccessibilityNodeInfo.java +++ b/android/view/accessibility/AccessibilityNodeInfo.java @@ -2325,7 +2325,7 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Returns whether the node is explicitly marked as a focusable unit by a screen reader. Note * that {@code false} indicates that it is not explicitly marked, not that the node is not - * a focusable unit. Screen readers should generally used other signals, such as + * a focusable unit. Screen readers should generally use other signals, such as * {@link #isFocusable()}, or the presence of text in a node, to determine what should receive * focus. * @@ -3695,8 +3695,9 @@ public class AccessibilityNodeInfo implements Parcelable { if (DEBUG) { builder.append("; sourceNodeId: " + mSourceNodeId); - builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId)); - builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId)); + builder.append("; windowId: " + mWindowId); + builder.append("; accessibilityViewId: ").append(getAccessibilityViewId(mSourceNodeId)); + builder.append("; virtualDescendantId: ").append(getVirtualDescendantId(mSourceNodeId)); builder.append("; mParentNodeId: " + mParentNodeId); builder.append("; traversalBefore: ").append(mTraversalBefore); builder.append("; traversalAfter: ").append(mTraversalAfter); @@ -3726,8 +3727,8 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append("]"); } - builder.append("; boundsInParent: " + mBoundsInParent); - builder.append("; boundsInScreen: " + mBoundsInScreen); + builder.append("; boundsInParent: ").append(mBoundsInParent); + builder.append("; boundsInScreen: ").append(mBoundsInScreen); builder.append("; packageName: ").append(mPackageName); builder.append("; className: ").append(mClassName); diff --git a/android/view/accessibility/AccessibilityRequestPreparer.java b/android/view/accessibility/AccessibilityRequestPreparer.java index 889feb98..25f830a5 100644 --- a/android/view/accessibility/AccessibilityRequestPreparer.java +++ b/android/view/accessibility/AccessibilityRequestPreparer.java @@ -44,10 +44,9 @@ public abstract class AccessibilityRequestPreparer { public static final int REQUEST_TYPE_EXTRA_DATA = 0x00000001; /** @hide */ - @IntDef(flag = true, - value = { - REQUEST_TYPE_EXTRA_DATA - }) + @IntDef(flag = true, prefix = { "REQUEST_TYPE_" }, value = { + REQUEST_TYPE_EXTRA_DATA + }) @Retention(RetentionPolicy.SOURCE) public @interface RequestTypes {} diff --git a/android/view/accessibility/AccessibilityWindowInfo.java b/android/view/accessibility/AccessibilityWindowInfo.java index f11767de..ef1a3f3b 100644 --- a/android/view/accessibility/AccessibilityWindowInfo.java +++ b/android/view/accessibility/AccessibilityWindowInfo.java @@ -87,6 +87,7 @@ public final class AccessibilityWindowInfo implements Parcelable { private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0; private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1; private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 1 << 2; + private static final int BOOLEAN_PROPERTY_PICTURE_IN_PICTURE = 1 << 3; // Housekeeping. private static final int MAX_POOL_SIZE = 10; @@ -103,8 +104,7 @@ public final class AccessibilityWindowInfo implements Parcelable { private final Rect mBoundsInScreen = new Rect(); private LongArray mChildIds; private CharSequence mTitle; - private int mAnchorId = UNDEFINED_WINDOW_ID; - private boolean mInPictureInPicture; + private long mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; private int mConnectionId = UNDEFINED_WINDOW_ID; @@ -202,7 +202,7 @@ public final class AccessibilityWindowInfo implements Parcelable { * * @hide */ - public void setAnchorId(int anchorId) { + public void setAnchorId(long anchorId) { mAnchorId = anchorId; } @@ -212,7 +212,8 @@ public final class AccessibilityWindowInfo implements Parcelable { * @return The anchor node, or {@code null} if none exists. */ public AccessibilityNodeInfo getAnchor() { - if ((mConnectionId == UNDEFINED_WINDOW_ID) || (mAnchorId == UNDEFINED_WINDOW_ID) + if ((mConnectionId == UNDEFINED_WINDOW_ID) + || (mAnchorId == AccessibilityNodeInfo.UNDEFINED_NODE_ID) || (mParentId == UNDEFINED_WINDOW_ID)) { return null; } @@ -224,17 +225,7 @@ public final class AccessibilityWindowInfo implements Parcelable { /** @hide */ public void setPictureInPicture(boolean pictureInPicture) { - mInPictureInPicture = pictureInPicture; - } - - /** - * Check if the window is in picture-in-picture mode. - * - * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise. - * @removed - */ - public boolean inPictureInPicture() { - return isInPictureInPictureMode(); + setBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE, pictureInPicture); } /** @@ -243,7 +234,7 @@ public final class AccessibilityWindowInfo implements Parcelable { * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise. */ public boolean isInPictureInPictureMode() { - return mInPictureInPicture; + return getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE); } /** @@ -463,7 +454,6 @@ public final class AccessibilityWindowInfo implements Parcelable { infoClone.mBoundsInScreen.set(info.mBoundsInScreen); infoClone.mTitle = info.mTitle; infoClone.mAnchorId = info.mAnchorId; - infoClone.mInPictureInPicture = info.mInPictureInPicture; if (info.mChildIds != null && info.mChildIds.size() > 0) { if (infoClone.mChildIds == null) { @@ -520,8 +510,7 @@ public final class AccessibilityWindowInfo implements Parcelable { parcel.writeInt(mParentId); mBoundsInScreen.writeToParcel(parcel, flags); parcel.writeCharSequence(mTitle); - parcel.writeInt(mAnchorId); - parcel.writeInt(mInPictureInPicture ? 1 : 0); + parcel.writeLong(mAnchorId); final LongArray childIds = mChildIds; if (childIds == null) { @@ -545,8 +534,7 @@ public final class AccessibilityWindowInfo implements Parcelable { mParentId = parcel.readInt(); mBoundsInScreen.readFromParcel(parcel); mTitle = parcel.readCharSequence(); - mAnchorId = parcel.readInt(); - mInPictureInPicture = parcel.readInt() == 1; + mAnchorId = parcel.readLong(); final int childCount = parcel.readInt(); if (childCount > 0) { @@ -593,7 +581,7 @@ public final class AccessibilityWindowInfo implements Parcelable { builder.append(", bounds=").append(mBoundsInScreen); builder.append(", focused=").append(isFocused()); builder.append(", active=").append(isActive()); - builder.append(", pictureInPicture=").append(inPictureInPicture()); + builder.append(", pictureInPicture=").append(isInPictureInPictureMode()); if (DEBUG) { builder.append(", parent=").append(mParentId); builder.append(", children=["); @@ -611,7 +599,8 @@ public final class AccessibilityWindowInfo implements Parcelable { builder.append(']'); } else { builder.append(", hasParent=").append(mParentId != UNDEFINED_WINDOW_ID); - builder.append(", isAnchored=").append(mAnchorId != UNDEFINED_WINDOW_ID); + builder.append(", isAnchored=") + .append(mAnchorId != AccessibilityNodeInfo.UNDEFINED_NODE_ID); builder.append(", hasChildren=").append(mChildIds != null && mChildIds.size() > 0); } @@ -633,8 +622,7 @@ public final class AccessibilityWindowInfo implements Parcelable { mChildIds.clear(); } mConnectionId = UNDEFINED_WINDOW_ID; - mAnchorId = UNDEFINED_WINDOW_ID; - mInPictureInPicture = false; + mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID; mTitle = null; } diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java index 547e0db9..26974545 100644 --- a/android/view/autofill/AutofillManager.java +++ b/android/view/autofill/AutofillManager.java @@ -36,6 +36,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.service.autofill.AutofillService; import android.service.autofill.FillEventHistory; +import android.service.autofill.UserData; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -45,6 +46,7 @@ import android.view.View; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.Preconditions; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -426,7 +428,7 @@ public final class AutofillManager { * @hide */ public AutofillManager(Context context, IAutoFillManager service) { - mContext = context; + mContext = Preconditions.checkNotNull(context, "context cannot be null"); mService = service; } @@ -455,7 +457,7 @@ public final class AutofillManager { if (mSessionId != NO_SESSION) { ensureServiceClientAddedIfNeededLocked(); - final AutofillClient client = getClientLocked(); + final AutofillClient client = getClient(); if (client != null) { try { final boolean sessionWasRestored = mService.restoreSession(mSessionId, @@ -528,10 +530,13 @@ public final class AutofillManager { * @return whether autofill is enabled for the current user. */ public boolean isEnabled() { - if (!hasAutofillFeature() || isDisabledByService()) { + if (!hasAutofillFeature()) { return false; } synchronized (mLock) { + if (isDisabledByServiceLocked()) { + return false; + } ensureServiceClientAddedIfNeededLocked(); return mEnabled; } @@ -603,19 +608,16 @@ public final class AutofillManager { } private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) { - if (isDisabledByService()) { + if (isDisabledByServiceLocked()) { if (sVerbose) { Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view + ") on state " + getStateAsStringLocked()); } return true; } - if (mState == STATE_FINISHED && (flags & FLAG_MANUAL_REQUEST) == 0) { - if (sVerbose) { - Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view - + ") on state " + getStateAsStringLocked()); - } - return true; + if (sVerbose && isFinishedLocked()) { + Log.v(TAG, "not ignoring notifyViewEntered(flags=" + flags + ", view=" + view + + ") on state " + getStateAsStringLocked()); } return false; } @@ -1006,6 +1008,76 @@ public final class AutofillManager { } } + /** + * Returns the component name of the {@link AutofillService} that is enabled for the current + * user. + */ + @Nullable + public ComponentName getAutofillServiceComponentName() { + if (mService == null) return null; + + try { + return mService.getAutofillServiceComponentName(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets the user data used for + * field classification. + * + *

Note: This method should only be called by an app providing an autofill service. + * + * @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. + */ + @Nullable public UserData getUserData() { + try { + return mService.getUserData(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return null; + } + } + + /** + * Sets the user data used for + * field classification + * + *

Note: 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. + */ + public void setUserData(@Nullable UserData userData) { + try { + mService.setUserData(userData); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Checks if field classification is + * enabled. + * + *

As field classification is an expensive operation, it could be disabled, either + * temporarily (for example, because the service exceeded a rate-limit threshold) or + * permanently (for example, because the device is a low-level device). + * + *

Note: 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. + */ + public boolean isFieldClassificationEnabled() { + try { + return mService.isFieldClassificationEnabled(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return false; + } + } + /** * Returns {@code true} if autofill is supported by the current device and * is supported for this user. @@ -1026,7 +1098,8 @@ public final class AutofillManager { } } - private AutofillClient getClientLocked() { + // Note: don't need to use locked suffix because mContext is final. + private AutofillClient getClient() { final AutofillClient client = mContext.getAutofillClient(); if (client == null && sDebug) { Log.d(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context " @@ -1081,24 +1154,24 @@ public final class AutofillManager { Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value + ", flags=" + flags + ", state=" + getStateAsStringLocked()); } - if (mState != STATE_UNKNOWN && (flags & FLAG_MANUAL_REQUEST) == 0) { + if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) { if (sVerbose) { Log.v(TAG, "not automatically starting session for " + id - + " on state " + getStateAsStringLocked()); + + " on state " + getStateAsStringLocked() + " and flags " + flags); } return; } try { - final AutofillClient client = getClientLocked(); + final AutofillClient client = getClient(); + if (client == null) return; // NOTE: getClient() already logd it.. + mSessionId = mService.startSession(mContext.getActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), mCallback != null, flags, client.getComponentName()); if (mSessionId != NO_SESSION) { mState = STATE_ACTIVE; } - if (client != null) { - client.autofillCallbackResetableStateAvailable(); - } + client.autofillCallbackResetableStateAvailable(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1150,7 +1223,9 @@ public final class AutofillManager { try { if (restartIfNecessary) { - final AutofillClient client = getClientLocked(); + final AutofillClient client = getClient(); + if (client == null) return; // NOTE: getClient() already logd it.. + final int newId = mService.updateOrRestartSession(mContext.getActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), mCallback != null, flags, client.getComponentName(), mSessionId, action); @@ -1158,9 +1233,7 @@ public final class AutofillManager { if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId); mSessionId = newId; mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE; - if (client != null) { - client.autofillCallbackResetableStateAvailable(); - } + client.autofillCallbackResetableStateAvailable(); } } else { mService.updateSession(mSessionId, id, bounds, value, action, flags, @@ -1173,7 +1246,7 @@ public final class AutofillManager { } private void ensureServiceClientAddedIfNeededLocked() { - if (getClientLocked() == null) { + if (getClient() == null) { return; } @@ -1256,7 +1329,7 @@ public final class AutofillManager { AutofillCallback callback = null; synchronized (mLock) { if (mSessionId == sessionId) { - AutofillClient client = getClientLocked(); + AutofillClient client = getClient(); if (client != null) { if (client.autofillCallbackRequestShowFillUi(anchor, width, height, @@ -1281,7 +1354,7 @@ public final class AutofillManager { Intent fillInIntent) { synchronized (mLock) { if (sessionId == mSessionId) { - AutofillClient client = getClientLocked(); + final AutofillClient client = getClient(); if (client != null) { client.autofillCallbackAuthenticate(authenticationId, intent, fillInIntent); } @@ -1346,7 +1419,7 @@ public final class AutofillManager { return; } - final AutofillClient client = getClientLocked(); + final AutofillClient client = getClient(); if (client == null) { return; } @@ -1523,7 +1596,7 @@ public final class AutofillManager { // 1. If local and remote session id are off sync the UI would be stuck shown // 2. There is a race between the user state being destroyed due the fill // service being uninstalled and the UI being dismissed. - AutofillClient client = getClientLocked(); + AutofillClient client = getClient(); if (client != null) { if (client.autofillCallbackRequestHideFillUi() && mCallback != null) { callback = mCallback; @@ -1553,7 +1626,7 @@ public final class AutofillManager { AutofillCallback callback = null; synchronized (mLock) { - if (mSessionId == sessionId && getClientLocked() != null) { + if (mSessionId == sessionId && getClient() != null) { callback = mCallback; } } @@ -1610,7 +1683,7 @@ public final class AutofillManager { * @return The view or {@code null} if view was not found */ private View findView(@NonNull AutofillId autofillId) { - final AutofillClient client = getClientLocked(); + final AutofillClient client = getClient(); if (client == null) { return null; @@ -1644,7 +1717,7 @@ public final class AutofillManager { pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId); pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked()); pw.print(pfx); pw.print("context: "); pw.println(mContext); - pw.print(pfx); pw.print("client: "); pw.println(getClientLocked()); + pw.print(pfx); pw.print("client: "); pw.println(getClient()); 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); @@ -1686,12 +1759,16 @@ public final class AutofillManager { return mState == STATE_ACTIVE; } - private boolean isDisabledByService() { + private boolean isDisabledByServiceLocked() { return mState == STATE_DISABLED_BY_SERVICE; } + private boolean isFinishedLocked() { + return mState == STATE_FINISHED; + } + private void post(Runnable runnable) { - final AutofillClient client = getClientLocked(); + final AutofillClient client = getClient(); if (client == null) { if (sVerbose) Log.v(TAG, "ignoring post() because client is null"); return; @@ -1774,7 +1851,7 @@ public final class AutofillManager { * @param trackedIds The views to be tracked */ TrackedViews(@Nullable AutofillId[] trackedIds) { - final AutofillClient client = getClientLocked(); + final AutofillClient client = getClient(); if (trackedIds != null && client != null) { final boolean[] isVisible; @@ -1815,7 +1892,7 @@ public final class AutofillManager { * @param isVisible visible if the view is visible in the view hierarchy. */ void notifyViewVisibilityChanged(@NonNull AutofillId id, boolean isVisible) { - AutofillClient client = getClientLocked(); + AutofillClient client = getClient(); if (sDebug) { Log.d(TAG, "notifyViewVisibilityChanged(): id=" + id + " isVisible=" @@ -1852,7 +1929,7 @@ public final class AutofillManager { void onVisibleForAutofillLocked() { // The visibility of the views might have changed while the client was not be visible, // hence update the visibility state for all views. - AutofillClient client = getClientLocked(); + AutofillClient client = getClient(); ArraySet updatedVisibleTrackedIds = null; ArraySet updatedInvisibleTrackedIds = null; if (client != null) { @@ -1918,7 +1995,11 @@ public final class AutofillManager { public abstract static class AutofillCallback { /** @hide */ - @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN, EVENT_INPUT_UNAVAILABLE}) + @IntDef(prefix = { "EVENT_INPUT_" }, value = { + EVENT_INPUT_SHOWN, + EVENT_INPUT_HIDDEN, + EVENT_INPUT_UNAVAILABLE + }) @Retention(RetentionPolicy.SOURCE) public @interface AutofillEventType {} diff --git a/android/view/autofill/AutofillPopupWindow.java b/android/view/autofill/AutofillPopupWindow.java index b4688bb1..5cba21e3 100644 --- a/android/view/autofill/AutofillPopupWindow.java +++ b/android/view/autofill/AutofillPopupWindow.java @@ -108,11 +108,12 @@ public class AutofillPopupWindow extends PopupWindow { // symmetrically when the dropdown is below and above the anchor. final View actualAnchor; if (virtualBounds != null) { + final int[] mLocationOnScreen = new int[] {virtualBounds.left, virtualBounds.top}; actualAnchor = new View(anchor.getContext()) { @Override public void getLocationOnScreen(int[] location) { - location[0] = virtualBounds.left; - location[1] = virtualBounds.top; + location[0] = mLocationOnScreen[0]; + location[1] = mLocationOnScreen[1]; } @Override @@ -178,6 +179,12 @@ public class AutofillPopupWindow extends PopupWindow { virtualBounds.right, virtualBounds.bottom); actualAnchor.setScrollX(anchor.getScrollX()); actualAnchor.setScrollY(anchor.getScrollY()); + + anchor.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { + mLocationOnScreen[0] = mLocationOnScreen[0] - (scrollX - oldScrollX); + mLocationOnScreen[1] = mLocationOnScreen[1] - (scrollY - oldScrollY); + }); + actualAnchor.setWillNotDraw(true); } else { actualAnchor = anchor; } diff --git a/android/view/autofill/AutofillValue.java b/android/view/autofill/AutofillValue.java index 3beae11c..8e649de5 100644 --- a/android/view/autofill/AutofillValue.java +++ b/android/view/autofill/AutofillValue.java @@ -177,7 +177,7 @@ public final class AutofillValue implements Parcelable { .append("[type=").append(mType) .append(", value="); if (isText()) { - string.append(((CharSequence) mValue).length()).append("_chars"); + Helper.appendRedacted(string, (CharSequence) mValue); } else { string.append(mValue); } diff --git a/android/view/autofill/Helper.java b/android/view/autofill/Helper.java index 829e7f3a..4b2c53c7 100644 --- a/android/view/autofill/Helper.java +++ b/android/view/autofill/Helper.java @@ -16,11 +16,8 @@ package android.view.autofill; -import android.os.Bundle; - -import java.util.Arrays; -import java.util.Objects; -import java.util.Set; +import android.annotation.NonNull; +import android.annotation.Nullable; /** @hide */ public final class Helper { @@ -29,25 +26,37 @@ public final class Helper { public static boolean sDebug = false; public static boolean sVerbose = false; - public static final String REDACTED = "[REDACTED]"; + /** + * Appends {@code value} to the {@code builder} redacting its contents. + */ + public static void appendRedacted(@NonNull StringBuilder builder, + @Nullable CharSequence value) { + builder.append(getRedacted(value)); + } - static StringBuilder append(StringBuilder builder, Bundle bundle) { - if (bundle == null || !sDebug) { + /** + * Gets the redacted version of a value. + */ + @NonNull + public static String getRedacted(@Nullable CharSequence value) { + return (value == null) ? "null" : value.length() + "_chars"; + } + + /** + * Appends {@code values} to the {@code builder} redacting its contents. + */ + public static void appendRedacted(@NonNull StringBuilder builder, @Nullable String[] values) { + if (values == null) { builder.append("N/A"); - } else if (!sVerbose) { - builder.append(REDACTED); - } else { - final Set keySet = bundle.keySet(); - builder.append("[Bundle with ").append(keySet.size()).append(" extras:"); - for (String key : keySet) { - final Object value = bundle.get(key); - builder.append(' ').append(key).append('='); - builder.append((value instanceof Object[]) - ? Arrays.toString((Objects[]) value) : value); - } - builder.append(']'); + return; + } + builder.append("["); + for (String value : values) { + builder.append(" '"); + appendRedacted(builder, value); + builder.append("'"); } - return builder; + builder.append(" ]"); } private Helper() { diff --git a/android/view/inputmethod/InputMethodInfo.java b/android/view/inputmethod/InputMethodInfo.java index f0645b89..c69543f6 100644 --- a/android/view/inputmethod/InputMethodInfo.java +++ b/android/view/inputmethod/InputMethodInfo.java @@ -66,6 +66,11 @@ public final class InputMethodInfo implements Parcelable { */ final ResolveInfo mService; + /** + * IME only supports VR mode. + */ + final boolean mIsVrOnly; + /** * The unique string Id to identify the input method. This is generated * from the input method component. @@ -149,6 +154,7 @@ public final class InputMethodInfo implements Parcelable { PackageManager pm = context.getPackageManager(); String settingsActivityComponent = null; + boolean isVrOnly; int isDefaultResId = 0; XmlResourceParser parser = null; @@ -179,6 +185,7 @@ public final class InputMethodInfo implements Parcelable { com.android.internal.R.styleable.InputMethod); settingsActivityComponent = sa.getString( com.android.internal.R.styleable.InputMethod_settingsActivity); + isVrOnly = sa.getBoolean(com.android.internal.R.styleable.InputMethod_isVrOnly, false); isDefaultResId = sa.getResourceId( com.android.internal.R.styleable.InputMethod_isDefault, 0); supportsSwitchingToNextInputMethod = sa.getBoolean( @@ -254,6 +261,8 @@ public final class InputMethodInfo implements Parcelable { mIsDefaultResId = isDefaultResId; mIsAuxIme = isAuxIme; mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; + // TODO(b/68948291): remove this meta-data before release. + mIsVrOnly = isVrOnly || service.serviceInfo.metaData.getBoolean("isVrOnly", false); } InputMethodInfo(Parcel source) { @@ -262,6 +271,7 @@ public final class InputMethodInfo implements Parcelable { mIsDefaultResId = source.readInt(); mIsAuxIme = source.readInt() == 1; mSupportsSwitchingToNextInputMethod = source.readInt() == 1; + mIsVrOnly = source.readBoolean(); mService = ResolveInfo.CREATOR.createFromParcel(source); mSubtypes = new InputMethodSubtypeArray(source); mForceDefault = false; @@ -274,7 +284,8 @@ public final class InputMethodInfo implements Parcelable { CharSequence label, String settingsActivity) { this(buildDummyResolveInfo(packageName, className, label), false /* isAuxIme */, settingsActivity, null /* subtypes */, 0 /* isDefaultResId */, - false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */); + false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */, + false /* isVrOnly */); } /** @@ -285,7 +296,7 @@ public final class InputMethodInfo implements Parcelable { String settingsActivity, List subtypes, int isDefaultResId, boolean forceDefault) { this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault, - true /* supportsSwitchingToNextInputMethod */); + true /* supportsSwitchingToNextInputMethod */, false /* isVrOnly */); } /** @@ -294,7 +305,7 @@ public final class InputMethodInfo implements Parcelable { */ public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity, List subtypes, int isDefaultResId, boolean forceDefault, - boolean supportsSwitchingToNextInputMethod) { + boolean supportsSwitchingToNextInputMethod, boolean isVrOnly) { final ServiceInfo si = ri.serviceInfo; mService = ri; mId = new ComponentName(si.packageName, si.name).flattenToShortString(); @@ -304,6 +315,7 @@ public final class InputMethodInfo implements Parcelable { mSubtypes = new InputMethodSubtypeArray(subtypes); mForceDefault = forceDefault; mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; + mIsVrOnly = isVrOnly; } private static ResolveInfo buildDummyResolveInfo(String packageName, String className, @@ -397,6 +409,14 @@ public final class InputMethodInfo implements Parcelable { return mSettingsActivityName; } + /** + * Returns true if IME supports VR mode only. + * @hide + */ + public boolean isVrOnly() { + return mIsVrOnly; + } + /** * Return the count of the subtypes of Input Method. */ @@ -444,6 +464,7 @@ public final class InputMethodInfo implements Parcelable { public void dump(Printer pw, String prefix) { pw.println(prefix + "mId=" + mId + " mSettingsActivityName=" + mSettingsActivityName + + " mIsVrOnly=" + mIsVrOnly + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod); pw.println(prefix + "mIsDefaultResId=0x" + Integer.toHexString(mIsDefaultResId)); @@ -509,6 +530,7 @@ public final class InputMethodInfo implements Parcelable { dest.writeInt(mIsDefaultResId); dest.writeInt(mIsAuxIme ? 1 : 0); dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0); + dest.writeBoolean(mIsVrOnly); mService.writeToParcel(dest, flags); mSubtypes.writeToParcel(dest); } diff --git a/android/view/inputmethod/InputMethodManager.java b/android/view/inputmethod/InputMethodManager.java index 92d1de8e..80d7b6b7 100644 --- a/android/view/inputmethod/InputMethodManager.java +++ b/android/view/inputmethod/InputMethodManager.java @@ -24,6 +24,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.content.Context; import android.graphics.Rect; +import android.inputmethodservice.InputMethodService; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -280,10 +281,10 @@ public final class InputMethodManager { boolean mActive = false; /** - * Set whenever this client becomes inactive, to know we need to reset - * state with the IME the next time we receive focus. + * {@code true} if next {@link #onPostWindowFocus(View, View, int, boolean, int)} needs to + * restart input. */ - boolean mHasBeenInactive = true; + boolean mRestartOnNextWindowFocus = true; /** * As reported by IME through InputConnection. @@ -488,7 +489,7 @@ public final class InputMethodManager { // Some other client has starting using the IME, so note // that this happened and make sure our own editor's // state is reset. - mHasBeenInactive = true; + mRestartOnNextWindowFocus = true; try { // Note that finishComposingText() is allowed to run // even when we are not active. @@ -499,7 +500,7 @@ public final class InputMethodManager { // Check focus again in case that "onWindowFocus" is called before // handling this message. if (mServedView != null && mServedView.hasWindowFocus()) { - if (checkFocusNoStartInput(mHasBeenInactive)) { + if (checkFocusNoStartInput(mRestartOnNextWindowFocus)) { final int reason = active ? InputMethodClient.START_INPUT_REASON_ACTIVATED_BY_IMMS : InputMethodClient.START_INPUT_REASON_DEACTIVATED_BY_IMMS; @@ -697,6 +698,19 @@ public final class InputMethodManager { } } + /** + * Returns a list of VR InputMethod currently installed. + * @hide + */ + @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) + public List getVrInputMethodList() { + try { + return mService.getVrInputMethodList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + public List getEnabledInputMethodList() { try { return mService.getEnabledInputMethodList(); @@ -722,7 +736,20 @@ public final class InputMethodManager { } } + /** + * @deprecated Use {@link InputMethodService#showStatusIcon(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 showStatusIcon(IBinder imeToken, String packageName, int iconId) { + showStatusIconInternal(imeToken, packageName, iconId); + } + + /** + * @hide + */ + public void showStatusIconInternal(IBinder imeToken, String packageName, int iconId) { try { mService.updateStatusIcon(imeToken, packageName, iconId); } catch (RemoteException e) { @@ -730,7 +757,20 @@ public final class InputMethodManager { } } + /** + * @deprecated Use {@link InputMethodService#hideStatusIcon()} 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 hideStatusIcon(IBinder imeToken) { + hideStatusIconInternal(imeToken); + } + + /** + * @hide + */ + public void hideStatusIconInternal(IBinder imeToken) { try { mService.updateStatusIcon(imeToken, null, 0); } catch (RemoteException e) { @@ -1108,7 +1148,6 @@ public final class InputMethodManager { } } - /** * This method toggles the input method window display. * If the input window is already displayed, it gets hidden. @@ -1294,48 +1333,31 @@ public final class InputMethodManager { + Integer.toHexString(controlFlags)); final InputBindResult res = mService.startInputOrWindowGainedFocus( startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode, - windowFlags, tba, servedContext, missingMethodFlags); + windowFlags, tba, servedContext, missingMethodFlags, + view.getContext().getApplicationInfo().targetSdkVersion); if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res); - if (res != null) { - if (res.id != null) { - setInputChannelLocked(res.channel); - mBindSequence = res.sequence; - mCurMethod = res.method; - mCurId = res.id; - mNextUserActionNotificationSequenceNumber = - res.userActionNotificationSequenceNumber; - } else { - if (res.channel != null && res.channel != mCurChannel) { - res.channel.dispose(); - } - if (mCurMethod == null) { - // This means there is no input method available. - if (DEBUG) Log.v(TAG, "ABORT input: no input method!"); - return true; - } - } - } else { - if (startInputReason - == InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN) { - // We are here probably because of an obsolete window-focus-in message sent - // to windowGainingFocus. Since IMMS determines whether a Window can have - // IME focus or not by using the latest window focus state maintained in the - // WMS, this kind of race condition cannot be avoided. One obvious example - // would be that we have already received a window-focus-out message but the - // UI thread is still handling previous window-focus-in message here. - // TODO: InputBindResult should have the error code. - if (DEBUG) Log.w(TAG, "startInputOrWindowGainedFocus failed. " - + "Window focus may have already been lost. " - + "win=" + windowGainingFocus + " view=" + dumpViewInfo(view)); - if (!mActive) { - // mHasBeenInactive is a latch switch to forcefully refresh IME focus - // state when an inactive (mActive == false) client is gaining window - // focus. In case we have unnecessary disable the latch due to this - // spurious wakeup, we re-enable the latch here. - // TODO: Come up with more robust solution. - mHasBeenInactive = true; - } - } + if (res == null) { + Log.wtf(TAG, "startInputOrWindowGainedFocus must not return" + + " null. startInputReason=" + + InputMethodClient.getStartInputReason(startInputReason) + + " editorInfo=" + tba + + " controlFlags=#" + Integer.toHexString(controlFlags)); + return false; + } + if (res.id != null) { + setInputChannelLocked(res.channel); + mBindSequence = res.sequence; + mCurMethod = res.method; + mCurId = res.id; + mNextUserActionNotificationSequenceNumber = + res.userActionNotificationSequenceNumber; + } else if (res.channel != null && res.channel != mCurChannel) { + res.channel.dispose(); + } + switch (res.result) { + case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW: + mRestartOnNextWindowFocus = true; + break; } if (mCurMethod != null && mCompletions != null) { try { @@ -1511,9 +1533,9 @@ public final class InputMethodManager { + " softInputMode=" + InputMethodClient.softInputModeToString(softInputMode) + " first=" + first + " flags=#" + Integer.toHexString(windowFlags)); - if (mHasBeenInactive) { - if (DEBUG) Log.v(TAG, "Has been inactive! Starting fresh"); - mHasBeenInactive = false; + if (mRestartOnNextWindowFocus) { + if (DEBUG) Log.v(TAG, "Restarting due to mRestartOnNextWindowFocus"); + mRestartOnNextWindowFocus = false; forceNewFocus = true; } focusInLocked(focusedView != null ? focusedView : rootView); @@ -1549,7 +1571,8 @@ public final class InputMethodManager { mService.startInputOrWindowGainedFocus( InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient, rootView.getWindowToken(), controlFlags, softInputMode, windowFlags, null, - null, 0 /* missingMethodFlags */); + null, 0 /* missingMethodFlags */, + rootView.getContext().getApplicationInfo().targetSdkVersion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1787,8 +1810,19 @@ public final class InputMethodManager { * when it was started, which allows it to perform this operation on * itself. * @param id The unique identifier for the new input method to be switched to. + * @deprecated Use {@link InputMethodService#setInputMethod(String)} 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 setInputMethod(IBinder token, String id) { + setInputMethodInternal(token, id); + } + + /** + * @hide + */ + public void setInputMethodInternal(IBinder token, String id) { try { mService.setInputMethod(token, id); } catch (RemoteException e) { @@ -1804,8 +1838,21 @@ public final class InputMethodManager { * itself. * @param id The unique identifier for the new input method to be switched to. * @param subtype The new subtype of the new input method to be switched to. + * @deprecated Use + * {@link InputMethodService#setInputMethodAndSubtype(String, InputMethodSubtype)} + * 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 setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) { + setInputMethodAndSubtypeInternal(token, id, subtype); + } + + /** + * @hide + */ + public void setInputMethodAndSubtypeInternal( + IBinder token, String id, InputMethodSubtype subtype) { try { mService.setInputMethodAndSubtype(token, id, subtype); } catch (RemoteException e) { @@ -1824,8 +1871,19 @@ 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 public void hideSoftInputFromInputMethod(IBinder token, int flags) { + hideSoftInputFromInputMethodInternal(token, flags); + } + + /** + * @hide + */ + public void hideSoftInputFromInputMethodInternal(IBinder token, int flags) { try { mService.hideMySoftInput(token, flags); } catch (RemoteException e) { @@ -1845,8 +1903,19 @@ 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 public void showSoftInputFromInputMethod(IBinder token, int flags) { + showSoftInputFromInputMethodInternal(token, flags); + } + + /** + * @hide + */ + public void showSoftInputFromInputMethodInternal(IBinder token, int flags) { try { mService.showMySoftInput(token, flags); } catch (RemoteException e) { @@ -2226,8 +2295,19 @@ public final class InputMethodManager { * which allows it to perform this operation on itself. * @return true if the current input method and subtype was successfully switched to the last * used input method and subtype. + * @deprecated Use {@link InputMethodService#switchToLastInputMethod()} 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 boolean switchToLastInputMethod(IBinder imeToken) { + return switchToLastInputMethodInternal(imeToken); + } + + /** + * @hide + */ + public boolean switchToLastInputMethodInternal(IBinder imeToken) { synchronized (mH) { try { return mService.switchToLastInputMethod(imeToken); @@ -2246,8 +2326,19 @@ public final class InputMethodManager { * belongs to the current IME * @return true if the current input method and subtype was successfully switched to the next * input method and subtype. + * @deprecated Use {@link InputMethodService#switchToNextInputMethod(boolean)} 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 boolean switchToNextInputMethod(IBinder imeToken, boolean onlyCurrentIme) { + return switchToNextInputMethodInternal(imeToken, onlyCurrentIme); + } + + /** + * @hide + */ + public boolean switchToNextInputMethodInternal(IBinder imeToken, boolean onlyCurrentIme) { synchronized (mH) { try { return mService.switchToNextInputMethod(imeToken, onlyCurrentIme); @@ -2267,8 +2358,19 @@ public final class InputMethodManager { * between IMEs and subtypes. * @param imeToken Supplies the identifying token given to an input method when it was started, * which allows it to perform this operation on itself. + * @deprecated Use {@link InputMethodService#shouldOfferSwitchingToNextInputMethod()} + * 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 boolean shouldOfferSwitchingToNextInputMethod(IBinder imeToken) { + return shouldOfferSwitchingToNextInputMethodInternal(imeToken); + } + + /** + * @hide + */ + public boolean shouldOfferSwitchingToNextInputMethodInternal(IBinder imeToken) { synchronized (mH) { try { return mService.shouldOfferSwitchingToNextInputMethod(imeToken); @@ -2365,7 +2467,7 @@ public final class InputMethodManager { p.println(" mMainLooper=" + mMainLooper); p.println(" mIInputContext=" + mIInputContext); p.println(" mActive=" + mActive - + " mHasBeenInactive=" + mHasBeenInactive + + " mRestartOnNextWindowFocus=" + mRestartOnNextWindowFocus + " mBindSequence=" + mBindSequence + " mCurId=" + mCurId); p.println(" mFullscreenMode=" + mFullscreenMode); diff --git a/android/view/inputmethod/InputMethodManagerInternal.java b/android/view/inputmethod/InputMethodManagerInternal.java index 77df4e38..e13813e5 100644 --- a/android/view/inputmethod/InputMethodManagerInternal.java +++ b/android/view/inputmethod/InputMethodManagerInternal.java @@ -16,6 +16,8 @@ package android.view.inputmethod; +import android.content.ComponentName; + /** * Input method manager local system service interface. * @@ -37,4 +39,9 @@ public interface InputMethodManagerInternal { * Hides the current input method, if visible. */ void hideCurrentInputMethod(); + + /** + * Switches to VR InputMethod defined in the packageName of {@param componentName}. + */ + void startVrInputMethodNoCheck(ComponentName componentName); } diff --git a/android/view/textclassifier/EntityConfidence.java b/android/view/textclassifier/EntityConfidence.java index 0589d204..19660d95 100644 --- a/android/view/textclassifier/EntityConfidence.java +++ b/android/view/textclassifier/EntityConfidence.java @@ -18,13 +18,12 @@ package android.view.textclassifier; import android.annotation.FloatRange; import android.annotation.NonNull; +import android.util.ArrayMap; import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,42 +35,43 @@ import java.util.Map; */ final class EntityConfidence { - private final Map mEntityConfidence = new HashMap<>(); - - private final Comparator mEntityComparator = (e1, e2) -> { - float score1 = mEntityConfidence.get(e1); - float score2 = mEntityConfidence.get(e2); - if (score1 > score2) { - return -1; - } - if (score1 < score2) { - return 1; - } - return 0; - }; + private final ArrayMap mEntityConfidence = new ArrayMap<>(); + private final ArrayList mSortedEntities = new ArrayList<>(); EntityConfidence() {} EntityConfidence(@NonNull EntityConfidence source) { Preconditions.checkNotNull(source); mEntityConfidence.putAll(source.mEntityConfidence); + mSortedEntities.addAll(source.mSortedEntities); } /** - * Sets an entity type for the classified text and assigns a confidence score. + * Constructs an EntityConfidence from a map of entity to confidence. * - * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence). - * 0 implies the entity does not exist for the classified text. - * Values greater than 1 are clamped to 1. + * Map entries that have 0 confidence are removed, and values greater than 1 are clamped to 1. + * + * @param source a map from entity to a confidence value in the range 0 (low confidence) to + * 1 (high confidence). */ - public void setEntityType( - @NonNull T type, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) { - Preconditions.checkNotNull(type); - if (confidenceScore > 0) { - mEntityConfidence.put(type, Math.min(1, confidenceScore)); - } else { - mEntityConfidence.remove(type); + EntityConfidence(@NonNull Map source) { + Preconditions.checkNotNull(source); + + // Prune non-existent entities and clamp to 1. + mEntityConfidence.ensureCapacity(source.size()); + for (Map.Entry 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); + }); } /** @@ -80,10 +80,7 @@ final class EntityConfidence { */ @NonNull public List getEntities() { - List entities = new ArrayList<>(mEntityConfidence.size()); - entities.addAll(mEntityConfidence.keySet()); - entities.sort(mEntityComparator); - return Collections.unmodifiableList(entities); + return Collections.unmodifiableList(mSortedEntities); } /** diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java index f675c355..7ffbf635 100644 --- a/android/view/textclassifier/TextClassification.java +++ b/android/view/textclassifier/TextClassification.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.LocaleList; +import android.util.ArrayMap; import android.view.View.OnClickListener; import android.view.textclassifier.TextClassifier.EntityType; @@ -32,12 +33,14 @@ import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Map; /** * Information for generating a widget to handle classified text. * *

A TextClassification object contains icons, labels, onClickListeners and intents that may - * be used to build a widget that can be used to act on classified text. + * be used to build a widget that can be used to act on classified text. There is the concept of a + * primary action and other secondary actions. * *

e.g. building a view that, when clicked, shares the classified text with the preferred app: * @@ -62,11 +65,18 @@ import java.util.Locale; * view.startActionMode(new ActionMode.Callback() { * * public boolean onCreateActionMode(ActionMode mode, Menu menu) { - * for (int i = 0; i < classification.getActionCount(); i++) { - * if (thisAppHasPermissionToInvokeIntent(classification.getIntent(i))) { - * menu.add(Menu.NONE, i, 20, classification.getLabel(i)) - * .setIcon(classification.getIcon(i)) - * .setIntent(classification.getIntent(i)); + * // Add the "primary" action. + * if (thisAppHasPermissionToInvokeIntent(classification.getIntent())) { + * menu.add(Menu.NONE, 0, 20, classification.getLabel()) + * .setIcon(classification.getIcon()) + * .setIntent(classification.getIntent()); + * } + * // Add the "secondary" actions. + * for (int i = 0; i < classification.getSecondaryActionsCount(); i++) { + * if (thisAppHasPermissionToInvokeIntent(classification.getSecondaryIntent(i))) { + * menu.add(Menu.NONE, i + 1, 20, classification.getSecondaryLabel(i)) + * .setIcon(classification.getSecondaryIcon(i)) + * .setIntent(classification.getSecondaryIntent(i)); * } * } * return true; @@ -90,36 +100,43 @@ public final class TextClassification { static final TextClassification EMPTY = new TextClassification.Builder().build(); @NonNull private final String mText; - @NonNull private final List mIcons; - @NonNull private final List mLabels; - @NonNull private final List mIntents; - @NonNull private final List mOnClickListeners; + @Nullable private final Drawable mPrimaryIcon; + @Nullable private final String mPrimaryLabel; + @Nullable private final Intent mPrimaryIntent; + @Nullable private final OnClickListener mPrimaryOnClickListener; + @NonNull private final List mSecondaryIcons; + @NonNull private final List mSecondaryLabels; + @NonNull private final List mSecondaryIntents; + @NonNull private final List mSecondaryOnClickListeners; @NonNull private final EntityConfidence mEntityConfidence; - @NonNull private final List mEntities; - private int mLogType; - @NonNull private final String mVersionInfo; + @NonNull private final String mSignature; private TextClassification( @Nullable String text, - @NonNull List icons, - @NonNull List labels, - @NonNull List intents, - @NonNull List onClickListeners, - @NonNull EntityConfidence entityConfidence, - int logType, - @NonNull String versionInfo) { - Preconditions.checkArgument(labels.size() == intents.size()); - Preconditions.checkArgument(icons.size() == intents.size()); - Preconditions.checkArgument(onClickListeners.size() == intents.size()); + @Nullable Drawable primaryIcon, + @Nullable String primaryLabel, + @Nullable Intent primaryIntent, + @Nullable OnClickListener primaryOnClickListener, + @NonNull List secondaryIcons, + @NonNull List secondaryLabels, + @NonNull List secondaryIntents, + @NonNull List secondaryOnClickListeners, + @NonNull Map entityConfidence, + @NonNull String signature) { + Preconditions.checkArgument(secondaryLabels.size() == secondaryIntents.size()); + Preconditions.checkArgument(secondaryIcons.size() == secondaryIntents.size()); + Preconditions.checkArgument(secondaryOnClickListeners.size() == secondaryIntents.size()); mText = text; - mIcons = icons; - mLabels = labels; - mIntents = intents; - mOnClickListeners = onClickListeners; + mPrimaryIcon = primaryIcon; + mPrimaryLabel = primaryLabel; + mPrimaryIntent = primaryIntent; + mPrimaryOnClickListener = primaryOnClickListener; + mSecondaryIcons = secondaryIcons; + mSecondaryLabels = secondaryLabels; + mSecondaryIntents = secondaryIntents; + mSecondaryOnClickListeners = secondaryOnClickListeners; mEntityConfidence = new EntityConfidence<>(entityConfidence); - mEntities = mEntityConfidence.getEntities(); - mLogType = logType; - mVersionInfo = versionInfo; + mSignature = signature; } /** @@ -135,7 +152,7 @@ public final class TextClassification { */ @IntRange(from = 0) public int getEntityCount() { - return mEntities.size(); + return mEntityConfidence.getEntities().size(); } /** @@ -147,7 +164,7 @@ public final class TextClassification { */ @NonNull public @EntityType String getEntity(int index) { - return mEntities.get(index); + return mEntityConfidence.getEntities().get(index); } /** @@ -161,130 +178,160 @@ public final class TextClassification { } /** - * Returns the number of actions that are available to act on the classified text. - * @see #getIntent(int) - * @see #getLabel(int) - * @see #getIcon(int) - * @see #getOnClickListener(int) + * Returns the number of secondary actions that are available to act on the classified + * text. + * + *

Note: that there may or may not be a primary action. + * + * @see #getSecondaryIntent(int) + * @see #getSecondaryLabel(int) + * @see #getSecondaryIcon(int) + * @see #getSecondaryOnClickListener(int) */ @IntRange(from = 0) - public int getActionCount() { - return mIntents.size(); + public int getSecondaryActionsCount() { + return mSecondaryIntents.size(); } /** - * Returns one of the icons that maybe rendered on a widget used to act on the classified text. + * Returns one of the secondary icons that maybe rendered on a widget used to act on the + * classified text. + * * @param index Index of the action to get the icon for. + * * @throws IndexOutOfBoundsException if the specified index is out of range. - * @see #getActionCount() for the number of entities available. - * @see #getIntent(int) - * @see #getLabel(int) - * @see #getOnClickListener(int) + * + * @see #getSecondaryActionsCount() for the number of actions available. + * @see #getSecondaryIntent(int) + * @see #getSecondaryLabel(int) + * @see #getSecondaryOnClickListener(int) + * @see #getIcon() */ @Nullable - public Drawable getIcon(int index) { - return mIcons.get(index); + public Drawable getSecondaryIcon(int index) { + return mSecondaryIcons.get(index); } /** - * Returns an icon for the default intent that may be rendered on a widget used to act on the - * classified text. + * Returns an icon for the primary intent that may be rendered on a widget used to act + * on the classified text. + * + * @see #getSecondaryIcon(int) */ @Nullable public Drawable getIcon() { - return mIcons.isEmpty() ? null : mIcons.get(0); + return mPrimaryIcon; } /** - * Returns one of the labels that may be rendered on a widget used to act on the classified - * text. + * Returns one of the secondary labels that may be rendered on a widget used to act on + * the classified text. + * * @param index Index of the action to get the label for. + * * @throws IndexOutOfBoundsException if the specified index is out of range. - * @see #getActionCount() - * @see #getIntent(int) - * @see #getIcon(int) - * @see #getOnClickListener(int) + * + * @see #getSecondaryActionsCount() + * @see #getSecondaryIntent(int) + * @see #getSecondaryIcon(int) + * @see #getSecondaryOnClickListener(int) + * @see #getLabel() */ @Nullable - public CharSequence getLabel(int index) { - return mLabels.get(index); + public CharSequence getSecondaryLabel(int index) { + return mSecondaryLabels.get(index); } /** - * Returns a label for the default intent that may be rendered on a widget used to act on the - * classified text. + * Returns a label for the primary intent that may be rendered on a widget used to act + * on the classified text. + * + * @see #getSecondaryLabel(int) */ @Nullable public CharSequence getLabel() { - return mLabels.isEmpty() ? null : mLabels.get(0); + return mPrimaryLabel; } /** - * Returns one of the intents that may be fired to act on the classified text. + * Returns one of the secondary intents that may be fired to act on the classified text. + * * @param index Index of the action to get the intent for. + * * @throws IndexOutOfBoundsException if the specified index is out of range. - * @see #getActionCount() - * @see #getLabel(int) - * @see #getIcon(int) - * @see #getOnClickListener(int) + * + * @see #getSecondaryActionsCount() + * @see #getSecondaryLabel(int) + * @see #getSecondaryIcon(int) + * @see #getSecondaryOnClickListener(int) + * @see #getIntent() */ @Nullable - public Intent getIntent(int index) { - return mIntents.get(index); + public Intent getSecondaryIntent(int index) { + return mSecondaryIntents.get(index); } /** - * Returns the default intent that may be fired to act on the classified text. + * Returns the primary intent that may be fired to act on the classified text. + * + * @see #getSecondaryIntent(int) */ @Nullable public Intent getIntent() { - return mIntents.isEmpty() ? null : mIntents.get(0); + return mPrimaryIntent; } /** - * Returns one of the OnClickListeners that may be triggered to act on the classified text. + * Returns one of the secondary OnClickListeners that may be triggered to act on the + * classified text. + * * @param index Index of the action to get the click listener for. + * * @throws IndexOutOfBoundsException if the specified index is out of range. - * @see #getActionCount() - * @see #getIntent(int) - * @see #getLabel(int) - * @see #getIcon(int) + * + * @see #getSecondaryActionsCount() + * @see #getSecondaryIntent(int) + * @see #getSecondaryLabel(int) + * @see #getSecondaryIcon(int) + * @see #getOnClickListener() */ @Nullable - public OnClickListener getOnClickListener(int index) { - return mOnClickListeners.get(index); + public OnClickListener getSecondaryOnClickListener(int index) { + return mSecondaryOnClickListeners.get(index); } /** - * Returns the default OnClickListener that may be triggered to act on the classified text. + * Returns the primary OnClickListener that may be triggered to act on the classified + * text. + * + * @see #getSecondaryOnClickListener(int) */ @Nullable public OnClickListener getOnClickListener() { - return mOnClickListeners.isEmpty() ? null : mOnClickListeners.get(0); + return mPrimaryOnClickListener; } /** - * Returns the MetricsLogger subtype for the action that is performed for this result. - * @hide - */ - public int getLogType() { - return mLogType; - } - - /** - * Returns information about the classifier model used to generate this TextClassification. - * @hide + * Returns the signature for this object. + * The TextClassifier that generates this object may use it as a way to internally identify + * this object. */ @NonNull - public String getVersionInfo() { - return mVersionInfo; + public String getSignature() { + return mSignature; } @Override public String toString() { - return String.format(Locale.US, - "TextClassification {text=%s, entities=%s, labels=%s, intents=%s}", - mText, mEntityConfidence, mLabels, mIntents); + return String.format(Locale.US, "TextClassification {" + + "text=%s, entities=%s, " + + "primaryLabel=%s, secondaryLabels=%s, " + + "primaryIntent=%s, secondaryIntents=%s, " + + "signature=%s}", + mText, mEntityConfidence, + mPrimaryLabel, mSecondaryLabels, + mPrimaryIntent, mSecondaryIntents, + mSignature); } /** @@ -303,18 +350,33 @@ public final class TextClassification { /** * Builder for building {@link TextClassification} objects. + * + *

e.g. + * + *

{@code
+     *   TextClassification classification = new TextClassification.Builder()
+     *          .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)
+     *          .build();
+     * }
*/ public static final class Builder { @NonNull private String mText; - @NonNull private final List mIcons = new ArrayList<>(); - @NonNull private final List mLabels = new ArrayList<>(); - @NonNull private final List mIntents = new ArrayList<>(); - @NonNull private final List mOnClickListeners = new ArrayList<>(); - @NonNull private final EntityConfidence mEntityConfidence = - new EntityConfidence<>(); - private int mLogType; - @NonNull private String mVersionInfo = ""; + @NonNull private final List mSecondaryIcons = new ArrayList<>(); + @NonNull private final List mSecondaryLabels = new ArrayList<>(); + @NonNull private final List mSecondaryIntents = new ArrayList<>(); + @NonNull private final List mSecondaryOnClickListeners = new ArrayList<>(); + @NonNull private final Map mEntityConfidence = new ArrayMap<>(); + @Nullable Drawable mPrimaryIcon; + @Nullable String mPrimaryLabel; + @Nullable Intent mPrimaryIntent; + @Nullable OnClickListener mPrimaryOnClickListener; + @NonNull private String mSignature = ""; /** * Sets the classified text. @@ -326,6 +388,8 @@ public final class TextClassification { /** * Sets an entity type for the classification result and assigns a confidence score. + * If a confidence score had already been set for the specified entity type, this will + * override that score. * * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence). * 0 implies the entity does not exist for the classified text. @@ -334,110 +398,128 @@ public final class TextClassification { public Builder setEntityType( @NonNull @EntityType String type, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) { - mEntityConfidence.setEntityType(type, confidenceScore); + mEntityConfidence.put(type, confidenceScore); return this; } /** - * Adds an action that may be performed on the classified text. The label and icon are used - * for rendering of widgets that offer the intent. Actions should be added in order of - * priority and the first one will be treated as the default. + * Adds an secondary action that may be performed on the classified text. + * Secondary actions are in addition to the primary action which may or may not + * exist. + * + *

The label and icon are used for rendering of widgets that offer the intent. + * Actions should be added in order of priority. + * + *

Note: If all input parameters are set to null, this method will be a + * no-op. + * + * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) */ - public Builder addAction( - Intent intent, @Nullable String label, @Nullable Drawable icon, + public Builder addSecondaryAction( + @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon, @Nullable OnClickListener onClickListener) { - mIntents.add(intent); - mLabels.add(label); - mIcons.add(icon); - mOnClickListeners.add(onClickListener); + if (intent != null || label != null || icon != null || onClickListener != null) { + mSecondaryIntents.add(intent); + mSecondaryLabels.add(label); + mSecondaryIcons.add(icon); + mSecondaryOnClickListeners.add(onClickListener); + } return this; } /** - * Removes all actions. + * Removes all the secondary actions. */ - public Builder clearActions() { - mIntents.clear(); - mOnClickListeners.clear(); - mLabels.clear(); - mIcons.clear(); + public Builder clearSecondaryActions() { + mSecondaryIntents.clear(); + mSecondaryOnClickListeners.clear(); + mSecondaryLabels.clear(); + mSecondaryIcons.clear(); return this; } /** - * Sets the icon for the default action that may be rendered on a widget used to act on the - * classified text. + * Sets the primary action that may be performed on the classified text. This is + * equivalent to calling {@code + * setIntent(intent).setLabel(label).setIcon(icon).setOnClickListener(onClickListener)}. + * + *

Note: If all input parameters are null, there will be no + * primary action but there may still be secondary actions. + * + * @see #addSecondaryAction(Intent, String, Drawable, OnClickListener) */ - public Builder setIcon(@Nullable Drawable icon) { - ensureDefaultActionAvailable(); - mIcons.set(0, icon); - return this; + public Builder setPrimaryAction( + @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon, + @Nullable OnClickListener onClickListener) { + return setIntent(intent).setLabel(label).setIcon(icon) + .setOnClickListener(onClickListener); } /** - * Sets the label for the default action that may be rendered on a widget used to act on the - * classified text. + * Sets the icon for the primary action that may be rendered on a widget used to act + * on the classified text. + * + * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) */ - public Builder setLabel(@Nullable String label) { - ensureDefaultActionAvailable(); - mLabels.set(0, label); + public Builder setIcon(@Nullable Drawable icon) { + mPrimaryIcon = icon; return this; } /** - * Sets the intent for the default action that may be fired to act on the classified text. + * Sets the label for the primary action that may be rendered on a widget used to + * act on the classified text. + * + * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) */ - public Builder setIntent(@Nullable Intent intent) { - ensureDefaultActionAvailable(); - mIntents.set(0, intent); + public Builder setLabel(@Nullable String label) { + mPrimaryLabel = label; return this; } /** - * Sets the MetricsLogger subtype for the action that is performed for this result. - * @hide + * Sets the intent for the primary action that may be fired to act on the classified + * text. + * + * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) */ - public Builder setLogType(int type) { - mLogType = type; + public Builder setIntent(@Nullable Intent intent) { + mPrimaryIntent = intent; return this; } /** - * Sets the OnClickListener for the default action that may be triggered to act on the - * classified text. + * Sets the OnClickListener for the primary action that may be triggered to act on + * the classified text. + * + * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener) */ public Builder setOnClickListener(@Nullable OnClickListener onClickListener) { - ensureDefaultActionAvailable(); - mOnClickListeners.set(0, onClickListener); + mPrimaryOnClickListener = onClickListener; return this; } /** - * Sets information about the classifier model used to generate this TextClassification. - * @hide + * Sets a signature for the TextClassification object. + * The TextClassifier that generates the TextClassification object may use it as a way to + * internally identify the TextClassification object. */ - Builder setVersionInfo(@NonNull String versionInfo) { - mVersionInfo = Preconditions.checkNotNull(versionInfo); + public Builder setSignature(@NonNull String signature) { + mSignature = Preconditions.checkNotNull(signature); return this; } - /** - * Ensures that we have storage for the default action. - */ - private void ensureDefaultActionAvailable() { - if (mIntents.isEmpty()) mIntents.add(null); - if (mLabels.isEmpty()) mLabels.add(null); - if (mIcons.isEmpty()) mIcons.add(null); - if (mOnClickListeners.isEmpty()) mOnClickListeners.add(null); - } - /** * Builds and returns a {@link TextClassification} object. */ public TextClassification build() { return new TextClassification( - mText, mIcons, mLabels, mIntents, mOnClickListeners, mEntityConfidence, - mLogType, mVersionInfo); + mText, + mPrimaryIcon, mPrimaryLabel, + mPrimaryIntent, mPrimaryOnClickListener, + mSecondaryIcons, mSecondaryLabels, + mSecondaryIntents, mSecondaryOnClickListeners, + mEntityConfidence, mSignature); } } diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java index 5aaa5ad1..ed604303 100644 --- a/android/view/textclassifier/TextClassifier.java +++ b/android/view/textclassifier/TextClassifier.java @@ -16,17 +16,23 @@ package android.view.textclassifier; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.WorkerThread; import android.os.LocaleList; +import android.util.ArraySet; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; /** * Interface for providing text classification related features. @@ -37,7 +43,7 @@ import java.lang.annotation.RetentionPolicy; public interface TextClassifier { /** @hide */ - String DEFAULT_LOG_TAG = "TextClassifierImpl"; + String DEFAULT_LOG_TAG = "androidtc"; String TYPE_UNKNOWN = ""; String TYPE_OTHER = "other"; @@ -48,11 +54,30 @@ public interface TextClassifier { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @StringDef({ - TYPE_UNKNOWN, TYPE_OTHER, TYPE_EMAIL, TYPE_PHONE, TYPE_ADDRESS, TYPE_URL + @StringDef(prefix = { "TYPE_" }, value = { + TYPE_UNKNOWN, + TYPE_OTHER, + TYPE_EMAIL, + TYPE_PHONE, + TYPE_ADDRESS, + TYPE_URL, }) @interface EntityType {} + /** Designates that the TextClassifier should identify all entity types it can. **/ + int ENTITY_PRESET_ALL = 0; + /** Designates that the TextClassifier should identify no entities. **/ + int ENTITY_PRESET_NONE = 1; + /** Designates that the TextClassifier should identify a base set of entities determined by the + * TextClassifier. **/ + int ENTITY_PRESET_BASE = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "ENTITY_CONFIG_" }, + value = {ENTITY_PRESET_ALL, ENTITY_PRESET_NONE, ENTITY_PRESET_BASE}) + @interface EntityPreset {} + /** * No-op TextClassifier. * This may be used to turn off TextClassifier features. @@ -212,6 +237,8 @@ public interface TextClassifier { * Returns a {@link TextLinks} that may be applied to the text to annotate it with links * information. * + * If no options are supplied, default values will be used, determined by the TextClassifier. + * * @param text the text to generate annotations for * @param options configuration for link generation * @@ -245,6 +272,16 @@ public interface TextClassifier { return generateLinks(text, null); } + /** + * Returns a {@link Collection} of the entity types in the specified preset. + * + * @see #ENTITIES_ALL + * @see #ENTITIES_NONE + */ + default Collection getEntitiesForPreset(@EntityPreset int entityPreset) { + return Collections.EMPTY_LIST; + } + /** * Logs a TextClassifier event. * @@ -263,6 +300,62 @@ public interface TextClassifier { return TextClassifierConstants.DEFAULT; } + /** + * Configuration object for specifying what entities to identify. + * + * Configs are initially based on a predefined preset, and can be modified from there. + */ + final class EntityConfig { + private final @TextClassifier.EntityPreset int mEntityPreset; + private final Collection mExcludedEntityTypes; + private final Collection mIncludedEntityTypes; + + public EntityConfig(@TextClassifier.EntityPreset int mEntityPreset) { + this.mEntityPreset = mEntityPreset; + mExcludedEntityTypes = new ArraySet<>(); + mIncludedEntityTypes = new ArraySet<>(); + } + + /** + * Specifies an entity to include in addition to any specified by the enity preset. + * + * Note that if an entity has been excluded, the exclusion will take precedence. + */ + public EntityConfig includeEntities(String... entities) { + for (String entity : entities) { + mIncludedEntityTypes.add(entity); + } + return this; + } + + /** + * Specifies an entity to be excluded. + */ + public EntityConfig excludeEntities(String... entities) { + for (String entity : entities) { + mExcludedEntityTypes.add(entity); + } + return this; + } + + /** + * Returns an unmodifiable list of the final set of entities to find. + */ + public List getEntities(TextClassifier textClassifier) { + ArrayList entities = new ArrayList<>(); + for (String entity : textClassifier.getEntitiesForPreset(mEntityPreset)) { + if (!mExcludedEntityTypes.contains(entity)) { + entities.add(entity); + } + } + for (String entity : mIncludedEntityTypes) { + if (!mExcludedEntityTypes.contains(entity) && !entities.contains(entity)) { + entities.add(entity); + } + } + return Collections.unmodifiableList(entities); + } + } /** * Utility functions for TextClassifier methods. diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java index df5e35f0..aea3cb06 100644 --- a/android/view/textclassifier/TextClassifierImpl.java +++ b/android/view/textclassifier/TextClassifierImpl.java @@ -32,7 +32,7 @@ import android.provider.ContactsContract; import android.provider.Settings; import android.text.util.Linkify; import android.util.Patterns; -import android.widget.TextViewMetrics; +import android.view.View.OnClickListener; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; @@ -42,6 +42,9 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -66,6 +69,18 @@ final class TextClassifierImpl implements TextClassifier { private static final String MODEL_FILE_REGEX = "textclassifier\\.smartselection\\.(.*)\\.model"; private static final String UPDATED_MODEL_FILE_PATH = "/data/misc/textclassifier/textclassifier.smartselection.model"; + private static final List ENTITY_TYPES_ALL = + Collections.unmodifiableList(Arrays.asList( + TextClassifier.TYPE_ADDRESS, + TextClassifier.TYPE_EMAIL, + TextClassifier.TYPE_PHONE, + TextClassifier.TYPE_URL)); + private static final List ENTITY_TYPES_BASE = + Collections.unmodifiableList(Arrays.asList( + TextClassifier.TYPE_ADDRESS, + TextClassifier.TYPE_EMAIL, + TextClassifier.TYPE_PHONE, + TextClassifier.TYPE_URL)); private final Context mContext; @@ -121,8 +136,8 @@ final class TextClassifierImpl implements TextClassifier { tsBuilder.setEntityType(results[i].mCollection, results[i].mScore); } return tsBuilder - .setLogSource(LOG_TAG) - .setVersionInfo(getVersionInfo()) + .setSignature( + getSignature(string, selectionStartIndex, selectionEndIndex)) .build(); } else { // We can not trust the result. Log the issue and ignore the result. @@ -154,8 +169,7 @@ final class TextClassifierImpl implements TextClassifier { getHintFlags(string, startIndex, endIndex)); if (results.length > 0) { final TextClassification classificationResult = - createClassificationResult( - results, string.subSequence(startIndex, endIndex)); + createClassificationResult(results, string, startIndex, endIndex); return classificationResult; } } @@ -169,17 +183,23 @@ final class TextClassifierImpl implements TextClassifier { @Override public TextLinks generateLinks( - @NonNull CharSequence text, @NonNull TextLinks.Options options) { + @NonNull CharSequence text, @Nullable TextLinks.Options options) { Utils.validateInput(text); final String textString = text.toString(); final TextLinks.Builder builder = new TextLinks.Builder(textString); try { - LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null; + final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null; + final Collection entitiesToIdentify = + options != null && options.getEntityConfig() != null + ? options.getEntityConfig().getEntities(this) : ENTITY_TYPES_ALL; final SmartSelection smartSelection = getSmartSelection(defaultLocales); final SmartSelection.AnnotatedSpan[] annotations = smartSelection.annotate(textString); for (SmartSelection.AnnotatedSpan span : annotations) { - final Map entityScores = new HashMap<>(); final SmartSelection.ClassificationResult[] results = span.getClassification(); + if (results.length == 0 || !entitiesToIdentify.contains(results[0].mCollection)) { + continue; + } + final Map entityScores = new HashMap<>(); for (int i = 0; i < results.length; i++) { entityScores.put(results[i].mCollection, results[i].mScore); } @@ -193,6 +213,20 @@ final class TextClassifierImpl implements TextClassifier { return builder.build(); } + @Override + public Collection getEntitiesForPreset(@TextClassifier.EntityPreset int entityPreset) { + switch (entityPreset) { + case TextClassifier.ENTITY_PRESET_NONE: + return Collections.emptyList(); + case TextClassifier.ENTITY_PRESET_BASE: + return ENTITY_TYPES_BASE; + case TextClassifier.ENTITY_PRESET_ALL: + // fall through + default: + return ENTITY_TYPES_ALL; + } + } + @Override public void logEvent(String source, String event) { if (LOG_TAG.equals(source)) { @@ -229,13 +263,13 @@ final class TextClassifierImpl implements TextClassifier { } } - @NonNull - private String getVersionInfo() { + private String getSignature(String text, int start, int end) { synchronized (mSmartSelectionLock) { - if (mLocale != null) { - return String.format("%s_v%d", mLocale.toLanguageTag(), mVersion); - } - return ""; + final String versionInfo = (mLocale != null) + ? String.format(Locale.US, "%s_v%d", mLocale.toLanguageTag(), mVersion) + : ""; + final int hash = Objects.hash(text, start, end, mContext.getPackageName()); + return String.format(Locale.US, "%s|%s|%d", LOG_TAG, versionInfo, hash); } } @@ -371,9 +405,11 @@ final class TextClassifierImpl implements TextClassifier { } private TextClassification createClassificationResult( - SmartSelection.ClassificationResult[] classifications, CharSequence text) { + SmartSelection.ClassificationResult[] classifications, + String text, int start, int end) { + final String classifiedText = text.substring(start, end); final TextClassification.Builder builder = new TextClassification.Builder() - .setText(text.toString()); + .setText(classifiedText); final int size = classifications.length; for (int i = 0; i < size; i++) { @@ -381,50 +417,54 @@ final class TextClassifierImpl implements TextClassifier { } final String type = getHighestScoringType(classifications); - builder.setLogType(IntentFactory.getLogType(type)); - - final List intents = IntentFactory.create(mContext, type, text.toString()); - for (Intent intent : intents) { - extendClassificationWithIntent(intent, builder); - } + addActions(builder, IntentFactory.create(mContext, type, classifiedText)); - return builder.setVersionInfo(getVersionInfo()).build(); + return builder.setSignature(getSignature(text, start, end)).build(); } - /** Extends the classification with the intent if it can be resolved. */ - private void extendClassificationWithIntent(Intent intent, TextClassification.Builder builder) { - final PackageManager pm; - final ResolveInfo resolveInfo; - if (intent != null) { - pm = mContext.getPackageManager(); - resolveInfo = pm.resolveActivity(intent, 0); - } else { - pm = null; - resolveInfo = null; - } - if (resolveInfo != null && resolveInfo.activityInfo != null) { - final String packageName = resolveInfo.activityInfo.packageName; - CharSequence label; - Drawable icon; - if ("android".equals(packageName)) { - // Requires the chooser to find an activity to handle the intent. - label = IntentFactory.getLabel(mContext, intent); - icon = null; + /** Extends the classification with the intents that can be resolved. */ + private void addActions( + TextClassification.Builder builder, List intents) { + final PackageManager pm = mContext.getPackageManager(); + final int size = intents.size(); + for (int i = 0; i < size; i++) { + final Intent intent = intents.get(i); + final ResolveInfo resolveInfo; + if (intent != null) { + resolveInfo = pm.resolveActivity(intent, 0); } else { - // A default activity will handle the intent. - intent.setComponent(new ComponentName(packageName, resolveInfo.activityInfo.name)); - icon = resolveInfo.activityInfo.loadIcon(pm); - if (icon == null) { - icon = resolveInfo.loadIcon(pm); + resolveInfo = null; + } + if (resolveInfo != null && resolveInfo.activityInfo != null) { + final String packageName = resolveInfo.activityInfo.packageName; + CharSequence label; + Drawable icon; + if ("android".equals(packageName)) { + // Requires the chooser to find an activity to handle the intent. + label = IntentFactory.getLabel(mContext, intent); + icon = null; + } else { + // A default activity will handle the intent. + intent.setComponent( + new ComponentName(packageName, resolveInfo.activityInfo.name)); + icon = resolveInfo.activityInfo.loadIcon(pm); + if (icon == null) { + icon = resolveInfo.loadIcon(pm); + } + label = resolveInfo.activityInfo.loadLabel(pm); + if (label == null) { + label = resolveInfo.loadLabel(pm); + } } - label = resolveInfo.activityInfo.loadLabel(pm); - if (label == null) { - label = resolveInfo.loadLabel(pm); + final String labelString = (label != null) ? label.toString() : null; + final OnClickListener onClickListener = + TextClassification.createStartActivityOnClickListener(mContext, intent); + if (i == 0) { + builder.setPrimaryAction(intent, labelString, icon, onClickListener); + } else { + builder.addSecondaryAction(intent, labelString, icon, onClickListener); } } - builder.addAction( - intent, label != null ? label.toString() : null, icon, - TextClassification.createStartActivityOnClickListener(mContext, intent)); } } @@ -557,22 +597,5 @@ final class TextClassifierImpl implements TextClassifier { return null; } } - - @Nullable - public static int getLogType(String type) { - type = type.trim().toLowerCase(Locale.ENGLISH); - switch (type) { - case TextClassifier.TYPE_EMAIL: - return TextViewMetrics.SUBTYPE_ASSIST_MENU_ITEM_EMAIL; - case TextClassifier.TYPE_PHONE: - return TextViewMetrics.SUBTYPE_ASSIST_MENU_ITEM_PHONE; - case TextClassifier.TYPE_ADDRESS: - return TextViewMetrics.SUBTYPE_ASSIST_MENU_ITEM_ADDRESS; - case TextClassifier.TYPE_URL: - return TextViewMetrics.SUBTYPE_ASSIST_MENU_ITEM_URL; - default: - return TextViewMetrics.SUBTYPE_ASSIST_MENU_ITEM_OTHER; - } - } } } diff --git a/android/view/textclassifier/TextLinks.java b/android/view/textclassifier/TextLinks.java index 76748d2b..6c587cf9 100644 --- a/android/view/textclassifier/TextLinks.java +++ b/android/view/textclassifier/TextLinks.java @@ -22,6 +22,8 @@ import android.annotation.Nullable; import android.os.LocaleList; import android.text.SpannableString; import android.text.style.ClickableSpan; +import android.view.View; +import android.widget.TextView; import com.android.internal.util.Preconditions; @@ -103,11 +105,7 @@ public final class TextLinks { mOriginalText = originalText; mStart = start; mEnd = end; - mEntityScores = new EntityConfidence<>(); - - for (Map.Entry entry : entityScores.entrySet()) { - mEntityScores.setEntityType(entry.getKey(), entry.getValue()); - } + mEntityScores = new EntityConfidence<>(entityScores); } /** @@ -163,17 +161,29 @@ public final class TextLinks { public static final class Options { private LocaleList mDefaultLocales; + private TextClassifier.EntityConfig mEntityConfig; /** - * @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 - * locale list. + * @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 locale list. */ public Options setDefaultLocales(@Nullable LocaleList defaultLocales) { mDefaultLocales = defaultLocales; return this; } + /** + * Sets the entity configuration to use. This determines what types of entities the + * TextClassifier will look for. + * + * @param entityConfig EntityConfig to use + */ + public Options setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) { + mEntityConfig = entityConfig; + return this; + } + /** * @return ordered list of locale preferences that can be used to disambiguate * the provided text. @@ -182,6 +192,15 @@ public final class TextLinks { public LocaleList getDefaultLocales() { return mDefaultLocales; } + + /** + * @return The config representing the set of entities to look for. + * @see #setEntityConfig(TextClassifier.EntityConfig) + */ + @Nullable + public TextClassifier.EntityConfig getEntityConfig() { + return mEntityConfig; + } } /** @@ -193,9 +212,14 @@ public final class TextLinks { * @hide */ public static final Function DEFAULT_SPAN_FACTORY = - textLink -> { - // TODO: Implement. - throw new UnsupportedOperationException("Not yet implemented"); + textLink -> new ClickableSpan() { + @Override + public void onClick(View widget) { + if (widget instanceof TextView) { + final TextView textView = (TextView) widget; + textView.requestActionMode(textLink); + } + } }; /** diff --git a/android/view/textclassifier/TextSelection.java b/android/view/textclassifier/TextSelection.java index 480b27a7..25e9e7ec 100644 --- a/android/view/textclassifier/TextSelection.java +++ b/android/view/textclassifier/TextSelection.java @@ -21,12 +21,13 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.LocaleList; +import android.util.ArrayMap; import android.view.textclassifier.TextClassifier.EntityType; import com.android.internal.util.Preconditions; -import java.util.List; import java.util.Locale; +import java.util.Map; /** * Information about where text selection should be. @@ -36,19 +37,15 @@ public final class TextSelection { private final int mStartIndex; private final int mEndIndex; @NonNull private final EntityConfidence mEntityConfidence; - @NonNull private final List mEntities; - @NonNull private final String mLogSource; - @NonNull private final String mVersionInfo; + @NonNull private final String mSignature; private TextSelection( - int startIndex, int endIndex, @NonNull EntityConfidence entityConfidence, - @NonNull String logSource, @NonNull String versionInfo) { + int startIndex, int endIndex, @NonNull Map entityConfidence, + @NonNull String signature) { mStartIndex = startIndex; mEndIndex = endIndex; mEntityConfidence = new EntityConfidence<>(entityConfidence); - mEntities = mEntityConfidence.getEntities(); - mLogSource = logSource; - mVersionInfo = versionInfo; + mSignature = signature; } /** @@ -70,7 +67,7 @@ public final class TextSelection { */ @IntRange(from = 0) public int getEntityCount() { - return mEntities.size(); + return mEntityConfidence.getEntities().size(); } /** @@ -82,7 +79,7 @@ public final class TextSelection { */ @NonNull public @EntityType String getEntity(int index) { - return mEntities.get(index); + return mEntityConfidence.getEntities().get(index); } /** @@ -96,27 +93,21 @@ public final class TextSelection { } /** - * Returns a tag for the source classifier used to generate this result. - * @hide + * Returns the signature for this object. + * The TextClassifier that generates this object may use it as a way to internally identify + * this object. */ @NonNull - public String getSourceClassifier() { - return mLogSource; - } - - /** - * Returns information about the classifier model used to generate this TextSelection. - * @hide - */ - @NonNull - public String getVersionInfo() { - return mVersionInfo; + public String getSignature() { + return mSignature; } @Override public String toString() { - return String.format(Locale.US, - "TextSelection {%d, %d, %s}", mStartIndex, mEndIndex, mEntityConfidence); + return String.format( + Locale.US, + "TextSelection {startIndex=%d, endIndex=%d, entities=%s, signature=%s}", + mStartIndex, mEndIndex, mEntityConfidence, mSignature); } /** @@ -126,10 +117,8 @@ public final class TextSelection { private final int mStartIndex; private final int mEndIndex; - @NonNull private final EntityConfidence mEntityConfidence = - new EntityConfidence<>(); - @NonNull private String mLogSource = ""; - @NonNull private String mVersionInfo = ""; + @NonNull private final Map mEntityConfidence = new ArrayMap<>(); + @NonNull private String mSignature = ""; /** * Creates a builder used to build {@link TextSelection} objects. @@ -154,25 +143,18 @@ public final class TextSelection { public Builder setEntityType( @NonNull @EntityType String type, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) { - mEntityConfidence.setEntityType(type, confidenceScore); + mEntityConfidence.put(type, confidenceScore); return this; } /** - * Sets a tag for the source classifier used to generate this result. - * @hide - */ - Builder setLogSource(@NonNull String logSource) { - mLogSource = Preconditions.checkNotNull(logSource); - return this; - } - - /** - * Sets information about the classifier model used to generate this TextSelection. - * @hide + * Sets a signature for the TextSelection object. + * + * The TextClassifier that generates the TextSelection object may use it as a way to + * internally identify the TextSelection object. */ - Builder setVersionInfo(@NonNull String versionInfo) { - mVersionInfo = Preconditions.checkNotNull(versionInfo); + public Builder setSignature(@NonNull String signature) { + mSignature = Preconditions.checkNotNull(signature); return this; } @@ -181,7 +163,7 @@ public final class TextSelection { */ public TextSelection build() { return new TextSelection( - mStartIndex, mEndIndex, mEntityConfidence, mLogSource, mVersionInfo); + mStartIndex, mEndIndex, mEntityConfidence, mSignature); } } diff --git a/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/android/view/textclassifier/logging/SmartSelectionEventTracker.java index 2833564f..157b3d82 100644 --- a/android/view/textclassifier/logging/SmartSelectionEventTracker.java +++ b/android/view/textclassifier/logging/SmartSelectionEventTracker.java @@ -473,7 +473,7 @@ public final class SmartSelectionEventTracker { final String entityType = classification.getEntityCount() > 0 ? classification.getEntity(0) : TextClassifier.TYPE_UNKNOWN; - final String versionTag = classification.getVersionInfo(); + final String versionTag = getVersionInfo(classification.getSignature()); return new SelectionEvent( start, end, EventType.SELECTION_MODIFIED, entityType, versionTag); } @@ -489,7 +489,7 @@ public final class SmartSelectionEventTracker { */ public static SelectionEvent selectionModified( int start, int end, @NonNull TextSelection selection) { - final boolean smartSelection = selection.getSourceClassifier() + final boolean smartSelection = getSourceClassifier(selection.getSignature()) .equals(TextClassifier.DEFAULT_LOG_TAG); final int eventType; if (smartSelection) { @@ -503,7 +503,7 @@ public final class SmartSelectionEventTracker { final String entityType = selection.getEntityCount() > 0 ? selection.getEntity(0) : TextClassifier.TYPE_UNKNOWN; - final String versionTag = selection.getVersionInfo(); + final String versionTag = getVersionInfo(selection.getSignature()); return new SelectionEvent(start, end, eventType, entityType, versionTag); } @@ -538,26 +538,25 @@ public final class SmartSelectionEventTracker { final String entityType = classification.getEntityCount() > 0 ? classification.getEntity(0) : TextClassifier.TYPE_UNKNOWN; - final String versionTag = classification.getVersionInfo(); + final String versionTag = getVersionInfo(classification.getSignature()); return new SelectionEvent(start, end, actionType, entityType, versionTag); } - private boolean isActionType() { - switch (mEventType) { - case ActionType.OVERTYPE: // fall through - case ActionType.COPY: // fall through - case ActionType.PASTE: // fall through - case ActionType.CUT: // fall through - case ActionType.SHARE: // fall through - case ActionType.SMART_SHARE: // fall through - case ActionType.DRAG: // fall through - case ActionType.ABANDON: // fall through - case ActionType.SELECT_ALL: // fall through - case ActionType.RESET: // fall through - return true; - default: - return false; + private static String getVersionInfo(String signature) { + final int start = signature.indexOf("|"); + final int end = signature.indexOf("|", start); + if (start >= 0 && end >= start) { + return signature.substring(start, end); + } + return ""; + } + + private static String getSourceClassifier(String signature) { + final int end = signature.indexOf("|"); + if (end >= 0) { + return signature.substring(0, end); } + return ""; } private boolean isTerminal() { diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java index f368c74a..8e1f2183 100644 --- a/android/view/textservice/TextServicesManager.java +++ b/android/view/textservice/TextServicesManager.java @@ -1,213 +1,58 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package android.view.textservice; -import android.annotation.SystemService; -import android.content.Context; import android.os.Bundle; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.ServiceManager.ServiceNotFoundException; -import android.util.Log; import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener; -import com.android.internal.textservice.ITextServicesManager; - import java.util.Locale; /** - * System API to the overall text services, which arbitrates interaction between applications - * and text services. - * - * The user can change the current text services in Settings. And also applications can specify - * the target text services. - * - *

Architecture Overview

- * - *

There are three primary parties involved in the text services - * framework (TSF) architecture:

- * - *
    - *
  • The text services manager as expressed by this class - * is the central point of the system that manages interaction between all - * other parts. It is expressed as the client-side API here which exists - * in each application context and communicates with a global system service - * that manages the interaction across all processes. - *
  • A text service implements a particular - * interaction model allowing the client application to retrieve information of text. - * The system binds to the current text service that is in use, causing it to be created and run. - *
  • Multiple client applications arbitrate with the text service - * manager for connections to text services. - *
- * - *

Text services sessions

- *
    - *
  • The spell checker session is one of the text services. - * {@link android.view.textservice.SpellCheckerSession}
  • - *
- * + * A stub class of TextServicesManager for Layout-Lib. */ -@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE) public final class TextServicesManager { - private static final String TAG = TextServicesManager.class.getSimpleName(); - private static final boolean DBG = false; - - private static TextServicesManager sInstance; - - private final ITextServicesManager mService; - - private TextServicesManager() throws ServiceNotFoundException { - mService = ITextServicesManager.Stub.asInterface( - ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE)); - } + private static final TextServicesManager sInstance = new TextServicesManager(); + private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0]; /** * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist. * @hide */ public static TextServicesManager getInstance() { - synchronized (TextServicesManager.class) { - if (sInstance == null) { - try { - sInstance = new TextServicesManager(); - } catch (ServiceNotFoundException e) { - throw new IllegalStateException(e); - } - } - return sInstance; - } - } - - /** - * Returns the language component of a given locale string. - */ - private static String parseLanguageFromLocaleString(String locale) { - final int idx = locale.indexOf('_'); - if (idx < 0) { - return locale; - } else { - return locale.substring(0, idx); - } + return sInstance; } - /** - * Get a spell checker session for the specified spell checker - * @param locale the locale for the spell checker. If {@code locale} is null and - * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be - * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true, - * the locale specified in Settings will be returned only when it is same as {@code locale}. - * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is - * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be - * selected. - * @param listener a spell checker session lister for getting results from a spell checker. - * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled - * languages in settings will be returned. - * @return the spell checker session of the spell checker - */ public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale, SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) { - if (listener == null) { - throw new NullPointerException(); - } - if (!referToSpellCheckerLanguageSettings && locale == null) { - throw new IllegalArgumentException("Locale should not be null if you don't refer" - + " settings."); - } - - if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) { - return null; - } - - final SpellCheckerInfo sci; - try { - sci = mService.getCurrentSpellChecker(null); - } catch (RemoteException e) { - return null; - } - if (sci == null) { - return null; - } - SpellCheckerSubtype subtypeInUse = null; - if (referToSpellCheckerLanguageSettings) { - subtypeInUse = getCurrentSpellCheckerSubtype(true); - if (subtypeInUse == null) { - return null; - } - if (locale != null) { - final String subtypeLocale = subtypeInUse.getLocale(); - final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale); - if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) { - return null; - } - } - } else { - final String localeStr = locale.toString(); - for (int i = 0; i < sci.getSubtypeCount(); ++i) { - final SpellCheckerSubtype subtype = sci.getSubtypeAt(i); - final String tempSubtypeLocale = subtype.getLocale(); - final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale); - if (tempSubtypeLocale.equals(localeStr)) { - subtypeInUse = subtype; - break; - } else if (tempSubtypeLanguage.length() >= 2 && - locale.getLanguage().equals(tempSubtypeLanguage)) { - subtypeInUse = subtype; - } - } - } - if (subtypeInUse == null) { - return null; - } - final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener); - try { - mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(), - session.getTextServicesSessionListener(), - session.getSpellCheckerSessionListener(), bundle); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - return session; + return null; } /** * @hide */ public SpellCheckerInfo[] getEnabledSpellCheckers() { - try { - final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers(); - if (DBG) { - Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null")); - } - return retval; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return EMPTY_SPELL_CHECKER_INFO; } /** * @hide */ public SpellCheckerInfo getCurrentSpellChecker() { - try { - // Passing null as a locale for ICS - return mService.getCurrentSpellChecker(null); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return null; } /** @@ -215,22 +60,13 @@ public final class TextServicesManager { */ public SpellCheckerSubtype getCurrentSpellCheckerSubtype( boolean allowImplicitlySelectedSubtype) { - try { - // Passing null as a locale until we support multiple enabled spell checker subtypes. - return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return null; } /** * @hide */ public boolean isSpellCheckerEnabled() { - try { - return mService.isSpellCheckerEnabled(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return false; } } -- cgit v1.2.3