diff options
author | Justin Klaassen <justinklaassen@google.com> | 2017-11-17 16:38:15 -0500 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2017-11-17 16:38:15 -0500 |
commit | 6a65f2da209bff03cb0eb6da309710ac6ee5026d (patch) | |
tree | 48e2090e716d4178378cb0599fc5d9cffbcf3f63 /android/view | |
parent | 46c77c203439b3b37c99d09e326df4b1fe08c10b (diff) | |
download | android-28-6a65f2da209bff03cb0eb6da309710ac6ee5026d.tar.gz |
Import Android SDK Platform P [4456821]
/google/data/ro/projects/android/fetch_artifact \
--bid 4456821 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4456821.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: I2d206b200d7952f899a5d1647ab532638cc8dd43
Diffstat (limited to 'android/view')
25 files changed, 1001 insertions, 462 deletions
diff --git a/android/view/Display.java b/android/view/Display.java index e7c3f92d..6a44cdb9 100644 --- a/android/view/Display.java +++ b/android/view/Display.java @@ -294,11 +294,10 @@ public final class Display { /** * Display state: The display is dozing in a suspended low power state; it is still - * on but is optimized for showing static system-provided content while the device - * is non-interactive. This mode may be used to conserve even more power by allowing - * the hardware to stop applying frame buffer updates from the graphics subsystem or - * to take over the display and manage it autonomously to implement low power always-on - * display functionality. + * on but the CPU is not updating it. This may be used in one of two ways: to show + * static system-provided content while the device is non-interactive, or to allow + * a "Sidekick" compute resource to update the display. For this reason, the + * CPU must not control the display in this mode. * * @see #getState * @see android.os.PowerManager#isInteractive @@ -313,6 +312,18 @@ public final class Display { */ public static final int STATE_VR = 5; + /** + * Display state: The display is in a suspended full power state; it is still + * on but the CPU is not updating it. This may be used in one of two ways: to show + * static system-provided content while the device is non-interactive, or to allow + * a "Sidekick" compute resource to update the display. For this reason, the + * CPU must not control the display in this mode. + * + * @see #getState + * @see android.os.PowerManager#isInteractive + */ + public static final int STATE_ON_SUSPEND = 6; + /* The color mode constants defined below must be kept in sync with the ones in * system/core/include/system/graphics-base.h */ @@ -994,7 +1005,7 @@ public final class Display { * Gets the state of the display, such as whether it is on or off. * * @return The state of the display: one of {@link #STATE_OFF}, {@link #STATE_ON}, - * {@link #STATE_DOZE}, {@link #STATE_DOZE_SUSPEND}, or + * {@link #STATE_DOZE}, {@link #STATE_DOZE_SUSPEND}, {@link #STATE_ON_SUSPEND}, or * {@link #STATE_UNKNOWN}. */ public int getState() { @@ -1113,6 +1124,8 @@ public final class Display { return "DOZE_SUSPEND"; case STATE_VR: return "VR"; + case STATE_ON_SUSPEND: + return "ON_SUSPEND"; default: return Integer.toString(state); } @@ -1120,11 +1133,11 @@ public final class Display { /** * Returns true if display updates may be suspended while in the specified - * display power state. + * display power state. In SUSPEND states, updates are absolutely forbidden. * @hide */ public static boolean isSuspendedState(int state) { - return state == STATE_OFF || state == STATE_DOZE_SUSPEND; + return state == STATE_OFF || state == STATE_DOZE_SUSPEND || state == STATE_ON_SUSPEND; } /** diff --git a/android/view/DisplayFrames.java b/android/view/DisplayFrames.java new file mode 100644 index 00000000..e6861d83 --- /dev/null +++ b/android/view/DisplayFrames.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.view; + +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; +import static com.android.server.wm.proto.DisplayFramesProto.STABLE_BOUNDS; + +import android.graphics.Rect; +import android.util.proto.ProtoOutputStream; + +import java.io.PrintWriter; + +/** + * Container class for all the display frames that affect how we do window layout on a display. + * @hide + */ +public class DisplayFrames { + public final int mDisplayId; + + /** + * The current size of the screen; really; extends into the overscan area of the screen and + * doesn't account for any system elements like the status bar. + */ + public final Rect mOverscan = new Rect(); + + /** + * The current visible size of the screen; really; (ir)regardless of whether the status bar can + * be hidden but not extending into the overscan area. + */ + public final Rect mUnrestricted = new Rect(); + + /** Like mOverscan*, but allowed to move into the overscan region where appropriate. */ + public final Rect mRestrictedOverscan = new Rect(); + + /** + * The current size of the screen; these may be different than (0,0)-(dw,dh) if the status bar + * can't be hidden; in that case it effectively carves out that area of the display from all + * other windows. + */ + public final Rect mRestricted = new Rect(); + + /** + * During layout, the current screen borders accounting for any currently visible system UI + * elements. + */ + public final Rect mSystem = new Rect(); + + /** For applications requesting stable content insets, these are them. */ + public final Rect mStable = new Rect(); + + /** + * For applications requesting stable content insets but have also set the fullscreen window + * flag, these are the stable dimensions without the status bar. + */ + public final Rect mStableFullscreen = new Rect(); + + /** + * During layout, the current screen borders with all outer decoration (status bar, input method + * dock) accounted for. + */ + public final Rect mCurrent = new Rect(); + + /** + * During layout, the frame in which content should be displayed to the user, accounting for all + * screen decoration except for any space they deem as available for other content. This is + * usually the same as mCurrent*, but may be larger if the screen decor has supplied content + * insets. + */ + public final Rect mContent = new Rect(); + + /** + * During layout, the frame in which voice content should be displayed to the user, accounting + * for all screen decoration except for any space they deem as available for other content. + */ + public final Rect mVoiceContent = new Rect(); + + /** During layout, the current screen borders along which input method windows are placed. */ + public final Rect mDock = new Rect(); + + private final Rect mDisplayInfoOverscan = new Rect(); + private final Rect mRotatedDisplayInfoOverscan = new Rect(); + public int mDisplayWidth; + public int mDisplayHeight; + + public int mRotation; + + public DisplayFrames(int displayId, DisplayInfo info) { + mDisplayId = displayId; + onDisplayInfoUpdated(info); + } + + public void onDisplayInfoUpdated(DisplayInfo info) { + mDisplayWidth = info.logicalWidth; + mDisplayHeight = info.logicalHeight; + mRotation = info.rotation; + mDisplayInfoOverscan.set( + info.overscanLeft, info.overscanTop, info.overscanRight, info.overscanBottom); + } + + public void onBeginLayout() { + switch (mRotation) { + case ROTATION_90: + mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.top; + mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.right; + mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.bottom; + mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.left; + break; + case ROTATION_180: + mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.right; + mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.bottom; + mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.left; + mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.top; + break; + case ROTATION_270: + mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.bottom; + mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.left; + mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.top; + mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.right; + break; + default: + mRotatedDisplayInfoOverscan.set(mDisplayInfoOverscan); + break; + } + + mRestrictedOverscan.set(0, 0, mDisplayWidth, mDisplayHeight); + mOverscan.set(mRestrictedOverscan); + mSystem.set(mRestrictedOverscan); + mUnrestricted.set(mRotatedDisplayInfoOverscan); + mUnrestricted.right = mDisplayWidth - mUnrestricted.right; + mUnrestricted.bottom = mDisplayHeight - mUnrestricted.bottom; + mRestricted.set(mUnrestricted); + mDock.set(mUnrestricted); + mContent.set(mUnrestricted); + mVoiceContent.set(mUnrestricted); + mStable.set(mUnrestricted); + mStableFullscreen.set(mUnrestricted); + mCurrent.set(mUnrestricted); + + } + + public int getInputMethodWindowVisibleHeight() { + return mDock.bottom - mCurrent.bottom; + } + + public void writeToProto(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + mStable.writeToProto(proto, STABLE_BOUNDS); + proto.end(token); + } + + public void dump(String prefix, PrintWriter pw) { + pw.println(prefix + "DisplayFrames w=" + mDisplayWidth + " h=" + mDisplayHeight + + " r=" + mRotation); + final String myPrefix = prefix + " "; + dumpFrame(mStable, "mStable", myPrefix, pw); + dumpFrame(mStableFullscreen, "mStableFullscreen", myPrefix, pw); + dumpFrame(mDock, "mDock", myPrefix, pw); + dumpFrame(mCurrent, "mCurrent", myPrefix, pw); + dumpFrame(mSystem, "mSystem", myPrefix, pw); + dumpFrame(mContent, "mContent", myPrefix, pw); + dumpFrame(mVoiceContent, "mVoiceContent", myPrefix, pw); + dumpFrame(mOverscan, "mOverscan", myPrefix, pw); + dumpFrame(mRestrictedOverscan, "mRestrictedOverscan", myPrefix, pw); + dumpFrame(mRestricted, "mRestricted", myPrefix, pw); + dumpFrame(mUnrestricted, "mUnrestricted", myPrefix, pw); + dumpFrame(mDisplayInfoOverscan, "mDisplayInfoOverscan", myPrefix, pw); + dumpFrame(mRotatedDisplayInfoOverscan, "mRotatedDisplayInfoOverscan", myPrefix, pw); + } + + private void dumpFrame(Rect frame, String name, String prefix, PrintWriter pw) { + pw.print(prefix + name + "="); frame.printShortString(pw); pw.println(); + } +} diff --git a/android/view/FocusFinder.java b/android/view/FocusFinder.java index 74555de5..713cfb48 100644 --- a/android/view/FocusFinder.java +++ b/android/view/FocusFinder.java @@ -530,7 +530,7 @@ public class FocusFinder { * axis distances. Warning: this fudge factor is finely tuned, be sure to * run all focus tests if you dare tweak it. */ - int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) { + long getWeightedDistanceFor(long majorAxisDistance, long minorAxisDistance) { return 13 * majorAxisDistance * majorAxisDistance + minorAxisDistance * minorAxisDistance; } diff --git a/android/view/IWindowManagerImpl.java b/android/view/IWindowManagerImpl.java index b34dfbfe..6c006cae 100644 --- a/android/view/IWindowManagerImpl.java +++ b/android/view/IWindowManagerImpl.java @@ -16,6 +16,7 @@ package android.view; +import android.app.IAssistDataReceiver; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; @@ -29,7 +30,6 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.DisplayMetrics; -import com.android.internal.app.IAssistScreenshotReceiver; import com.android.internal.os.IResultReceiver; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; @@ -261,7 +261,7 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver) + public boolean requestAssistScreenshot(IAssistDataReceiver receiver) throws RemoteException { // TODO Auto-generated method stub return false; @@ -507,7 +507,7 @@ public class IWindowManagerImpl implements IWindowManager { throws RemoteException {} @Override - public void createInputConsumer(String name, InputChannel inputChannel) + public void createInputConsumer(IBinder token, String name, InputChannel inputChannel) throws RemoteException {} @Override diff --git a/android/view/NotificationHeaderView.java b/android/view/NotificationHeaderView.java index 58045602..ab0b3eec 100644 --- a/android/view/NotificationHeaderView.java +++ b/android/view/NotificationHeaderView.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; import android.app.Notification; import android.content.Context; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Rect; @@ -43,6 +44,7 @@ public class NotificationHeaderView extends ViewGroup { public static final int NO_COLOR = Notification.COLOR_INVALID; private final int mChildMinWidth; private final int mContentEndMargin; + private final int mGravity; private View mAppName; private View mHeaderText; private OnClickListener mExpandClickListener; @@ -50,7 +52,6 @@ public class NotificationHeaderView extends ViewGroup { private ImageView mExpandButton; private CachingIconView mIcon; private View mProfileBadge; - private View mInfo; private int mIconColor; private int mOriginalNotificationColor; private boolean mExpanded; @@ -61,6 +62,7 @@ public class NotificationHeaderView extends ViewGroup { private boolean mEntireHeaderClickable; private boolean mExpandOnlyOnButton; private boolean mAcceptAllTouches; + private int mTotalWidth; ViewOutlineProvider mProvider = new ViewOutlineProvider() { @Override @@ -92,6 +94,11 @@ public class NotificationHeaderView extends ViewGroup { mHeaderBackgroundHeight = res.getDimensionPixelSize( R.dimen.notification_header_background_height); mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand); + + int[] attrIds = { android.R.attr.gravity }; + TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes); + mGravity = ta.getInt(0, 0); + ta.recycle(); } @Override @@ -146,6 +153,7 @@ public class NotificationHeaderView extends ViewGroup { mHeaderText.measure(childWidthSpec, wrapContentHeightSpec); } } + mTotalWidth = Math.min(totalWidth, givenWidth); setMeasuredDimension(givenWidth, givenHeight); } @@ -153,6 +161,10 @@ public class NotificationHeaderView extends ViewGroup { protected void onLayout(boolean changed, int l, int t, int r, int b) { int left = getPaddingStart(); int end = getMeasuredWidth(); + final boolean centerAligned = (mGravity & Gravity.CENTER_HORIZONTAL) != 0; + if (centerAligned) { + left += getMeasuredWidth() / 2 - mTotalWidth / 2; + } int childCount = getChildCount(); int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); for (int i = 0; i < childCount; i++) { diff --git a/android/view/RenderNode.java b/android/view/RenderNode.java index ea6e63c3..50701518 100644 --- a/android/view/RenderNode.java +++ b/android/view/RenderNode.java @@ -353,6 +353,11 @@ public class RenderNode { return nHasShadow(mNativeRenderNode); } + /** setShadowColor */ + public boolean setShadowColor(int color) { + return nSetShadowColor(mNativeRenderNode, color); + } + /** * Enables or disables clipping to the outline. * @@ -910,6 +915,8 @@ public class RenderNode { @CriticalNative private static native boolean nHasShadow(long renderNode); @CriticalNative + private static native boolean nSetShadowColor(long renderNode, int color); + @CriticalNative private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline); @CriticalNative private static native boolean nSetRevealClip(long renderNode, diff --git a/android/view/RenderNodeAnimator.java b/android/view/RenderNodeAnimator.java index 95150409..c4a71601 100644 --- a/android/view/RenderNodeAnimator.java +++ b/android/view/RenderNodeAnimator.java @@ -19,7 +19,6 @@ package android.view; import android.animation.Animator; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; -import android.graphics.Canvas; import android.graphics.CanvasProperty; import android.graphics.Paint; import android.util.SparseIntArray; @@ -281,12 +280,9 @@ public class RenderNodeAnimator extends Animator { setTarget(mViewTarget.mRenderNode); } - public void setTarget(Canvas canvas) { - if (!(canvas instanceof DisplayListCanvas)) { - throw new IllegalArgumentException("Not a GLES20RecordingCanvas"); - } - final DisplayListCanvas recordingCanvas = (DisplayListCanvas) canvas; - setTarget(recordingCanvas.mNode); + /** Sets the animation target to the owning view of the DisplayListCanvas */ + public void setTarget(DisplayListCanvas canvas) { + setTarget(canvas.mNode); } private void setTarget(RenderNode node) { diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java index 6f8315ae..5641009c 100644 --- a/android/view/SurfaceControl.java +++ b/android/view/SurfaceControl.java @@ -295,6 +295,12 @@ public class SurfaceControl { public static final int POWER_MODE_DOZE_SUSPEND = 3; /** + * Display power mode on: used while putting the screen into a suspended + * full power mode. Use only with {@link SurfaceControl#setDisplayPowerMode}. + */ + public static final int POWER_MODE_ON_SUSPEND = 4; + + /** * A value for windowType used to indicate that the window should be omitted from screenshots * and display mirroring. A temporary workaround until we express such things with * the hierarchy. @@ -1206,56 +1212,65 @@ public class SurfaceControl { } public Transaction show(SurfaceControl sc) { + sc.checkNotReleased(); nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN); return this; } public Transaction hide(SurfaceControl sc) { + sc.checkNotReleased(); nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN); return this; } public Transaction setPosition(SurfaceControl sc, float x, float y) { + sc.checkNotReleased(); nativeSetPosition(mNativeObject, sc.mNativeObject, x, y); return this; } public Transaction setSize(SurfaceControl sc, int w, int h) { - nativeSetSize(mNativeObject, sc.mNativeObject, - w, h); + sc.checkNotReleased(); + nativeSetSize(mNativeObject, sc.mNativeObject, w, h); return this; } public Transaction setLayer(SurfaceControl sc, int z) { + sc.checkNotReleased(); nativeSetLayer(mNativeObject, sc.mNativeObject, z); return this; } public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) { + sc.checkNotReleased(); nativeSetRelativeLayer(mNativeObject, sc.mNativeObject, relativeTo.getHandle(), z); return this; } public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) { + sc.checkNotReleased(); nativeSetTransparentRegionHint(mNativeObject, sc.mNativeObject, transparentRegion); return this; } public Transaction setAlpha(SurfaceControl sc, float alpha) { + sc.checkNotReleased(); nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha); return this; } public Transaction setMatrix(SurfaceControl sc, float dsdx, float dtdx, float dtdy, float dsdy) { + sc.checkNotReleased(); nativeSetMatrix(mNativeObject, sc.mNativeObject, dsdx, dtdx, dtdy, dsdy); return this; } public Transaction setWindowCrop(SurfaceControl sc, Rect crop) { + sc.checkNotReleased(); if (crop != null) { nativeSetWindowCrop(mNativeObject, sc.mNativeObject, crop.left, crop.top, crop.right, crop.bottom); @@ -1267,6 +1282,7 @@ public class SurfaceControl { } public Transaction setFinalCrop(SurfaceControl sc, Rect crop) { + sc.checkNotReleased(); if (crop != null) { nativeSetFinalCrop(mNativeObject, sc.mNativeObject, crop.left, crop.top, crop.right, crop.bottom); @@ -1278,40 +1294,48 @@ public class SurfaceControl { } public Transaction setLayerStack(SurfaceControl sc, int layerStack) { + sc.checkNotReleased(); nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack); return this; } - public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, long frameNumber) { + public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, + long frameNumber) { + sc.checkNotReleased(); nativeDeferTransactionUntil(mNativeObject, sc.mNativeObject, handle, frameNumber); return this; } public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface, long frameNumber) { + sc.checkNotReleased(); nativeDeferTransactionUntilSurface(mNativeObject, sc.mNativeObject, barrierSurface.mNativeObject, frameNumber); return this; } public Transaction reparentChildren(SurfaceControl sc, IBinder newParentHandle) { + sc.checkNotReleased(); nativeReparentChildren(mNativeObject, sc.mNativeObject, newParentHandle); return this; } /** Re-parents a specific child layer to a new parent */ public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) { + sc.checkNotReleased(); nativeReparent(mNativeObject, sc.mNativeObject, newParentHandle); return this; } public Transaction detachChildren(SurfaceControl sc) { + sc.checkNotReleased(); nativeSeverChildren(mNativeObject, sc.mNativeObject); return this; } public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) { + sc.checkNotReleased(); nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject, overrideScalingMode); return this; @@ -1322,6 +1346,7 @@ public class SurfaceControl { * @param color A float array with three values to represent r, g, b in range [0..1] */ public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) { + sc.checkNotReleased(); nativeSetColor(mNativeObject, sc.mNativeObject, color); return this; } @@ -1334,6 +1359,7 @@ public class SurfaceControl { * (at which point the geometry influencing aspects of this transaction will then occur) */ public Transaction setGeometryAppliesWithResize(SurfaceControl sc) { + sc.checkNotReleased(); nativeSetGeometryAppliesWithResize(mNativeObject, sc.mNativeObject); return this; } @@ -1343,6 +1369,7 @@ public class SurfaceControl { * Surface with the {@link #SECURE} flag. */ Transaction setSecure(SurfaceControl sc, boolean isSecure) { + sc.checkNotReleased(); if (isSecure) { nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE); } else { @@ -1356,6 +1383,7 @@ public class SurfaceControl { * Surface with the {@link #OPAQUE} flag. */ public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) { + sc.checkNotReleased(); if (isOpaque) { nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE); } else { diff --git a/android/view/ThreadedRenderer.java b/android/view/ThreadedRenderer.java index 2166f6e4..7c76bab2 100644 --- a/android/view/ThreadedRenderer.java +++ b/android/view/ThreadedRenderer.java @@ -70,6 +70,7 @@ public final class ThreadedRenderer { * Name of the file that holds the shaders cache. */ private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache"; + private static final String CACHE_PATH_SKIASHADERS = "com.android.skia.shaders_cache"; /** * System property used to enable or disable threaded rendering profiling. @@ -272,7 +273,9 @@ public final class ThreadedRenderer { * @hide */ public static void setupDiskCache(File cacheDir) { - ThreadedRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath()); + ThreadedRenderer.setupShadersDiskCache( + new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath(), + new File(cacheDir, CACHE_PATH_SKIASHADERS).getAbsolutePath()); } /** @@ -1007,7 +1010,7 @@ public final class ThreadedRenderer { /** Not actually public - internal use only. This doc to make lint happy */ public static native void disableVsync(); - static native void setupShadersDiskCache(String cacheFile); + static native void setupShadersDiskCache(String cacheFile, String skiaCacheFile); private static native void nRotateProcessStatsBuffer(); private static native void nSetProcessStatsBuffer(int fd); diff --git a/android/view/View.java b/android/view/View.java index c043dcac..be09fe86 100644 --- a/android/view/View.java +++ b/android/view/View.java @@ -14218,6 +14218,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public void setScaleX(float scaleX) { if (scaleX != getScaleX()) { + requireIsFinite(scaleX, "scaleX"); invalidateViewProperty(true, false); mRenderNode.setScaleX(scaleX); invalidateViewProperty(false, true); @@ -14254,6 +14255,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public void setScaleY(float scaleY) { if (scaleY != getScaleY()) { + requireIsFinite(scaleY, "scaleY"); invalidateViewProperty(true, false); mRenderNode.setScaleY(scaleY); invalidateViewProperty(false, true); @@ -14803,6 +14805,15 @@ 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"); + } + if (Float.isInfinite(transform)) { + throw new IllegalArgumentException("Cannot set '" + propertyName + "' to infinity"); + } + } + /** * The visual x position of this view, in pixels. This is equivalent to the * {@link #setTranslationX(float) translationX} property plus the current @@ -14889,6 +14900,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public void setElevation(float elevation) { if (elevation != getElevation()) { + requireIsFinite(elevation, "elevation"); invalidateViewProperty(true, false); mRenderNode.setElevation(elevation); invalidateViewProperty(false, true); @@ -14981,6 +14993,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public void setTranslationZ(float translationZ) { if (translationZ != getTranslationZ()) { + requireIsFinite(translationZ, "translationZ"); invalidateViewProperty(true, false); mRenderNode.setTranslationZ(translationZ); invalidateViewProperty(false, true); @@ -15169,6 +15182,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return mRenderNode.hasShadow(); } + /** + * @hide + */ + public void setShadowColor(@ColorInt int color) { + if (mRenderNode.setShadowColor(color)) { + invalidateViewProperty(true, true); + } + } + /** @hide */ public void setRevealClip(boolean shouldClip, float x, float y, float radius) { diff --git a/android/view/ViewConfiguration.java b/android/view/ViewConfiguration.java index 45008627..c44c8dda 100644 --- a/android/view/ViewConfiguration.java +++ b/android/view/ViewConfiguration.java @@ -92,7 +92,7 @@ public class ViewConfiguration { * Defines the duration in milliseconds a user needs to hold down the * appropriate button to enable the accessibility shortcut once it's configured. */ - private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1500; + private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1000; /** * Defines the duration in milliseconds we will wait to see if a touch event diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java index 37829f0b..e30496fb 100644 --- a/android/view/ViewRootImpl.java +++ b/android/view/ViewRootImpl.java @@ -513,7 +513,7 @@ public final class ViewRootImpl implements ViewParent, mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); if (!sCompatibilityDone) { - sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P; + sAlwaysAssignFocus = true; sCompatibilityDone = true; } diff --git a/android/view/WindowManager.java b/android/view/WindowManager.java index c29a1daf..eb5fc92e 100644 --- a/android/view/WindowManager.java +++ b/android/view/WindowManager.java @@ -1422,7 +1422,7 @@ public interface WindowManager extends ViewManager { * this window is visible. * @hide */ - @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) + @RequiresPermission(permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) public static final int PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 0x00080000; /** @@ -1443,6 +1443,15 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN = 0x00200000; /** + * Flag to indicate that this window should be considered a screen decoration similar to the + * nav bar and status bar. This will cause this window to affect the window insets reported + * to other windows when it is visible. + * @hide + */ + @RequiresPermission(permission.STATUS_BAR_SERVICE) + public static final int PRIVATE_FLAG_IS_SCREEN_DECOR = 0x00400000; + + /** * Control flags that are private to the platform. * @hide */ @@ -1526,7 +1535,11 @@ public interface WindowManager extends ViewManager { @ViewDebug.FlagToString( mask = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN, equals = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN, - name = "ACQUIRES_SLEEP_TOKEN") + name = "ACQUIRES_SLEEP_TOKEN"), + @ViewDebug.FlagToString( + mask = PRIVATE_FLAG_IS_SCREEN_DECOR, + equals = PRIVATE_FLAG_IS_SCREEN_DECOR, + name = "IS_SCREEN_DECOR") }) @TestApi public int privateFlags; diff --git a/android/view/WindowManagerGlobal.java b/android/view/WindowManagerGlobal.java index c7e8dee3..cca66d6b 100644 --- a/android/view/WindowManagerGlobal.java +++ b/android/view/WindowManagerGlobal.java @@ -605,9 +605,10 @@ public final class WindowManagerGlobal { public void setStoppedState(IBinder token, boolean stopped) { synchronized (mLock) { int count = mViews.size(); - for (int i = 0; i < count; i++) { + for (int i = count - 1; i >= 0; i--) { if (token == null || mParams.get(i).token == token) { ViewRootImpl root = mRoots.get(i); + // Client might remove the view by "stopped" event. root.setWindowStopped(stopped); } } diff --git a/android/view/WindowManagerInternal.java b/android/view/WindowManagerInternal.java index 69cc1002..cd1b1908 100644 --- a/android/view/WindowManagerInternal.java +++ b/android/view/WindowManagerInternal.java @@ -18,6 +18,7 @@ package android.view; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ClipData; import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManagerInternal; @@ -140,6 +141,30 @@ public abstract class WindowManagerInternal { } /** + * An interface to customize drag and drop behaviors. + */ + public interface IDragDropCallback { + /** + * Called when drag operation is started. + */ + default boolean performDrag(IWindow window, IBinder dragToken, + int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY, + ClipData data) { + return true; + } + + /** + * Called when drop result is reported. + */ + default void reportDropResult(IWindow window, boolean consumed) {} + + /** + * Called when drag operation is cancelled. + */ + default void cancelDragAndDrop(IBinder dragToken) {} + } + + /** * Request that the window manager call * {@link DisplayManagerInternal#performTraversalInTransactionFromWindowManager} * within a surface transaction at a later time. @@ -225,9 +250,6 @@ public abstract class WindowManagerInternal { */ public abstract boolean isKeyguardLocked(); - /** @return {@code true} if the keyguard is going away. */ - public abstract boolean isKeyguardGoingAway(); - /** * @return Whether the keyguard is showing and not occluded. */ @@ -354,4 +376,9 @@ public abstract class WindowManagerInternal { * {@param vr2dDisplayId}. */ public abstract void setVr2dDisplayId(int vr2dDisplayId); + + /** + * Sets callback to DragDropController. + */ + public abstract void registerDragDropControllerCallback(IDragDropCallback callback); } diff --git a/android/view/WindowManagerPolicy.java b/android/view/WindowManagerPolicy.java index 137e551d..534335bf 100644 --- a/android/view/WindowManagerPolicy.java +++ b/android/view/WindowManagerPolicy.java @@ -66,7 +66,6 @@ import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SystemApi; -import android.app.ActivityManager.StackId; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.CompatibilityInfo; @@ -722,12 +721,6 @@ public interface WindowManagerPolicy { public void setInitialDisplaySize(Display display, int width, int height, int density); /** - * Called by window manager to set the overscan region that should be used for the - * given display. - */ - public void setDisplayOverscan(Display display, int left, int top, int right, int bottom); - - /** * Check permissions when adding a window. * * @param attrs The window's LayoutParams. @@ -758,7 +751,8 @@ public interface WindowManagerPolicy { * @param attrs The window layout parameters to be modified. These values * are modified in-place. */ - public void adjustWindowParamsLw(WindowManager.LayoutParams attrs); + public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs, + boolean hasStatusBarServicePermission); /** * After the window manager has computed the current configuration based @@ -1172,14 +1166,10 @@ public interface WindowManagerPolicy { /** * Called when layout of the windows is about to start. * - * @param isDefaultDisplay true if window is on {@link Display#DEFAULT_DISPLAY}. - * @param displayWidth The current full width of the screen. - * @param displayHeight The current full height of the screen. - * @param displayRotation The current rotation being applied to the base window. + * @param displayFrames frames of the display we are doing layout on. * @param uiMode The current uiMode in configuration. */ - public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight, - int displayRotation, int uiMode); + default void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {} /** * Returns the bottom-most layer of the system decor, above which no policy decor should @@ -1188,37 +1178,28 @@ public interface WindowManagerPolicy { public int getSystemDecorLayerLw(); /** - * Return the rectangle of the screen that is available for applications to run in. - * This will be called immediately after {@link #beginLayoutLw}. - * - * @param r The rectangle to be filled with the boundaries available to applications. - */ - public void getContentRectLw(Rect r); - - /** - * Called for each window attached to the window manager as layout is - * proceeding. The implementation of this function must take care of - * setting the window's frame, either here or in finishLayout(). + * Called for each window attached to the window manager as layout is proceeding. The + * implementation of this function must take care of setting the window's frame, either here or + * in finishLayout(). * * @param win The window being positioned. * @param attached For sub-windows, the window it is attached to; this * window will already have had layoutWindow() called on it * so you can use its Rect. Otherwise null. + * @param displayFrames The display frames. */ - public void layoutWindowLw(WindowState win, WindowState attached); + default void layoutWindowLw( + WindowState win, WindowState attached, DisplayFrames displayFrames) {} /** - * Return the insets for the areas covered by system windows. These values - * are computed on the most recent layout, so they are not guaranteed to - * be correct. + * Return the insets for the areas covered by system windows. These values are computed on the + * most recent layout, so they are not guaranteed to be correct. * * @param attrs The LayoutParams of the window. * @param taskBounds The bounds of the task this window is on or {@code null} if no task is * associated with the window. - * @param displayRotation Rotation of the display. - * @param displayWidth The width of the display. - * @param displayHeight The height of the display. + * @param displayFrames display frames. * @param outContentInsets The areas covered by system windows, expressed as positive insets. * @param outStableInsets The areas covered by stable system windows irrespective of their * current visibility. Expressed as positive insets. @@ -1226,16 +1207,11 @@ public interface WindowManagerPolicy { * @return Whether to always consume the navigation bar. * See {@link #isNavBarForcedShownLw(WindowState)}. */ - public boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds, - int displayRotation, int displayWidth, int displayHeight, Rect outContentInsets, - Rect outStableInsets, Rect outOutsets); - - /** - * Called when layout of the windows is finished. After this function has - * returned, all windows given to layoutWindow() <em>must</em> have had a - * frame assigned. - */ - public void finishLayoutLw(); + default boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds, + DisplayFrames displayFrames, Rect outContentInsets, Rect outStableInsets, + Rect outOutsets) { + return false; + } /** Layout state may have changed (so another layout will be performed) */ static final int FINISH_LAYOUT_REDO_LAYOUT = 0x0001; @@ -1652,11 +1628,6 @@ public interface WindowManagerPolicy { public void showGlobalActions(); /** - * @return The current height of the input method window. - */ - public int getInputMethodWindowVisibleHeightLw(); - - /** * Called when the current user changes. Guaranteed to be called before the broadcast * of the new user id is made to all listeners. * diff --git a/android/view/accessibility/AccessibilityInteractionClient.java b/android/view/accessibility/AccessibilityInteractionClient.java index 19213ca0..c3d6c695 100644 --- a/android/view/accessibility/AccessibilityInteractionClient.java +++ b/android/view/accessibility/AccessibilityInteractionClient.java @@ -187,8 +187,11 @@ public final class AccessibilityInteractionClient Log.i(LOG_TAG, "Window cache miss"); } final long identityToken = Binder.clearCallingIdentity(); - window = connection.getWindow(accessibilityWindowId); - Binder.restoreCallingIdentity(identityToken); + try { + window = connection.getWindow(accessibilityWindowId); + } finally { + Binder.restoreCallingIdentity(identityToken); + } if (window != null) { sAccessibilityCache.addWindow(window); return window; @@ -225,8 +228,11 @@ public final class AccessibilityInteractionClient Log.i(LOG_TAG, "Windows cache miss"); } final long identityToken = Binder.clearCallingIdentity(); - windows = connection.getWindows(); - Binder.restoreCallingIdentity(identityToken); + try { + windows = connection.getWindows(); + } finally { + Binder.restoreCallingIdentity(identityToken); + } if (windows != null) { sAccessibilityCache.setWindows(windows); return windows; @@ -283,10 +289,14 @@ public final class AccessibilityInteractionClient } final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId( - accessibilityWindowId, accessibilityNodeId, interactionId, this, - prefetchFlags, Thread.currentThread().getId(), arguments); - Binder.restoreCallingIdentity(identityToken); + final boolean success; + try { + success = connection.findAccessibilityNodeInfoByAccessibilityId( + accessibilityWindowId, accessibilityNodeId, interactionId, this, + prefetchFlags, Thread.currentThread().getId(), arguments); + } finally { + Binder.restoreCallingIdentity(identityToken); + } if (success) { List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); @@ -333,10 +343,15 @@ public final class AccessibilityInteractionClient if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.findAccessibilityNodeInfosByViewId( - accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this, - Thread.currentThread().getId()); - Binder.restoreCallingIdentity(identityToken); + final boolean success; + try { + success = connection.findAccessibilityNodeInfosByViewId( + accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this, + Thread.currentThread().getId()); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + if (success) { List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); @@ -381,10 +396,15 @@ public final class AccessibilityInteractionClient if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.findAccessibilityNodeInfosByText( - accessibilityWindowId, accessibilityNodeId, text, interactionId, this, - Thread.currentThread().getId()); - Binder.restoreCallingIdentity(identityToken); + final boolean success; + try { + success = connection.findAccessibilityNodeInfosByText( + accessibilityWindowId, accessibilityNodeId, text, interactionId, this, + Thread.currentThread().getId()); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + if (success) { List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); @@ -428,10 +448,15 @@ public final class AccessibilityInteractionClient if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.findFocus(accessibilityWindowId, - accessibilityNodeId, focusType, interactionId, this, - Thread.currentThread().getId()); - Binder.restoreCallingIdentity(identityToken); + final boolean success; + try { + success = connection.findFocus(accessibilityWindowId, + accessibilityNodeId, focusType, interactionId, this, + Thread.currentThread().getId()); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + if (success) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); @@ -472,10 +497,15 @@ public final class AccessibilityInteractionClient if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.focusSearch(accessibilityWindowId, - accessibilityNodeId, direction, interactionId, this, - Thread.currentThread().getId()); - Binder.restoreCallingIdentity(identityToken); + final boolean success; + try { + success = connection.focusSearch(accessibilityWindowId, + accessibilityNodeId, direction, interactionId, this, + Thread.currentThread().getId()); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + if (success) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); @@ -515,10 +545,15 @@ public final class AccessibilityInteractionClient if (connection != null) { final int interactionId = mInteractionIdCounter.getAndIncrement(); final long identityToken = Binder.clearCallingIdentity(); - final boolean success = connection.performAccessibilityAction( - accessibilityWindowId, accessibilityNodeId, action, arguments, - interactionId, this, Thread.currentThread().getId()); - Binder.restoreCallingIdentity(identityToken); + final boolean success; + try { + success = connection.performAccessibilityAction( + accessibilityWindowId, accessibilityNodeId, action, arguments, + interactionId, this, Thread.currentThread().getId()); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + if (success) { return getPerformAccessibilityActionResultAndClear(interactionId); } diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java index 0b9bc576..35f6acba 100644 --- a/android/view/accessibility/AccessibilityManager.java +++ b/android/view/accessibility/AccessibilityManager.java @@ -436,8 +436,11 @@ public final class AccessibilityManager { // client using it is called through Binder from another process. Example: MMS // app adds a SMS notification and the NotificationManagerService calls this method long identityToken = Binder.clearCallingIdentity(); - service.sendAccessibilityEvent(event, userId); - Binder.restoreCallingIdentity(identityToken); + try { + service.sendAccessibilityEvent(event, userId); + } finally { + Binder.restoreCallingIdentity(identityToken); + } if (DEBUG) { Log.i(LOG_TAG, event + " sent"); } diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java index e79d201b..9241ec00 100644 --- a/android/view/autofill/AutofillManager.java +++ b/android/view/autofill/AutofillManager.java @@ -54,6 +54,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +// TODO: use java.lang.ref.Cleaner once Android supports Java 9 +import sun.misc.Cleaner; + /** * The {@link AutofillManager} provides ways for apps and custom views to integrate with the * Autofill Framework lifecycle. @@ -303,6 +306,9 @@ public final class AutofillManager { private IAutoFillManagerClient mServiceClient; @GuardedBy("mLock") + private Cleaner mServiceClientCleaner; + + @GuardedBy("mLock") private AutofillCallback mCallback; private final Context mContext; @@ -1172,10 +1178,19 @@ public final class AutofillManager { if (mServiceClient == null) { mServiceClient = new AutofillManagerClient(this); try { - final int flags = mService.addClient(mServiceClient, mContext.getUserId()); + final int userId = mContext.getUserId(); + final int flags = mService.addClient(mServiceClient, userId); mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0; sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0; sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0; + final IAutoFillManager service = mService; + final IAutoFillManagerClient serviceClient = mServiceClient; + mServiceClientCleaner = Cleaner.create(this, () -> { + try { + service.removeClient(serviceClient, userId); + } catch (RemoteException e) { + } + }); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1272,18 +1287,36 @@ public final class AutofillManager { } } - private void setState(boolean enabled, boolean resetSession, boolean resetClient) { + /** @hide */ + public static final int SET_STATE_FLAG_ENABLED = 0x01; + /** @hide */ + public static final int SET_STATE_FLAG_RESET_SESSION = 0x02; + /** @hide */ + public static final int SET_STATE_FLAG_RESET_CLIENT = 0x04; + /** @hide */ + public static final int SET_STATE_FLAG_DEBUG = 0x08; + /** @hide */ + public static final int SET_STATE_FLAG_VERBOSE = 0x10; + + private void setState(int flags) { + if (sVerbose) Log.v(TAG, "setState(" + flags + ")"); synchronized (mLock) { - mEnabled = enabled; - if (!mEnabled || resetSession) { + mEnabled = (flags & SET_STATE_FLAG_ENABLED) != 0; + if (!mEnabled || (flags & SET_STATE_FLAG_RESET_SESSION) != 0) { // Reset the session state resetSessionLocked(); } - if (resetClient) { + if ((flags & SET_STATE_FLAG_RESET_CLIENT) != 0) { // Reset connection to system mServiceClient = null; + if (mServiceClientCleaner != null) { + mServiceClientCleaner.clean(); + mServiceClientCleaner = null; + } } } + sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0; + sVerbose = (flags & SET_STATE_FLAG_VERBOSE) != 0; } /** @@ -1609,6 +1642,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("enabled: "); pw.println(mEnabled); pw.print(pfx); pw.print("hasService: "); pw.println(mService != null); pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null); @@ -1625,6 +1659,8 @@ public final class AutofillManager { pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds); pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId); pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish); + pw.print(pfx); pw.print("debug: "); pw.print(sDebug); + pw.print(" verbose: "); pw.println(sVerbose); } private String getStateAsStringLocked() { @@ -1880,7 +1916,7 @@ public final class AutofillManager { public abstract static class AutofillCallback { /** @hide */ - @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN}) + @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN, EVENT_INPUT_UNAVAILABLE}) @Retention(RetentionPolicy.SOURCE) public @interface AutofillEventType {} @@ -1940,10 +1976,10 @@ public final class AutofillManager { } @Override - public void setState(boolean enabled, boolean resetSession, boolean resetClient) { + public void setState(int flags) { final AutofillManager afm = mAfm.get(); if (afm != null) { - afm.post(() -> afm.setState(enabled, resetSession, resetClient)); + afm.post(() -> afm.setState(flags)); } } diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java index 26d2141e..2779aa2d 100644 --- a/android/view/textclassifier/TextClassification.java +++ b/android/view/textclassifier/TextClassification.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; +import android.os.LocaleList; import android.view.View.OnClickListener; import android.view.textclassifier.TextClassifier.EntityType; @@ -438,4 +439,31 @@ public final class TextClassification { mLogType, mVersionInfo); } } + + /** + * TextClassification optional input parameters. + */ + public static final class Options { + + private LocaleList mDefaultLocales; + + /** + * @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; + } + + /** + * @return ordered list of locale preferences that can be used to disambiguate + * the provided text. + */ + @Nullable + public LocaleList getDefaultLocales() { + return mDefaultLocales; + } + } } diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java index 46dbd0e3..aeb84897 100644 --- a/android/view/textclassifier/TextClassifier.java +++ b/android/view/textclassifier/TextClassifier.java @@ -56,23 +56,7 @@ public interface TextClassifier { * No-op TextClassifier. * This may be used to turn off TextClassifier features. */ - TextClassifier NO_OP = new TextClassifier() { - - @Override - public TextSelection suggestSelection( - CharSequence text, - int selectionStartIndex, - int selectionEndIndex, - LocaleList defaultLocales) { - return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build(); - } - - @Override - public TextClassification classifyText( - CharSequence text, int startIndex, int endIndex, LocaleList defaultLocales) { - return TextClassification.EMPTY; - } - }; + TextClassifier NO_OP = new TextClassifier() {}; /** * Returns suggested text selection start and end indices, recognized entity types, and their @@ -82,21 +66,34 @@ public interface TextClassifier { * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) * @param selectionStartIndex start index of the selected part of text * @param selectionEndIndex end index of the selected part of text - * @param defaultLocales ordered list of locale preferences that can be used to disambiguate - * the provided text. If no locale preferences exist, set this to null or an empty locale - * list in which case the classifier will decide whether to use no locale information, use - * a default locale, or use the system default. + * @param options optional input parameters * * @throws IllegalArgumentException if text is null; selectionStartIndex is negative; * selectionEndIndex is greater than text.length() or not greater than selectionStartIndex */ @WorkerThread @NonNull - TextSelection suggestSelection( + default TextSelection suggestSelection( + @NonNull CharSequence text, + @IntRange(from = 0) int selectionStartIndex, + @IntRange(from = 0) int selectionEndIndex, + @Nullable TextSelection.Options options) { + return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build(); + } + + /** + * @see #suggestSelection(CharSequence, int, int, TextSelection.Options) + */ + // TODO: Consider deprecating (b/68846316) + @WorkerThread + @NonNull + default TextSelection suggestSelection( @NonNull CharSequence text, @IntRange(from = 0) int selectionStartIndex, @IntRange(from = 0) int selectionEndIndex, - @Nullable LocaleList defaultLocales); + @Nullable LocaleList defaultLocales) { + return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build(); + } /** * Classifies the specified text and returns a {@link TextClassification} object that can be @@ -106,41 +103,48 @@ public interface TextClassifier { * by the sub sequence starting at startIndex and ending at endIndex) * @param startIndex start index of the text to classify * @param endIndex end index of the text to classify - * @param defaultLocales ordered list of locale preferences that can be used to disambiguate - * the provided text. If no locale preferences exist, set this to null or an empty locale - * list in which case the classifier will decide whether to use no locale information, use - * a default locale, or use the system default. + * @param options optional input parameters * * @throws IllegalArgumentException if text is null; startIndex is negative; * endIndex is greater than text.length() or not greater than startIndex */ @WorkerThread @NonNull - TextClassification classifyText( + default TextClassification classifyText( @NonNull CharSequence text, @IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex, - @Nullable LocaleList defaultLocales); + @Nullable TextClassification.Options options) { + return TextClassification.EMPTY; + } /** - * Returns a {@link LinksInfo} that may be applied to the text to annotate it with links + * @see #classifyText(CharSequence, int, int, TextClassification.Options) + */ + // TODO: Consider deprecating (b/68846316) + @WorkerThread + @NonNull + default TextClassification classifyText( + @NonNull CharSequence text, + @IntRange(from = 0) int startIndex, + @IntRange(from = 0) int endIndex, + @Nullable LocaleList defaultLocales) { + return TextClassification.EMPTY; + } + + /** + * Returns a {@link TextLinks} that may be applied to the text to annotate it with links * information. * * @param text the text to generate annotations for - * @param linkMask See {@link android.text.util.Linkify} for a list of linkMasks that may be - * specified. Subclasses of this interface may specify additional linkMasks - * @param defaultLocales ordered list of locale preferences that can be used to disambiguate - * the provided text. If no locale preferences exist, set this to null or an empty locale - * list in which case the classifier will decide whether to use no locale information, use - * a default locale, or use the system default. + * @param options configuration for link generation. If null, defaults will be used. * * @throws IllegalArgumentException if text is null - * @hide */ @WorkerThread - default LinksInfo getLinks( - @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) { - return LinksInfo.NO_OP; + default TextLinks generateLinks( + @NonNull CharSequence text, @Nullable TextLinks.Options options) { + return new TextLinks.Builder(text.toString()).build(); } /** diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java index 1c07be4b..2ad6e02c 100644 --- a/android/view/textclassifier/TextClassifierImpl.java +++ b/android/view/textclassifier/TextClassifierImpl.java @@ -30,13 +30,8 @@ import android.os.ParcelFileDescriptor; import android.provider.Browser; import android.provider.ContactsContract; import android.provider.Settings; -import android.text.Spannable; -import android.text.TextUtils; -import android.text.method.WordIterator; -import android.text.style.ClickableSpan; import android.text.util.Linkify; import android.util.Patterns; -import android.view.View; import android.widget.TextViewMetrics; import com.android.internal.annotations.GuardedBy; @@ -46,13 +41,8 @@ import com.android.internal.util.Preconditions; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.text.BreakIterator; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; @@ -100,16 +90,24 @@ final class TextClassifierImpl implements TextClassifier { @Override public TextSelection suggestSelection( @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex, - @Nullable LocaleList defaultLocales) { + @Nullable TextSelection.Options options) { validateInput(text, selectionStartIndex, selectionEndIndex); try { if (text.length() > 0) { - final SmartSelection smartSelection = getSmartSelection(defaultLocales); + final LocaleList locales = (options == null) ? null : options.getDefaultLocales(); + final SmartSelection smartSelection = getSmartSelection(locales); final String string = text.toString(); - final int[] startEnd = smartSelection.suggest( - string, selectionStartIndex, selectionEndIndex); - final int start = startEnd[0]; - final int end = startEnd[1]; + final int start; + final int end; + if (getSettings().isDarkLaunch() && !options.isDarkLaunchAllowed()) { + start = selectionStartIndex; + end = selectionEndIndex; + } else { + final int[] startEnd = smartSelection.suggest( + string, selectionStartIndex, selectionEndIndex); + start = startEnd[0]; + end = startEnd[1]; + } if (start <= end && start >= 0 && end <= string.length() && start <= selectionStartIndex && end >= selectionEndIndex) { @@ -139,18 +137,27 @@ final class TextClassifierImpl implements TextClassifier { } // Getting here means something went wrong, return a NO_OP result. return TextClassifier.NO_OP.suggestSelection( - text, selectionStartIndex, selectionEndIndex, defaultLocales); + text, selectionStartIndex, selectionEndIndex, options); + } + + @Override + public TextSelection suggestSelection( + @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex, + @Nullable LocaleList defaultLocales) { + return suggestSelection(text, selectionStartIndex, selectionEndIndex, + new TextSelection.Options().setDefaultLocales(defaultLocales)); } @Override public TextClassification classifyText( @NonNull CharSequence text, int startIndex, int endIndex, - @Nullable LocaleList defaultLocales) { + @Nullable TextClassification.Options options) { validateInput(text, startIndex, endIndex); try { if (text.length() > 0) { final String string = text.toString(); - SmartSelection.ClassificationResult[] results = getSmartSelection(defaultLocales) + final LocaleList locales = (options == null) ? null : options.getDefaultLocales(); + final SmartSelection.ClassificationResult[] results = getSmartSelection(locales) .classifyText(string, startIndex, endIndex, getHintFlags(string, startIndex, endIndex)); if (results.length > 0) { @@ -165,23 +172,41 @@ final class TextClassifierImpl implements TextClassifier { Log.e(LOG_TAG, "Error getting text classification info.", t); } // Getting here means something went wrong, return a NO_OP result. - return TextClassifier.NO_OP.classifyText( - text, startIndex, endIndex, defaultLocales); + return TextClassifier.NO_OP.classifyText(text, startIndex, endIndex, options); } @Override - public LinksInfo getLinks( - @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) { - Preconditions.checkArgument(text != null); + public TextClassification classifyText( + @NonNull CharSequence text, int startIndex, int endIndex, + @Nullable LocaleList defaultLocales) { + return classifyText(text, startIndex, endIndex, + new TextClassification.Options().setDefaultLocales(defaultLocales)); + } + + @Override + public TextLinks generateLinks( + @NonNull CharSequence text, @Nullable TextLinks.Options options) { + Preconditions.checkNotNull(text); + final String textString = text.toString(); + final TextLinks.Builder builder = new TextLinks.Builder(textString); try { - return LinksInfoFactory.create( - mContext, getSmartSelection(defaultLocales), text.toString(), linkMask); + LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null; + final SmartSelection smartSelection = getSmartSelection(defaultLocales); + final SmartSelection.AnnotatedSpan[] annotations = smartSelection.annotate(textString); + for (SmartSelection.AnnotatedSpan span : annotations) { + final Map<String, Float> entityScores = new HashMap<>(); + final SmartSelection.ClassificationResult[] results = span.getClassification(); + for (int i = 0; i < results.length; i++) { + entityScores.put(results[i].mCollection, results[i].mScore); + } + builder.addLink(new TextLinks.TextLink( + textString, span.getStartIndex(), span.getEndIndex(), entityScores)); + } } catch (Throwable t) { // Avoid throwing from this method. Log the error. Log.e(LOG_TAG, "Error getting links info.", t); } - // Getting here means something went wrong, return a NO_OP result. - return TextClassifier.NO_OP.getLinks(text, linkMask, defaultLocales); + return builder.build(); } @Override @@ -210,7 +235,9 @@ final class TextClassifierImpl implements TextClassifier { if (mSmartSelection == null || !Objects.equals(mLocale, locale)) { destroySmartSelectionIfExistsLocked(); final ParcelFileDescriptor fd = getFdLocked(locale); - mSmartSelection = new SmartSelection(fd.getFd()); + final int modelFd = fd.getFd(); + mVersion = SmartSelection.getVersion(modelFd); + mSmartSelection = new SmartSelection(modelFd); closeAndLogError(fd); mLocale = locale; } @@ -231,18 +258,26 @@ final class TextClassifierImpl implements TextClassifier { @GuardedBy("mSmartSelectionLock") // Do not call outside this lock. private ParcelFileDescriptor getFdLocked(Locale locale) throws FileNotFoundException { ParcelFileDescriptor updateFd; + int updateVersion = -1; try { updateFd = ParcelFileDescriptor.open( new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY); + if (updateFd != null) { + updateVersion = SmartSelection.getVersion(updateFd.getFd()); + } } catch (FileNotFoundException e) { updateFd = null; } ParcelFileDescriptor factoryFd; + int factoryVersion = -1; try { final String factoryModelFilePath = getFactoryModelFilePathsLocked().get(locale); if (factoryModelFilePath != null) { factoryFd = ParcelFileDescriptor.open( new File(factoryModelFilePath), ParcelFileDescriptor.MODE_READ_ONLY); + if (factoryFd != null) { + factoryVersion = SmartSelection.getVersion(factoryFd.getFd()); + } } else { factoryFd = null; } @@ -278,15 +313,11 @@ final class TextClassifierImpl implements TextClassifier { return factoryFd; } - final int updateVersion = SmartSelection.getVersion(updateFdInt); - final int factoryVersion = SmartSelection.getVersion(factoryFd.getFd()); if (updateVersion > factoryVersion) { closeAndLogError(factoryFd); - mVersion = updateVersion; return updateFd; } else { closeAndLogError(updateFd); - mVersion = factoryVersion; return factoryFd; } } @@ -466,180 +497,6 @@ final class TextClassifierImpl implements TextClassifier { } /** - * Detects and creates links for specified text. - */ - private static final class LinksInfoFactory { - - private LinksInfoFactory() {} - - public static LinksInfo create( - Context context, SmartSelection smartSelection, String text, int linkMask) { - final WordIterator wordIterator = new WordIterator(); - wordIterator.setCharSequence(text, 0, text.length()); - final List<SpanSpec> spans = new ArrayList<>(); - int start = 0; - int end; - while ((end = wordIterator.nextBoundary(start)) != BreakIterator.DONE) { - final String token = text.substring(start, end); - if (TextUtils.isEmpty(token)) { - continue; - } - - final int[] selection = smartSelection.suggest(text, start, end); - final int selectionStart = selection[0]; - final int selectionEnd = selection[1]; - if (selectionStart >= 0 && selectionEnd <= text.length() - && selectionStart <= selectionEnd) { - final SmartSelection.ClassificationResult[] results = - smartSelection.classifyText( - text, selectionStart, selectionEnd, - getHintFlags(text, selectionStart, selectionEnd)); - if (results.length > 0) { - final String type = getHighestScoringType(results); - if (matches(type, linkMask)) { - // For links without disambiguation, we simply use the default intent. - final List<Intent> intents = IntentFactory.create( - context, type, text.substring(selectionStart, selectionEnd)); - if (!intents.isEmpty() && hasActivityHandler(context, intents.get(0))) { - final ClickableSpan span = createSpan(context, intents.get(0)); - spans.add(new SpanSpec(selectionStart, selectionEnd, span)); - } - } - } - } - start = end; - } - return new LinksInfoImpl(text, avoidOverlaps(spans, text)); - } - - /** - * Returns true if the classification type matches the specified linkMask. - */ - private static boolean matches(String type, int linkMask) { - type = type.trim().toLowerCase(Locale.ENGLISH); - if ((linkMask & Linkify.PHONE_NUMBERS) != 0 - && TextClassifier.TYPE_PHONE.equals(type)) { - return true; - } - if ((linkMask & Linkify.EMAIL_ADDRESSES) != 0 - && TextClassifier.TYPE_EMAIL.equals(type)) { - return true; - } - if ((linkMask & Linkify.MAP_ADDRESSES) != 0 - && TextClassifier.TYPE_ADDRESS.equals(type)) { - return true; - } - if ((linkMask & Linkify.WEB_URLS) != 0 - && TextClassifier.TYPE_URL.equals(type)) { - return true; - } - return false; - } - - /** - * Trim the number of spans so that no two spans overlap. - * - * This algorithm first ensures that there is only one span per start index, then it - * makes sure that no two spans overlap. - */ - private static List<SpanSpec> avoidOverlaps(List<SpanSpec> spans, String text) { - Collections.sort(spans, Comparator.comparingInt(span -> span.mStart)); - // Group spans by start index. Take the longest span. - final Map<Integer, SpanSpec> reps = new LinkedHashMap<>(); // order matters. - final int size = spans.size(); - for (int i = 0; i < size; i++) { - final SpanSpec span = spans.get(i); - final LinksInfoFactory.SpanSpec rep = reps.get(span.mStart); - if (rep == null || rep.mEnd < span.mEnd) { - reps.put(span.mStart, span); - } - } - // Avoid span intersections. Take the longer span. - final LinkedList<SpanSpec> result = new LinkedList<>(); - for (SpanSpec rep : reps.values()) { - if (result.isEmpty()) { - result.add(rep); - continue; - } - - final SpanSpec last = result.getLast(); - if (rep.mStart < last.mEnd) { - // Spans intersect. Use the one with characters. - if ((rep.mEnd - rep.mStart) > (last.mEnd - last.mStart)) { - result.set(result.size() - 1, rep); - } - } else { - result.add(rep); - } - } - return result; - } - - private static ClickableSpan createSpan(final Context context, final Intent intent) { - return new ClickableSpan() { - // TODO: Style this span. - @Override - public void onClick(View widget) { - context.startActivity(intent); - } - }; - } - - private static boolean hasActivityHandler(Context context, Intent intent) { - if (intent == null) { - return false; - } - final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0); - return resolveInfo != null && resolveInfo.activityInfo != null; - } - - /** - * Implementation of LinksInfo that adds ClickableSpans to the specified text. - */ - private static final class LinksInfoImpl implements LinksInfo { - - private final CharSequence mOriginalText; - private final List<SpanSpec> mSpans; - - LinksInfoImpl(CharSequence originalText, List<SpanSpec> spans) { - mOriginalText = originalText; - mSpans = spans; - } - - @Override - public boolean apply(@NonNull CharSequence text) { - Preconditions.checkArgument(text != null); - if (text instanceof Spannable && mOriginalText.toString().equals(text.toString())) { - Spannable spannable = (Spannable) text; - final int size = mSpans.size(); - for (int i = 0; i < size; i++) { - final SpanSpec span = mSpans.get(i); - spannable.setSpan(span.mSpan, span.mStart, span.mEnd, 0); - } - return true; - } - return false; - } - } - - /** - * Span plus its start and end index. - */ - private static final class SpanSpec { - - private final int mStart; - private final int mEnd; - private final ClickableSpan mSpan; - - SpanSpec(int start, int end, ClickableSpan span) { - mStart = start; - mEnd = end; - mSpan = span; - } - } - } - - /** * Creates intents based on the classification type. */ private static final class IntentFactory { @@ -656,8 +513,8 @@ final class TextClassifierImpl implements TextClassifier { intents.add(new Intent(Intent.ACTION_SENDTO) .setData(Uri.parse(String.format("mailto:%s", text)))); intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT) - .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) - .putExtra(ContactsContract.Intents.Insert.EMAIL, text)); + .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE) + .putExtra(ContactsContract.Intents.Insert.EMAIL, text)); break; case TextClassifier.TYPE_PHONE: intents.add(new Intent(Intent.ACTION_DIAL) diff --git a/android/view/textclassifier/TextLinks.java b/android/view/textclassifier/TextLinks.java new file mode 100644 index 00000000..f3cc827f --- /dev/null +++ b/android/view/textclassifier/TextLinks.java @@ -0,0 +1,252 @@ +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import android.annotation.FloatRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.LocaleList; +import android.text.SpannableString; +import android.text.style.ClickableSpan; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +/** + * A collection of links, representing subsequences of text and the entity types (phone number, + * address, url, etc) they may be. + */ +public final class TextLinks { + private final String mFullText; + private final List<TextLink> mLinks; + + private TextLinks(String fullText, Collection<TextLink> links) { + mFullText = fullText; + mLinks = Collections.unmodifiableList(new ArrayList<>(links)); + } + + /** + * Returns an unmodifiable Collection of the links. + */ + public Collection<TextLink> getLinks() { + return mLinks; + } + + /** + * Annotates the given text with the generated links. It will fail if the provided text doesn't + * match the original text used to crete the TextLinks. + * + * @param text the text to apply the links to. Must match the original text. + * @param spanFactory a factory to generate spans from TextLinks. Will use a default if null. + * + * @return Success or failure. + */ + public boolean apply( + @NonNull SpannableString text, + @Nullable Function<TextLink, ClickableSpan> spanFactory) { + Preconditions.checkNotNull(text); + if (!mFullText.equals(text.toString())) { + return false; + } + + if (spanFactory == null) { + spanFactory = DEFAULT_SPAN_FACTORY; + } + for (TextLink link : mLinks) { + final ClickableSpan span = spanFactory.apply(link); + if (span != null) { + text.setSpan(span, link.getStart(), link.getEnd(), 0); + } + } + return true; + } + + /** + * A link, identifying a substring of text and possible entity types for it. + */ + public static final class TextLink { + private final EntityConfidence<String> mEntityScores; + private final String mOriginalText; + private final int mStart; + private final int mEnd; + + /** + * Create a new TextLink. + * + * @throws IllegalArgumentException if entityScores is null or empty. + */ + public TextLink(String originalText, int start, int end, Map<String, Float> entityScores) { + Preconditions.checkNotNull(originalText); + Preconditions.checkNotNull(entityScores); + Preconditions.checkArgument(!entityScores.isEmpty()); + Preconditions.checkArgument(start <= end); + mOriginalText = originalText; + mStart = start; + mEnd = end; + mEntityScores = new EntityConfidence<>(); + + for (Map.Entry<String, Float> entry : entityScores.entrySet()) { + mEntityScores.setEntityType(entry.getKey(), entry.getValue()); + } + } + + /** + * Returns the start index of this link in the original text. + * + * @return the start index. + */ + public int getStart() { + return mStart; + } + + /** + * Returns the end index of this link in the original text. + * + * @return the end index. + */ + public int getEnd() { + return mEnd; + } + + /** + * Returns the number of entity types that have confidence scores. + * + * @return the entity count. + */ + public int getEntityCount() { + return mEntityScores.getEntities().size(); + } + + /** + * Returns the entity type at a given index. Entity types are sorted by confidence. + * + * @return the entity type at the provided index. + */ + @NonNull public @TextClassifier.EntityType String getEntity(int index) { + return mEntityScores.getEntities().get(index); + } + + /** + * Returns the confidence score for a particular entity type. + * + * @param entityType the entity type. + */ + public @FloatRange(from = 0.0, to = 1.0) float getConfidenceScore( + @TextClassifier.EntityType String entityType) { + return mEntityScores.getConfidenceScore(entityType); + } + } + + /** + * Optional input parameters for generating TextLinks. + */ + public static final class Options { + private final LocaleList mLocaleList; + + private Options(LocaleList localeList) { + this.mLocaleList = localeList; + } + + /** + * Builder to construct Options. + */ + public static final class Builder { + private LocaleList mLocaleList; + + /** + * Sets the LocaleList to use. + * + * @return this Builder. + */ + public Builder setLocaleList(@Nullable LocaleList localeList) { + this.mLocaleList = localeList; + return this; + } + + /** + * Builds the Options object. + */ + public Options build() { + return new Options(mLocaleList); + } + } + public @Nullable LocaleList getDefaultLocales() { + return mLocaleList; + } + }; + + /** + * A function to create spans from TextLinks. + * + * Applies only to TextViews. + * We can hide this until we are convinced we want it to be part of the public API. + * + * @hide + */ + public static final Function<TextLink, ClickableSpan> DEFAULT_SPAN_FACTORY = + new Function<TextLink, ClickableSpan>() { + @Override + public ClickableSpan apply(TextLink textLink) { + // TODO: Implement. + throw new UnsupportedOperationException("Not yet implemented"); + } + }; + + /** + * A builder to construct a TextLinks instance. + */ + public static final class Builder { + private final String mFullText; + private final Collection<TextLink> mLinks; + + /** + * Create a new TextLinks.Builder. + * + * @param fullText The full text that links will be added to. + */ + public Builder(@NonNull String fullText) { + mFullText = Preconditions.checkNotNull(fullText); + mLinks = new ArrayList<>(); + } + + /** + * Adds a TextLink. + * + * @return this instance. + */ + public Builder addLink(TextLink link) { + Preconditions.checkNotNull(link); + mLinks.add(link); + return this; + } + + /** + * Constructs a TextLinks instance. + * + * @return the constructed TextLinks. + */ + public TextLinks build() { + return new TextLinks(mFullText, mLinks); + } + } +} diff --git a/android/view/textclassifier/TextSelection.java b/android/view/textclassifier/TextSelection.java index 11ebe835..0a67954a 100644 --- a/android/view/textclassifier/TextSelection.java +++ b/android/view/textclassifier/TextSelection.java @@ -19,6 +19,8 @@ package android.view.textclassifier; import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.LocaleList; import android.view.textclassifier.TextClassifier.EntityType; import com.android.internal.util.Preconditions; @@ -181,4 +183,55 @@ public final class TextSelection { mStartIndex, mEndIndex, mEntityConfidence, mLogSource, mVersionInfo); } } + + /** + * TextSelection optional input parameters. + */ + public static final class Options { + + private LocaleList mDefaultLocales; + private boolean mDarkLaunchAllowed; + + /** + * @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; + } + + /** + * @return ordered list of locale preferences that can be used to disambiguate + * the provided text. + */ + @Nullable + public LocaleList getDefaultLocales() { + return mDefaultLocales; + } + + /** + * @param allowed whether or not the TextClassifier should return selection suggestions + * when "dark launched". When a TextClassifier is dark launched, it can suggest + * selection changes that should not be used to actually change the user's selection. + * Instead, the suggested selection is logged, compared with the user's selection + * interaction, and used to generate quality metrics for the TextClassifier. + * + * @hide + */ + public void setDarkLaunchAllowed(boolean allowed) { + mDarkLaunchAllowed = allowed; + } + + /** + * Returns true if the TextClassifier should return selection suggestions when + * "dark launched". Otherwise, returns false. + * + * @hide + */ + public boolean isDarkLaunchAllowed() { + return mDarkLaunchAllowed; + } + } } diff --git a/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/android/view/textclassifier/logging/SmartSelectionEventTracker.java index 83af19bb..2833564f 100644 --- a/android/view/textclassifier/logging/SmartSelectionEventTracker.java +++ b/android/view/textclassifier/logging/SmartSelectionEventTracker.java @@ -48,31 +48,45 @@ public final class SmartSelectionEventTracker { private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START; private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS; private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX; - private static final int VERSION_TAG = MetricsEvent.FIELD_SELECTION_VERSION_TAG; - private static final int SMART_INDICES = MetricsEvent.FIELD_SELECTION_SMART_RANGE; - private static final int EVENT_INDICES = MetricsEvent.FIELD_SELECTION_RANGE; + private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE; + private static final int WIDGET_VERSION = MetricsEvent.FIELD_SELECTION_WIDGET_VERSION; + private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL; + private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE; + private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START; + private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END; + private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START; + private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END; private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID; private static final String ZERO = "0"; private static final String TEXTVIEW = "textview"; private static final String EDITTEXT = "edittext"; + private static final String UNSELECTABLE_TEXTVIEW = "nosel-textview"; private static final String WEBVIEW = "webview"; private static final String EDIT_WEBVIEW = "edit-webview"; + private static final String CUSTOM_TEXTVIEW = "customview"; + private static final String CUSTOM_EDITTEXT = "customedit"; + private static final String CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview"; private static final String UNKNOWN = "unknown"; @Retention(RetentionPolicy.SOURCE) @IntDef({WidgetType.UNSPECIFIED, WidgetType.TEXTVIEW, WidgetType.WEBVIEW, WidgetType.EDITTEXT, WidgetType.EDIT_WEBVIEW}) public @interface WidgetType { - int UNSPECIFIED = 0; - int TEXTVIEW = 1; - int WEBVIEW = 2; - int EDITTEXT = 3; - int EDIT_WEBVIEW = 4; + int UNSPECIFIED = 0; + int TEXTVIEW = 1; + int WEBVIEW = 2; + int EDITTEXT = 3; + int EDIT_WEBVIEW = 4; + int UNSELECTABLE_TEXTVIEW = 5; + int CUSTOM_TEXTVIEW = 6; + int CUSTOM_EDITTEXT = 7; + int CUSTOM_UNSELECTABLE_TEXTVIEW = 8; } private final MetricsLogger mMetricsLogger = new MetricsLogger(); private final int mWidgetType; + @Nullable private final String mWidgetVersion; private final Context mContext; @Nullable private String mSessionId; @@ -83,10 +97,18 @@ public final class SmartSelectionEventTracker { private long mSessionStartTime; private long mLastEventTime; private boolean mSmartSelectionTriggered; - private String mVersionTag; + private String mModelName; public SmartSelectionEventTracker(@NonNull Context context, @WidgetType int widgetType) { mWidgetType = widgetType; + mWidgetVersion = null; + mContext = Preconditions.checkNotNull(context); + } + + public SmartSelectionEventTracker( + @NonNull Context context, @WidgetType int widgetType, @Nullable String widgetVersion) { + mWidgetType = widgetType; + mWidgetVersion = widgetVersion; mContext = Preconditions.checkNotNull(context); } @@ -115,7 +137,7 @@ public final class SmartSelectionEventTracker { case SelectionEvent.EventType.SMART_SELECTION_SINGLE: // fall through case SelectionEvent.EventType.SMART_SELECTION_MULTI: mSmartSelectionTriggered = true; - mVersionTag = getVersionTag(event); + mModelName = getModelName(event); mSmartIndices[0] = event.mStart; mSmartIndices[1] = event.mEnd; break; @@ -137,14 +159,19 @@ public final class SmartSelectionEventTracker { final long prevEventDelta = mLastEventTime == 0 ? 0 : now - mLastEventTime; final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION) .setType(getLogType(event)) - .setSubtype(getLogSubType(event)) + .setSubtype(MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL) .setPackageName(mContext.getPackageName()) .addTaggedData(START_EVENT_DELTA, now - mSessionStartTime) .addTaggedData(PREV_EVENT_DELTA, prevEventDelta) .addTaggedData(INDEX, mIndex) - .addTaggedData(VERSION_TAG, mVersionTag) - .addTaggedData(SMART_INDICES, getSmartDelta()) - .addTaggedData(EVENT_INDICES, getEventDelta(event)) + .addTaggedData(WIDGET_TYPE, getWidgetTypeName()) + .addTaggedData(WIDGET_VERSION, mWidgetVersion) + .addTaggedData(MODEL_NAME, mModelName) + .addTaggedData(ENTITY_TYPE, event.mEntityType) + .addTaggedData(SMART_START, getSmartRangeDelta(mSmartIndices[0])) + .addTaggedData(SMART_END, getSmartRangeDelta(mSmartIndices[1])) + .addTaggedData(EVENT_START, getRangeDelta(event.mStart)) + .addTaggedData(EVENT_END, getRangeDelta(event.mEnd)) .addTaggedData(SESSION_ID, mSessionId); mMetricsLogger.write(log); debugLog(log); @@ -169,7 +196,7 @@ public final class SmartSelectionEventTracker { mSessionStartTime = 0; mLastEventTime = 0; mSmartSelectionTriggered = false; - mVersionTag = getVersionTag(null); + mModelName = getModelName(null); mSessionId = null; } @@ -251,113 +278,75 @@ public final class SmartSelectionEventTracker { } } - private static int getLogSubType(SelectionEvent event) { - switch (event.mEntityType) { - case TextClassifier.TYPE_OTHER: - return MetricsEvent.TEXT_CLASSIFIER_TYPE_OTHER; - case TextClassifier.TYPE_EMAIL: - return MetricsEvent.TEXT_CLASSIFIER_TYPE_EMAIL; - case TextClassifier.TYPE_PHONE: - return MetricsEvent.TEXT_CLASSIFIER_TYPE_PHONE; - case TextClassifier.TYPE_ADDRESS: - return MetricsEvent.TEXT_CLASSIFIER_TYPE_ADDRESS; - case TextClassifier.TYPE_URL: - return MetricsEvent.TEXT_CLASSIFIER_TYPE_URL; - default: - return MetricsEvent.TEXT_CLASSIFIER_TYPE_UNKNOWN; - } - } - - private static String getLogSubTypeString(int logSubType) { - switch (logSubType) { - case MetricsEvent.TEXT_CLASSIFIER_TYPE_OTHER: - return TextClassifier.TYPE_OTHER; - case MetricsEvent.TEXT_CLASSIFIER_TYPE_EMAIL: - return TextClassifier.TYPE_EMAIL; - case MetricsEvent.TEXT_CLASSIFIER_TYPE_PHONE: - return TextClassifier.TYPE_PHONE; - case MetricsEvent.TEXT_CLASSIFIER_TYPE_ADDRESS: - return TextClassifier.TYPE_ADDRESS; - case MetricsEvent.TEXT_CLASSIFIER_TYPE_URL: - return TextClassifier.TYPE_URL; - default: - return TextClassifier.TYPE_UNKNOWN; - } - } - - private int getSmartDelta() { - if (mSmartSelectionTriggered) { - return (clamp(mSmartIndices[0] - mOrigStart) << 16) - | (clamp(mSmartIndices[1] - mOrigStart) & 0xffff); - } - // If the smart selection model was not run, return invalid selection indices [0,0]. This - // allows us to tell from the terminal event alone whether the model was run. - return 0; + private int getRangeDelta(int offset) { + return offset - mOrigStart; } - private int getEventDelta(SelectionEvent event) { - return (clamp(event.mStart - mOrigStart) << 16) - | (clamp(event.mEnd - mOrigStart) & 0xffff); + private int getSmartRangeDelta(int offset) { + return mSmartSelectionTriggered ? getRangeDelta(offset) : 0; } - private String getVersionTag(@Nullable SelectionEvent event) { - final String widgetType; + private String getWidgetTypeName() { switch (mWidgetType) { case WidgetType.TEXTVIEW: - widgetType = TEXTVIEW; - break; + return TEXTVIEW; case WidgetType.WEBVIEW: - widgetType = WEBVIEW; - break; + return WEBVIEW; case WidgetType.EDITTEXT: - widgetType = EDITTEXT; - break; + return EDITTEXT; case WidgetType.EDIT_WEBVIEW: - widgetType = EDIT_WEBVIEW; - break; + return EDIT_WEBVIEW; + case WidgetType.UNSELECTABLE_TEXTVIEW: + return UNSELECTABLE_TEXTVIEW; + case WidgetType.CUSTOM_TEXTVIEW: + return CUSTOM_TEXTVIEW; + case WidgetType.CUSTOM_EDITTEXT: + return CUSTOM_EDITTEXT; + case WidgetType.CUSTOM_UNSELECTABLE_TEXTVIEW: + return CUSTOM_UNSELECTABLE_TEXTVIEW; default: - widgetType = UNKNOWN; + return UNKNOWN; } - final String version = event == null + } + + private String getModelName(@Nullable SelectionEvent event) { + return event == null ? SelectionEvent.NO_VERSION_TAG : Objects.toString(event.mVersionTag, SelectionEvent.NO_VERSION_TAG); - return String.format("%s/%s", widgetType, version); } private static String createSessionId() { return UUID.randomUUID().toString(); } - private static int clamp(int val) { - return Math.max(Math.min(val, Short.MAX_VALUE), Short.MIN_VALUE); - } - private static void debugLog(LogMaker log) { if (!DEBUG_LOG_ENABLED) return; - final String tag = Objects.toString(log.getTaggedData(VERSION_TAG), "tag"); + final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN); + final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), ""); + final String widget = widgetVersion.isEmpty() + ? widgetType : widgetType + "-" + widgetVersion; final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO)); if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) { String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), ""); sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1); - Log.d(LOG_TAG, String.format("New selection session: %s(%s)", tag, sessionId)); + Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId)); } + final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN); + final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN); final String type = getLogTypeString(log.getType()); - final String subType = getLogSubTypeString(log.getSubtype()); - - final int smartIndices = Integer.parseInt( - Objects.toString(log.getTaggedData(SMART_INDICES), ZERO)); - final int smartStart = (short) ((smartIndices & 0xffff0000) >> 16); - final int smartEnd = (short) (smartIndices & 0xffff); - - final int eventIndices = Integer.parseInt( - Objects.toString(log.getTaggedData(EVENT_INDICES), ZERO)); - final int eventStart = (short) ((eventIndices & 0xffff0000) >> 16); - final int eventEnd = (short) (eventIndices & 0xffff); - - Log.d(LOG_TAG, String.format("%2d: %s/%s, context=%d,%d - old=%d,%d (%s)", - index, type, subType, eventStart, eventEnd, smartStart, smartEnd, tag)); + final int smartStart = Integer.parseInt( + Objects.toString(log.getTaggedData(SMART_START), ZERO)); + final int smartEnd = Integer.parseInt( + Objects.toString(log.getTaggedData(SMART_END), ZERO)); + final int eventStart = Integer.parseInt( + Objects.toString(log.getTaggedData(EVENT_START), ZERO)); + final int eventEnd = Integer.parseInt( + Objects.toString(log.getTaggedData(EVENT_END), ZERO)); + + Log.d(LOG_TAG, String.format("%2d: %s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)", + index, type, entity, eventStart, eventEnd, smartStart, smartEnd, widget, model)); } /** @@ -369,12 +358,12 @@ public final class SmartSelectionEventTracker { /** * Use this to specify an indeterminate positive index. */ - public static final int OUT_OF_BOUNDS = Short.MAX_VALUE; + public static final int OUT_OF_BOUNDS = Integer.MAX_VALUE; /** * Use this to specify an indeterminate negative index. */ - public static final int OUT_OF_BOUNDS_NEGATIVE = Short.MIN_VALUE; + public static final int OUT_OF_BOUNDS_NEGATIVE = Integer.MIN_VALUE; private static final String NO_VERSION_TAG = ""; |