diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 9dd524f84..caee3a025 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -24,7 +24,7 @@ jobs: SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.13.0 + uses: styfle/cancel-workflow-action@0.13.1 with: access_token: ${{ github.token }} diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c0927d67d..babf90639 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -23,7 +23,7 @@ ******************************************************************************/ plugins { - id("com.android.application") + alias(libs.plugins.android.application) } android { diff --git a/app/src/main/assets/samples/big_sample.txt b/app/src/main/assets/samples/big_sample.txt index ba6ba63e4..c048d7987 100644 --- a/app/src/main/assets/samples/big_sample.txt +++ b/app/src/main/assets/samples/big_sample.txt @@ -17,19 +17,56 @@ package android.view; import static android.content.res.Resources.ID_NULL; +import static android.os.Trace.TRACE_TAG_APP; +import static android.os.Trace.TRACE_TAG_VIEW; +import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION; import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; +import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; +import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; +import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; +import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; +import static android.view.Surface.FRAME_RATE_COMPATIBILITY_AT_LEAST; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; +import static android.view.accessibility.Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS; +import static android.view.accessibility.Flags.FLAG_SUPPLEMENTAL_DESCRIPTION; +import static android.view.accessibility.Flags.removeChildHoverCheckForTouchExploration; +import static android.view.accessibility.Flags.supplementalDescription; +import static android.view.accessibility.Flags.supportMultipleLabeledby; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_UNKNOWN; import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH; import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE; +import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API; +import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY; +import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; +import static android.view.flags.Flags.calculateBoundsInParentFromBoundsInScreen; +import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout; +import static android.view.flags.Flags.sensitiveContentAppProtection; +import static android.view.flags.Flags.toolkitFrameRateAnimationBugfix25q1; +import static android.view.flags.Flags.toolkitFrameRateBySizeReadOnly; +import static android.view.flags.Flags.toolkitFrameRateDefaultNormalReadOnly; +import static android.view.flags.Flags.toolkitFrameRateSmallUsesPercentReadOnly; +import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly; +import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly; +import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision; +import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; +import static android.view.flags.Flags.toolkitViewgroupSetRequestedFrameRateApi; +import static android.view.flags.Flags.viewVelocityApi; +import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR; +import static android.view.inputmethod.Flags.initiationWithoutInputConnection; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; +import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS; +import static com.android.window.flags.Flags.FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW; +import static com.android.window.flags.Flags.reduceChangedExclusionRectsMsgs; import static java.lang.Math.max; @@ -39,6 +76,7 @@ import android.annotation.AttrRes; import android.annotation.CallSuper; import android.annotation.ColorInt; import android.annotation.DrawableRes; +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.IdRes; import android.annotation.IntDef; @@ -54,6 +92,9 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UiContext; import android.annotation.UiThread; +import android.app.PendingIntent; +import android.app.jank.AppJankStats; +import android.app.jank.JankTracker; import android.compat.annotation.UnsupportedAppUsage; import android.content.AutofillOptions; import android.content.ClipData; @@ -61,10 +102,17 @@ import android.content.ClipDescription; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; +import android.content.IntentSender; import android.content.res.ColorStateList; +import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; +import android.credentials.CredentialManager; +import android.credentials.CredentialOption; +import android.credentials.GetCredentialException; +import android.credentials.GetCredentialRequest; +import android.credentials.GetCredentialResponse; import android.graphics.Bitmap; import android.graphics.BlendMode; import android.graphics.Canvas; @@ -90,22 +138,27 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.hardware.display.DisplayManagerGlobal; +import android.hardware.input.InputManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.os.OutcomeReceiver; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; +import android.service.credentials.CredentialProviderService; import android.sysprop.DisplayProperties; import android.text.InputType; import android.text.TextUtils; +import android.util.ArraySet; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.util.FloatProperty; import android.util.LayoutDirection; import android.util.Log; @@ -118,6 +171,7 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.util.StateSet; import android.util.SuperNotCalledException; +import android.util.TimeUtils; import android.util.TypedValue; import android.view.AccessibilityIterators.CharacterTextSegmentIterator; import android.view.AccessibilityIterators.ParagraphTextSegmentIterator; @@ -151,6 +205,7 @@ import android.view.displayhash.DisplayHashManager; import android.view.displayhash.DisplayHashResultCallback; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; import android.view.inspector.InspectableProperty; import android.view.inspector.InspectableProperty.EnumEntry; import android.view.inspector.InspectableProperty.FlagEntry; @@ -160,7 +215,6 @@ import android.view.translation.ViewTranslationCallback; import android.view.translation.ViewTranslationRequest; import android.view.translation.ViewTranslationResponse; import android.widget.Checkable; -import android.widget.FrameLayout; import android.widget.ScrollBarDrawable; import android.window.OnBackInvokedDispatcher; @@ -184,6 +238,7 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -193,6 +248,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; @@ -310,7 +366,7 @@ import java.util.function.Predicate; * * *
{@link #onKeyDown(int, KeyEvent)}{@link #onTouchEvent(MotionEvent)}{@link #onGenericMotionEvent(MotionEvent)}{@link #onHoverEvent(MotionEvent)}The modified insets changed by {@link #onApplyWindowInsets} were passed to the @@ -1051,7 +1137,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static final int FOCUSABLE_MASK = 0x00000011; /** - * This view will adjust its padding to fit sytem windows (e.g. status bar) + * This view will adjust its padding to fit system windows (e.g. status bar) */ private static final int FITS_SYSTEM_WINDOWS = 0x00000002; @@ -1292,6 +1378,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // TODO(229765029): unhide this for UI toolkit public static final String AUTOFILL_HINT_PASSWORD_AUTO = "passwordAuto"; + /** + * Hint indicating that the developer intends to fill this view with output from + * CredentialManager. + * + * @hide + */ + public static final String AUTOFILL_HINT_CREDENTIAL_MANAGER = "credential"; + /** * Hints for the autofill services that describes the content of the view. */ @@ -1552,11 +1646,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int DISABLED = 0x00000020; - /** - * Mask for use with setFlags indicating bits used for indicating whether - * this view is enabled - * {@hide} - */ + /** + * Mask for use with setFlags indicating bits used for indicating whether + * this view is enabled + * {@hide} + */ static final int ENABLED_MASK = 0x00000020; /** @@ -1883,6 +1977,61 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int TOOLTIP = 0x40000000; + /** @hide */ + @IntDef(prefix = { "CONTENT_SENSITIVITY_" }, value = { + CONTENT_SENSITIVITY_AUTO, + CONTENT_SENSITIVITY_SENSITIVE, + CONTENT_SENSITIVITY_NOT_SENSITIVE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ContentSensitivity {} + + /** + * Content sensitivity is determined by the framework. The framework uses a heuristic to + * determine if this view displays sensitive content. + * Autofill hints i.e. {@link #getAutofillHints()} are used in the heuristic + * to determine if this view should be considered as a sensitive view. + *
+ * {@link #AUTOFILL_HINT_USERNAME}, + * {@link #AUTOFILL_HINT_PASSWORD}, + * {@link #AUTOFILL_HINT_CREDIT_CARD_NUMBER}, + * {@link #AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE}, + * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE}, + * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY}, + * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH}, + * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR} + * are considered sensitive hints by the framework, and the list may include more hints + * in the future. + * + *
The window hosting a sensitive view will be marked as secure during an active media + * projection session. This would be equivalent to applying + * {@link android.view.WindowManager.LayoutParams#FLAG_SECURE} to the window. + * + * @see #getContentSensitivity() + */ + @FlaggedApi(FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API) + public static final int CONTENT_SENSITIVITY_AUTO = 0x0; + + /** + * The view displays sensitive content. + * + *
The window hosting a sensitive view will be marked as secure during an active media + * projection session. This would be equivalent to applying + * {@link android.view.WindowManager.LayoutParams#FLAG_SECURE} to the window. + * + * @see #getContentSensitivity() + */ + @FlaggedApi(FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API) + public static final int CONTENT_SENSITIVITY_SENSITIVE = 0x1; + + /** + * The view doesn't display sensitive content. + * + * @see #getContentSensitivity() + */ + @FlaggedApi(FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API) + public static final int CONTENT_SENSITIVITY_NOT_SENSITIVE = 0x2; + /** @hide */ @IntDef(flag = true, prefix = { "FOCUSABLES_" }, value = { FOCUSABLES_ALL, @@ -2254,6 +2403,95 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET; + /** + * This indicates that the frame rate category was chosen for an unknown reason. + * @hide + */ + public static final int FRAME_RATE_CATEGORY_REASON_UNKNOWN = 0x0000_0000; + + /** + * This indicates that the frame rate category was chosen because it was a small area update. + * @hide + */ + public static final int FRAME_RATE_CATEGORY_REASON_SMALL = 0x0100_0000; + + /** + * This indicates that the frame rate category was chosen because it was an intermittent update. + * @hide + */ + public static final int FRAME_RATE_CATEGORY_REASON_INTERMITTENT = 0x0200_0000; + + /** + * This indicates that the frame rate category was chosen because it was a large View. + * @hide + */ + public static final int FRAME_RATE_CATEGORY_REASON_LARGE = 0x03000000; + + /** + * This indicates that the frame rate category was chosen because it was requested. + * @hide + */ + public static final int FRAME_RATE_CATEGORY_REASON_REQUESTED = 0x0400_0000; + + /** + * This indicates that the frame rate category was chosen because an invalid frame rate was + * requested. + * @hide + */ + public static final int FRAME_RATE_CATEGORY_REASON_INVALID = 0x0500_0000; + + /** + * This indicates that the frame rate category was chosen because the view has a velocity + * @hide + */ + public static final int FRAME_RATE_CATEGORY_REASON_VELOCITY = 0x0600_0000; + + /** + * This indicates that the frame rate category was chosen because it is currently boosting. + * @hide + */ + public static final int FRAME_RATE_CATEGORY_REASON_BOOST = 0x0800_0000; + + /** + * This indicates that the frame rate category was chosen because it is currently having + * touch boost. + * @hide + */ + public static final int FRAME_RATE_CATEGORY_REASON_TOUCH = 0x0900_0000; + + /** + * This indicates that the frame rate category was chosen because it is currently having + * touch boost. + * @hide + */ + public static final int FRAME_RATE_CATEGORY_REASON_CONFLICTED = 0x0A00_0000; + + private static final int FRAME_RATE_CATEGORY_REASON_MASK = 0xFFFF_0000; + + /** + * @hide + */ + protected static boolean sToolkitSetFrameRateReadOnlyFlagValue; + private static boolean sToolkitMetricsForFrameRateDecisionFlagValue; + private static final boolean sToolkitFrameRateDefaultNormalReadOnlyFlagValue = + toolkitFrameRateDefaultNormalReadOnly(); + private static final boolean sToolkitFrameRateBySizeReadOnlyFlagValue = + toolkitFrameRateBySizeReadOnly(); + private static final boolean sToolkitFrameRateSmallUsesPercentReadOnlyFlagValue = + toolkitFrameRateSmallUsesPercentReadOnly(); + private static final boolean sToolkitFrameRateViewEnablingReadOnlyFlagValue = + toolkitFrameRateViewEnablingReadOnly(); + private static boolean sToolkitFrameRateVelocityMappingReadOnlyFlagValue = + toolkitFrameRateVelocityMappingReadOnly(); + private static boolean sToolkitFrameRateAnimationBugfix25q1FlagValue = + toolkitFrameRateAnimationBugfix25q1(); + private static boolean sToolkitViewGroupFrameRateApiFlagValue = + toolkitViewgroupSetRequestedFrameRateApi(); + + // Used to set frame rate compatibility. + @Surface.FrameRateCompatibility int mFrameRateCompatibility = + FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; + static { EMPTY_STATE_SET = StateSet.get(0); @@ -2335,6 +2573,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED| StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED); + + sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly(); + sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision(); + sCalculateBoundsInParentFromBoundsInScreenFlagValue = + calculateBoundsInParentFromBoundsInScreen(); + sUseMeasureCacheDuringForceLayoutFlagValue = enableUseMeasureCacheDuringForceLayout(); } /** @@ -3074,6 +3318,44 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int IMPORTANT_FOR_ACCESSIBILITY_DEFAULT = IMPORTANT_FOR_ACCESSIBILITY_AUTO; + /** + * Automatically determine whether the view should only allow interactions from + * {@link android.accessibilityservice.AccessibilityService}s with the + * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property + * set to true. + * + *
+ * Accessibility interactions from services without {@code isAccessibilityTool} set to true are + * disallowed for any of the following conditions: + *
* Use with {@link #setAccessibilityLiveRegion(int)}. */ public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 0x00000001; /** - * Live region mode specifying that accessibility services should interrupt - * ongoing speech to immediately announce changes to this view. + * Live region mode specifying that accessibility services should immediately notify users of + * changes to this view. For example, a screen reader may interrupt ongoing speech to + * immediately announce these changes. *
* Use with {@link #setAccessibilityLiveRegion(int)}. */ @@ -3532,6 +3815,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE * 1 PFLAG4_DRAG_A11Y_STARTED * 1 PFLAG4_AUTO_HANDWRITING_INITIATION_ENABLED + * 1 PFLAG4_IMPORTANT_FOR_CREDENTIAL_MANAGER + * 1 PFLAG4_TRAVERSAL_TRACING_ENABLED + * 1 PFLAG4_RELAYOUT_TRACING_ENABLED + * 1 PFLAG4_ROTARY_HAPTICS_DETERMINED + * 1 PFLAG4_ROTARY_HAPTICS_ENABLED + * 1 PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT + * 1 PFLAG4_ROTARY_HAPTICS_WAITING_FOR_SCROLL_EVENT + * 11 PFLAG4_CONTENT_SENSITIVITY_MASK + * 1 PFLAG4_IS_COUNTED_AS_SENSITIVE + * 1 PFLAG4_HAS_DRAWN + * 1 PFLAG4_HAS_MOVED + * 1 PFLAG4_HAS_VIEW_PROPERTY_INVALIDATION + * 1 PFLAG4_FORCED_OVERRIDE_FRAME_RATE + * 1 PFLAG4_SELF_REQUESTED_FRAME_RATE * |-------|-------|-------|-------| */ @@ -3612,6 +3909,87 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Indicates that the view enables auto handwriting initiation. */ private static final int PFLAG4_AUTO_HANDWRITING_ENABLED = 0x000010000; + + /** + * Indicates that the view is important for Credential Manager. + */ + private static final int PFLAG4_IMPORTANT_FOR_CREDENTIAL_MANAGER = 0x000020000; + + /** + * When set, measure and layout passes of this view will be logged with {@link Trace}, so we + * can better debug jank due to complex view hierarchies. + */ + private static final int PFLAG4_TRAVERSAL_TRACING_ENABLED = 0x000040000; + + /** + * When set, emits a {@link Trace} instant event and stacktrace every time a requestLayout of + * this class happens. + */ + private static final int PFLAG4_RELAYOUT_TRACING_ENABLED = 0x000080000; + + /** Indicates if rotary scroll haptics support for the view has been determined. */ + private static final int PFLAG4_ROTARY_HAPTICS_DETERMINED = 0x100000; + + /** + * Indicates if rotary scroll haptics is enabled for this view. + * The source of truth for this info is a ViewConfiguration API; this bit only caches the value. + */ + private static final int PFLAG4_ROTARY_HAPTICS_ENABLED = 0x200000; + + /** Indicates if there has been a scroll event since the last rotary input. */ + private static final int PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT = 0x400000; + + /** + * Indicates if there has been a rotary input that may generate a scroll event. + * This flag is important so that a scroll event can be properly attributed to a rotary input. + */ + private static final int PFLAG4_ROTARY_HAPTICS_WAITING_FOR_SCROLL_EVENT = 0x800000; + + private static final int PFLAG4_CONTENT_SENSITIVITY_SHIFT = 24; + + /** + * Mask for obtaining the bits which specify how to determine whether a view + * displays sensitive content or not. + */ + private static final int PFLAG4_CONTENT_SENSITIVITY_MASK = + (CONTENT_SENSITIVITY_AUTO | CONTENT_SENSITIVITY_SENSITIVE + | CONTENT_SENSITIVITY_NOT_SENSITIVE) << PFLAG4_CONTENT_SENSITIVITY_SHIFT; + + /** + * Whether this view has been counted as a sensitive view or not. + * + * @see AttachInfo#mSensitiveViewsCount + */ + private static final int PFLAG4_IS_COUNTED_AS_SENSITIVE = 0x4000000; + + /** + * Whether this view has been drawn once with updateDisplayListIfDirty() or not. + * Used by VRR to for quick detection of scrolling. + */ + private static final int PFLAG4_HAS_DRAWN = 0x8000000; + + /** + * Whether this view has been moved with either setTranslationX/Y or setLeft/Top. + * Used by VRR to for quick detection of scrolling. + */ + private static final int PFLAG4_HAS_MOVED = 0x10000000; + + /** + * Whether the invalidateViewProperty is involked at current frame. + */ + private static final int PFLAG4_HAS_VIEW_PROPERTY_INVALIDATION = 0x20000000; + + /** + * When set, this indicates whether the frame rate of the children should be + * forcibly overridden, even if it has been explicitly configured by a user request. + */ + private static final int PFLAG4_FORCED_OVERRIDE_FRAME_RATE = 0x40000000; + + /** + * When set, this indicates that the frame rate is configured based on a user request. + */ + private static final int PFLAG4_SELF_REQUESTED_FRAME_RATE = 0x80000000; + /* End of masks for mPrivateFlags4 */ /** @hide */ @@ -4395,6 +4773,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.ExportedProperty(category = "layout") @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) protected int mLeft; + /** + * The mLeft from the previous frame. Used for detecting movement for purposes of variable + * refresh rate. + */ + private int mLastFrameLeft; /** * The distance in pixels from the left edge of this view's parent * to the right edge of this view. @@ -4411,6 +4794,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.ExportedProperty(category = "layout") @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) protected int mTop; + /** + * The mTop from the previous frame. Used for detecting movement for purposes of variable + * refresh rate. + */ + private int mLastFrameTop; /** * The distance in pixels from the top edge of this view's parent * to the bottom edge of this view. @@ -4474,6 +4862,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @UnsupportedAppUsage protected int mPaddingBottom; + /** + * The amount of pixel offset applied to the left edge of this view's handwriting bounds. + */ + private float mHandwritingBoundsOffsetLeft; + + /** + * The amount of pixel offset applied to the top edge of this view's handwriting bounds. + */ + private float mHandwritingBoundsOffsetTop; + + /** + * The amount of pixel offset applied to the right edge of this view's handwriting bounds. + */ + private float mHandwritingBoundsOffsetRight; + + /** + * The amount of pixel offset applied to the bottom edge of this view's handwriting bounds. + */ + private float mHandwritingBoundsOffsetBottom; + /** * The layout insets in pixels, that is the distance in pixels between the * visible edges of this view its bounds. @@ -4490,12 +4898,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private CharSequence mContentDescription; + /** + * Brief supplemental information for view and is primarily used for accessibility support. + */ + private CharSequence mSupplementalDescription; + /** * If this view represents a distinct part of the window, it can have a title that labels the * area. */ private CharSequence mAccessibilityPaneTitle; + /** + * Describes whether this view should only allow interactions from + * {@link android.accessibilityservice.AccessibilityService}s with the + * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property + * set to true. + */ + private int mExplicitAccessibilityDataSensitive = ACCESSIBILITY_DATA_SENSITIVE_AUTO; + /** Used to calculate and cache {@link #isAccessibilityDataSensitive()}. */ + private int mInferredAccessibilityDataSensitive = ACCESSIBILITY_DATA_SENSITIVE_AUTO; + /** * Specifies the id of a view for which this view serves as a label for * accessibility purposes. @@ -4982,6 +5405,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private boolean mHoveringTouchDelegate = false; + // These two fields are set if the view is a handwriting delegator. + private Runnable mHandwritingDelegatorCallback; + private String mAllowedHandwritingDelegatePackageName; + + // These three fields are set if the view is a handwriting delegate. + private boolean mIsHandwritingDelegate; + private String mAllowedHandwritingDelegatorPackageName; + private @InputMethodManager.HandwritingDelegateFlags int mHandwritingDelegateFlags; + /** * Solid color to use as a background when creating the drawing cache. Enables * the cache to use 16 bit bitmaps instead of 32 bit. @@ -5093,6 +5525,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public static final int DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION = 1 << 11; + /** + * Flag indicating that a drag can cross window boundaries (within the same application). When + * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called + * with this flag set, only visible windows belonging to the same application (ie. share the + * same UID) with targetSdkVersion >= {@link android.os.Build.VERSION_CODES#N API 24} will be + * able to participate in the drag operation and receive the dragged content. + * + * If both DRAG_FLAG_GLOBAL_SAME_APPLICATION and DRAG_FLAG_GLOBAL are set, then + * DRAG_FLAG_GLOBAL_SAME_APPLICATION takes precedence and the drag will only go to visible + * windows from the same application. + */ + @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) + public static final int DRAG_FLAG_GLOBAL_SAME_APPLICATION = 1 << 12; + + /** + * Flag indicating that an unhandled drag should be delegated to the system to be started if no + * visible window wishes to handle the drop. When using this flag, the caller must provide + * ClipData with an Item that contains an immutable IntentSender to an activity to be launched + * (not a broadcast, service, etc). See + * {@link ClipData.Item.Builder#setIntentSender(IntentSender)}. + * + * The system can decide to launch the intent or not based on factors like the current screen + * size or windowing mode. If the system does not launch the intent, it will be canceled via the + * normal drag and drop flow. + */ + @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) + public static final int DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG = 1 << 13; + + /** + * Flag indicating that this drag will result in the caller activity's task to be hidden for the + * duration of the drag, which means that the source activity will not receive drag events for + * the current drag gesture. Only the current + * {@link android.service.voice.VoiceInteractionService} may use this flag. + */ + @FlaggedApi(FLAG_SUPPORTS_DRAG_ASSISTANT_TO_MULTIWINDOW) + public static final int DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START = 1 << 14; + /** * Vertical scroll factor cached by {@link #getVerticalScrollFactor}. */ @@ -5191,11 +5660,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @Retention(RetentionPolicy.SOURCE) public @interface LayerType {} - @ViewDebug.ExportedProperty(category = "drawing", mapping = { - @ViewDebug.IntToString(from = LAYER_TYPE_NONE, to = "NONE"), - @ViewDebug.IntToString(from = LAYER_TYPE_SOFTWARE, to = "SOFTWARE"), - @ViewDebug.IntToString(from = LAYER_TYPE_HARDWARE, to = "HARDWARE") - }) int mLayerType = LAYER_TYPE_NONE; Paint mLayerPaint; @@ -5287,7 +5751,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * The pointer icon when the mouse hovers on this view. The default is null. */ - private PointerIcon mPointerIcon; + private PointerIcon mMousePointerIcon; /** * @hide @@ -5339,10 +5803,49 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @Nullable private ViewTranslationCallback mViewTranslationCallback; + private float mFrameContentVelocity = -1; + @Nullable private ViewTranslationResponse mViewTranslationResponse; + /** + * The size in DP that is considered small for VRR purposes, if square. + */ + private static final float FRAME_RATE_SQUARE_SMALL_SIZE_DP = 40f; + + /** + * The size in DP that is considered small for VRR purposes in the narrow dimension. Used for + * narrow Views like a progress bar. + */ + private static final float FRAME_RATE_NARROW_SIZE_DP = 10f; + + /** + * A threshold value to determine the frame rate category of the View based on the size. + */ + private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f; + + static final float MAX_FRAME_RATE = 120; + + // The preferred frame rate of the view that is mainly used for + // touch boosting, view velocity handling, and TextureView. + private float mPreferredFrameRate = REQUESTED_FRAME_RATE_CATEGORY_DEFAULT; + + private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + + @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = Float.NaN; + @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public static final float REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE = -1; + @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -2; + @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -3; + @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) + public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -4; + + private int mSizeBasedFrameRateCategoryAndReason; + /** * Simple constructor to use when creating a view from code. * @@ -5361,7 +5864,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) | (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) | (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT); - mPrivateFlags4 = PFLAG4_AUTO_HANDWRITING_ENABLED; final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = configuration.getScaledTouchSlop(); @@ -5375,20 +5877,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (!sCompatibilityDone && context != null) { final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; - // Older apps may need this compatibility hack for measurement. - sUseBrokenMakeMeasureSpec = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1; - - // Older apps expect onMeasure() to always be called on a layout pass, regardless - // of whether a layout was requested on that View. - sIgnoreMeasureCache = targetSdkVersion < Build.VERSION_CODES.KITKAT; - - // In M and newer, our widgets can pass a "hint" value in the size - // for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers - // know what the expected parent size is going to be, so e.g. list items can size - // themselves at 1/3 the size of their container. It breaks older apps though, - // specifically apps that use some popular open source libraries. - sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < Build.VERSION_CODES.M; - // Old versions of the platform would give different results from // LinearLayout measurement passes using EXACTLY and non-EXACTLY // modes, so we always need to run an additional EXACTLY pass. @@ -5565,8 +6053,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, boolean leftPaddingDefined = false; boolean rightPaddingDefined = false; - final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; - // Set default values. viewFlagValues |= FOCUSABLE_AUTO; viewFlagMasks |= FOCUSABLE_AUTO; @@ -5787,11 +6273,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, break; //noinspection deprecation case R.styleable.View_fadingEdge: - if (targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - // Ignore the attribute starting with ICS - break; - } - // With builds < ICS, fall through and apply fading edges + break; case R.styleable.View_requiresFadingEdge: final int fadingEdge = a.getInt(attr, FADING_EDGE_NONE); if (fadingEdge != FADING_EDGE_NONE) { @@ -5889,6 +6371,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setImportantForAccessibility(a.getInt(attr, IMPORTANT_FOR_ACCESSIBILITY_DEFAULT)); break; + case R.styleable.View_accessibilityDataSensitive: + setAccessibilityDataSensitive(a.getInt(attr, + ACCESSIBILITY_DATA_SENSITIVE_AUTO)); + break; case R.styleable.View_accessibilityLiveRegion: setAccessibilityLiveRegion(a.getInt(attr, ACCESSIBILITY_LIVE_REGION_DEFAULT)); break; @@ -5925,35 +6411,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback, PROVIDER_BACKGROUND)); break; case R.styleable.View_foreground: - if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) { - setForeground(a.getDrawable(attr)); - } + setForeground(a.getDrawable(attr)); break; case R.styleable.View_foregroundGravity: - if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) { - setForegroundGravity(a.getInt(attr, Gravity.NO_GRAVITY)); - } + setForegroundGravity(a.getInt(attr, Gravity.NO_GRAVITY)); break; case R.styleable.View_foregroundTintMode: - if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) { - setForegroundTintBlendMode( - Drawable.parseBlendMode(a.getInt(attr, -1), - null)); - } + setForegroundTintBlendMode( + Drawable.parseBlendMode(a.getInt(attr, -1), + null)); break; case R.styleable.View_foregroundTint: - if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) { - setForegroundTintList(a.getColorStateList(attr)); - } + setForegroundTintList(a.getColorStateList(attr)); break; case R.styleable.View_foregroundInsidePadding: - if (targetSdkVersion >= Build.VERSION_CODES.M || this instanceof FrameLayout) { - if (mForegroundInfo == null) { - mForegroundInfo = new ForegroundInfo(); - } - mForegroundInfo.mInsidePadding = a.getBoolean(attr, - mForegroundInfo.mInsidePadding); + if (mForegroundInfo == null) { + mForegroundInfo = new ForegroundInfo(); } + mForegroundInfo.mInsidePadding = a.getBoolean(attr, + mForegroundInfo.mInsidePadding); break; case R.styleable.View_scrollIndicators: final int scrollIndicators = @@ -6039,6 +6515,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setImportantForContentCapture(a.getInt(attr, IMPORTANT_FOR_CONTENT_CAPTURE_AUTO)); } + break; + case R.styleable.View_isCredential: + if (a.peekValue(attr) != null) { + setIsCredential(a.getBoolean(attr, false)); + } + break; case R.styleable.View_defaultFocusHighlightEnabled: if (a.peekValue(attr) != null) { setDefaultFocusHighlightEnabled(a.getBoolean(attr, true)); @@ -6076,8 +6558,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setPreferKeepClear(a.getBoolean(attr, false)); break; case R.styleable.View_autoHandwritingEnabled: - setAutoHandwritingEnabled(a.getBoolean(attr, true)); + setAutoHandwritingEnabled(a.getBoolean(attr, false)); + break; + case R.styleable.View_handwritingBoundsOffsetLeft: + mHandwritingBoundsOffsetLeft = a.getDimension(attr, 0); break; + case R.styleable.View_handwritingBoundsOffsetTop: + mHandwritingBoundsOffsetTop = a.getDimension(attr, 0); + break; + case R.styleable.View_handwritingBoundsOffsetRight: + mHandwritingBoundsOffsetRight = a.getDimension(attr, 0); + break; + case R.styleable.View_handwritingBoundsOffsetBottom: + mHandwritingBoundsOffsetBottom = a.getDimension(attr, 0); + break; + case R.styleable.View_contentSensitivity: + setContentSensitivity(a.getInt(attr, CONTENT_SENSITIVITY_AUTO)); + break; + default: { + if (supplementalDescription()) { + if (attr == com.android.internal.R.styleable.View_supplementalDescription) { + setSupplementalDescription(a.getString(attr)); + } + } + } } } @@ -6537,6 +7041,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, out.append(mRight); out.append(','); out.append(mBottom); + appendId(out); + if (mAutofillId != null) { + out.append(" aid="); out.append(mAutofillId); + } + out.append("}"); + return out.toString(); + } + + void appendId(StringBuilder out) { final int id = getId(); if (id != NO_ID) { out.append(" #"); @@ -6568,11 +7081,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } } - if (mAutofillId != null) { - out.append(" aid="); out.append(mAutofillId); - } - out.append("}"); - return out.toString(); } /** @@ -6656,12 +7164,81 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Returns the size of the horizontal faded edges used to indicate that more - * content in this view is visible. + * Clears the request and callback previously set + * through {@link View#setPendingCredentialRequest}. + * Once this API is invoked, there will be no request fired to {@link CredentialManager} + * on future view focus events. * - * @return The size in pixels of the horizontal faded edge or 0 if horizontal - * faded edges are not enabled for this view. - * @attr ref android.R.styleable#View_fadingEdgeLength + * @see #setPendingCredentialRequest + */ + @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) + public void clearPendingCredentialRequest() { + if (Log.isLoggable(AUTOFILL_LOG_TAG, Log.VERBOSE)) { + Log.v(AUTOFILL_LOG_TAG, "clearPendingCredentialRequest called"); + } + mViewCredentialHandler = null; + } + + /** + * Sets a {@link CredentialManager} request to retrieve credentials, when the user focuses + * on this given view. + * + * When this view is focused, the given {@code request} will be fired to + * {@link CredentialManager}, which will fetch content from all + * {@link android.service.credentials.CredentialProviderService} services on the + * device, and then display credential options to the user on a relevant UI + * (dropdown, keyboard suggestions etc.). + * + * When the user selects a credential, the final {@link GetCredentialResponse} will be + * propagated to the given {@code callback}. Developers are expected to handle the response + * programmatically and perform a relevant action, e.g. signing in the user. + * + *
For details on how to build a Credential Manager request, please see + * {@link GetCredentialRequest}. + * + *
This API should be called at any point before the user focuses on the view, e.g. during
+ * {@code onCreate} of an Activity.
+ *
+ * @param request the request to be fired when this view is entered
+ * @param callback to be invoked when either a response or an exception needs to be
+ * propagated for the given view
+ */
+ @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
+ public void setPendingCredentialRequest(@NonNull GetCredentialRequest request,
+ @NonNull OutcomeReceiver
+ * When transitioning from one Activity to another, instead of using
+ * {@code setAccessibilityPaneTitle()}, set a descriptive title for its window by using
+ * {@code android:label}
+ * for the matching Activity entry in your application's manifest or updating the title at
+ * runtime with {@link android.app.Activity#setTitle(CharSequence)}.
+ *
+ *
+ *
* @param accessibilityPaneTitle The pane's title. Setting to {@code null} indicates that this
* View is not a pane.
*
@@ -8331,17 +8959,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT}
- * {@link AccessibilityEvent} to suggest that an accessibility service announce the
- * specified text to its users.
- *
- * Note: The event generated with this API carries no semantic meaning, and is appropriate only
- * in exceptional situations. Apps can generally achieve correct behavior for accessibility by
- * accurately supplying the semantics of their UI.
- * They should not need to specify what exactly is announced to users.
+ * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT} {@link
+ * AccessibilityEvent} to suggest that an accessibility service announce the specified text to
+ * its users.
+ *
+ * Note: The event generated with this API carries no semantic meaning, and accessibility
+ * services may choose to ignore it. Apps that accurately supply accessibility with the
+ * semantics of their UI should not need to specify what exactly is announced.
+ *
+ * In general, do not attempt to generate announcements as confirmation message for simple
+ * actions like a button press. Label your controls concisely and precisely instead.
+ *
+ * To convey significant UI changes like window changes, use {@link
+ * android.app.Activity#setTitle(CharSequence)} and {@link
+ * #setAccessibilityPaneTitle(CharSequence)}.
*
+ * Use {@link #setAccessibilityLiveRegion(int)} to inform the user of changes to critical
+ * views within the user interface. These should still be used sparingly as they may generate
+ * announcements every time a View is updated.
+ *
+ * Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the
+ * user interface. While a live region may send different types of events generated by the view,
+ * state description will send {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events of
+ * type {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}.
+ *
+ * For notifying users about errors, such as in a login screen with text that displays an
+ * "incorrect password" notification, set {@link AccessibilityNodeInfo#setError(CharSequence)}
+ * and dispatch an {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event with a change
+ * type of {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR}, instead. Some widgets may
+ * expose methods that convey error states to accessibility automatically, such as {@link
+ * android.widget.TextView#setError(CharSequence)}, which manages these accessibility semantics
+ * and event dispatch for callers.
+ *
+ * @deprecated Use one of the methods described in the documentation above to semantically
+ * describe UI instead of using an announcement, as accessibility services may choose to
+ * ignore events dispatched with this method.
* @param text The announcement text.
*/
+ @FlaggedApi(FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS)
+ @Deprecated
public void announceForAccessibility(CharSequence text) {
if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) {
AccessibilityEvent event = AccessibilityEvent.obtain(
@@ -8460,15 +9116,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Dispatches an {@link AccessibilityEvent} to the {@link View} first and then
- * to its children for adding their text content to the event. Note that the
- * event text is populated in a separate dispatch path since we add to the
+ * Dispatches an {@link AccessibilityEvent} to the {@link View} to add the text content of the
+ * view and its children.
+ *
+ * Note: This method should only be used with event.setText().
+ * Avoid mutating other event state in this method. In general, put UI metadata in the node for
+ * services to easily query.
+ *
+ * Note that the event text is populated in a separate dispatch path
+ * ({@link #onPopulateAccessibilityEvent(AccessibilityEvent)}) since we add to the
* event not only the text of the source but also the text of all its descendants.
+ *
* A typical implementation will call
- * {@link #onPopulateAccessibilityEvent(AccessibilityEvent)} on the this view
+ * {@link #onPopulateAccessibilityEvent(AccessibilityEvent)} on this view
* and then call the {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}
- * on each child. Override this method if custom population of the event text
- * content is required.
+ * on each child or the first child that is visible. Override this method if custom population
+ * of the event text content is required.
+ *
*
* If an {@link AccessibilityDelegate} has been specified via calling
* {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
@@ -8476,6 +9160,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* is responsible for handling this call.
*
+ * If this view sets {@link #isAccessibilityDataSensitive()} then this view should only append
+ * sensitive information to an event that also sets
+ * {@link AccessibilityEvent#isAccessibilityDataSensitive()}.
+ *
* Note: Accessibility events of certain types are not dispatched for
* populating the event text via this method. For details refer to {@link AccessibilityEvent}.
*
+ * Note: This method should only be used with event.setText().
+ * Avoid mutating other event state in this method. Instead, follow the practices described in
+ * {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}. In general, put UI
+ * metadata in the node for services to easily query, than in transient events.
*
* Example: Adding formatted date string to an accessibility event in addition
* to the text added by the super implementation:
@@ -8731,7 +9423,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
@UnsupportedAppUsage
- public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+ @TestApi
+ public void getBoundsOnScreen(@NonNull Rect outRect, boolean clipToParent) {
if (mAttachInfo == null) {
return;
}
@@ -8739,6 +9432,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
getBoundsToScreenInternal(position, clipToParent);
outRect.set(Math.round(position.left), Math.round(position.top),
Math.round(position.right), Math.round(position.bottom));
+ // If "Sandboxing View Bounds APIs" override is enabled, applyViewBoundsSandboxingIfNeeded
+ // will sandbox outRect within window bounds.
+ mAttachInfo.mViewRootImpl.applyViewBoundsSandboxingIfNeeded(outRect);
}
/**
@@ -8757,11 +9453,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
outRect.set(position.left, position.top, position.right, position.bottom);
}
+ /**
+ * Gets the location of this view in window coordinates.
+ *
+ * @param outRect The output location
+ * @param clipToParent Whether to clip child bounds to the parent ones.
+ * @hide
+ */
+ public void getBoundsInWindow(Rect outRect, boolean clipToParent) {
+ if (mAttachInfo == null) {
+ return;
+ }
+ RectF position = mAttachInfo.mTmpTransformRect;
+ getBoundsToWindowInternal(position, clipToParent);
+ outRect.set(Math.round(position.left), Math.round(position.top),
+ Math.round(position.right), Math.round(position.bottom));
+ }
+
private void getBoundsToScreenInternal(RectF position, boolean clipToParent) {
position.set(0, 0, mRight - mLeft, mBottom - mTop);
mapRectFromViewToScreenCoords(position, clipToParent);
}
+ private void getBoundsToWindowInternal(RectF position, boolean clipToParent) {
+ position.set(0, 0, mRight - mLeft, mBottom - mTop);
+ mapRectFromViewToWindowCoords(position, clipToParent);
+ }
+
/**
* Map a rectangle from view-relative coordinates to screen-relative coordinates
*
@@ -8770,6 +9488,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
public void mapRectFromViewToScreenCoords(RectF rect, boolean clipToParent) {
+ mapRectFromViewToWindowCoords(rect, clipToParent);
+ rect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
+ }
+
+ /**
+ * Map a rectangle from view-relative coordinates to window-relative coordinates
+ *
+ * @param rect The rectangle to be mapped
+ * @param clipToParent Whether to clip child bounds to the parent ones.
+ * @hide
+ */
+ public void mapRectFromViewToWindowCoords(RectF rect, boolean clipToParent) {
if (!hasIdentityMatrix()) {
getMatrix().mapRect(rect);
}
@@ -8802,8 +9532,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
ViewRootImpl viewRootImpl = (ViewRootImpl) parent;
rect.offset(0, -viewRootImpl.mCurScrollY);
}
-
- rect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
}
/**
@@ -8987,6 +9715,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
structure.setAutofillType(autofillType);
structure.setAutofillHints(getAutofillHints());
structure.setAutofillValue(getAutofillValue());
+ structure.setIsCredential(isCredential());
+ }
+ if (getViewCredentialHandler() != null) {
+ structure.setPendingCredentialRequest(
+ getViewCredentialHandler().getRequest(),
+ getViewCredentialHandler().getCallback());
}
structure.setImportantForAutofill(getImportantForAutofill());
structure.setReceiveContentMimeTypes(getReceiveContentMimeTypes());
@@ -9004,8 +9738,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
while (parentGroup != null && !parentGroup.isImportantForAutofill()) {
- ignoredParentLeft += parentGroup.mLeft;
- ignoredParentTop += parentGroup.mTop;
+ ignoredParentLeft += parentGroup.mLeft - parentGroup.mScrollX;
+ ignoredParentTop += parentGroup.mTop - parentGroup.mScrollY;
viewParent = parentGroup.getParent();
if (viewParent instanceof View) {
@@ -9090,8 +9824,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final AccessibilityNodeInfo info = createAccessibilityNodeInfo();
structure.setChildCount(1);
final ViewStructure root = structure.newChild(0);
- populateVirtualStructure(root, provider, info, forAutofill);
- info.recycle();
+ if (info != null) {
+ populateVirtualStructure(root, provider, info, null, forAutofill);
+ info.recycle();
+ } else {
+ Log.w(AUTOFILL_LOG_TAG, "AccessibilityNodeInfo is null.");
+ }
}
}
@@ -9381,6 +10119,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+ /**
+ * @hide
+ */
+ public void onGetCredentialResponse(GetCredentialResponse response) {
+ if (getPendingCredentialCallback() == null) {
+ Log.w(AUTOFILL_LOG_TAG, "onGetCredentialResponse called but no callback found");
+ return;
+ }
+ getPendingCredentialCallback().onResult(response);
+ }
+
+ /**
+ * @hide
+ */
+ public void onGetCredentialException(String errorType, String errorMsg) {
+ if (getPendingCredentialCallback() == null) {
+ Log.w(AUTOFILL_LOG_TAG, "onGetCredentialException called but no callback found");
+ return;
+ }
+ getPendingCredentialCallback().onError(new GetCredentialException(errorType, errorMsg));
+ }
+
/**
* Gets the unique, logical identifier of this view in the activity, for autofill purposes.
*
@@ -9400,6 +10160,53 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return mAutofillId;
}
+ /**
+ * Returns the {@link GetCredentialRequest} associated with the view.
+ * If the return value is null, that means no request has been set
+ * on the view and no {@link CredentialManager} flow will be invoked
+ * when this view is focused. Traditioanl autofill flows will still
+ * work, autofilling content if applicable, from
+ * the active {@link android.service.autofill.AutofillService} on
+ * the device.
+ *
+ * See {@link #setPendingCredentialRequest} for more info.
+ *
+ * @return The credential request associated with this View.
+ */
+ @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
+ @Nullable
+ public final GetCredentialRequest getPendingCredentialRequest() {
+ if (mViewCredentialHandler == null) {
+ return null;
+ }
+ return mViewCredentialHandler.getRequest();
+ }
+
+
+ /**
+ * Returns the callback that has previously been set up on this view through
+ * the {@link #setPendingCredentialRequest} API.
+ * If the return value is null, that means no callback, or request, has been set
+ * on the view and no {@link CredentialManager} flow will be invoked
+ * when this view is focused. Traditioanl autofill flows will still
+ * work, and autofillable content will still be returned through the
+ * {@link #autofill(AutofillValue)} )} API.
+ *
+ * See {@link #setPendingCredentialRequest} for more info.
+ *
+ * @return The callback associated with this view that will be invoked on a response from
+ * {@link CredentialManager} .
+ */
+ @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
+ @Nullable
+ public final OutcomeReceiver Note: Setting the mode as {@link #IMPORTANT_FOR_AUTOFILL_NO} or
* {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS} does not guarantee the view (and its
- * children) will be always be considered not important; for example, when the user explicitly
- * makes an autofill request, all views are considered important. See
- * {@link #isImportantForAutofill()} for more details about how the View's importance for
- * autofill is used.
+ * children) will not be used for autofill purpose; for example, when the user explicitly
+ * makes an autofill request, all views are included in the ViewStructure, and starting in
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} the system uses other factors along
+ * with importance to determine the autofill behavior. See {@link #isImportantForAutofill()}
+ * for more details about how the View's importance for autofill is used.
*
* @param mode {@link #IMPORTANT_FOR_AUTOFILL_AUTO}, {@link #IMPORTANT_FOR_AUTOFILL_YES},
* {@link #IMPORTANT_FOR_AUTOFILL_NO}, {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS},
@@ -9662,21 +10470,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* When a view is considered important for autofill:
- * On the other hand, when a view is considered not important for autofill:
- * The behavior of importances depends on Android version:
+ * The window hosting a sensitive view will be marked as secure during an active media
+ * projection session. This would be equivalent to applying
+ * {@link android.view.WindowManager.LayoutParams#FLAG_SECURE} to the window.
+ *
+ * @param mode {@link #CONTENT_SENSITIVITY_AUTO}, {@link #CONTENT_SENSITIVITY_NOT_SENSITIVE}
+ * or {@link #CONTENT_SENSITIVITY_SENSITIVE}
+ */
+ @FlaggedApi(FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API)
+ public final void setContentSensitivity(@ContentSensitivity int mode) {
+ mPrivateFlags4 &= ~PFLAG4_CONTENT_SENSITIVITY_MASK;
+ mPrivateFlags4 |= ((mode << PFLAG4_CONTENT_SENSITIVITY_SHIFT)
+ & PFLAG4_CONTENT_SENSITIVITY_MASK);
+ if (sensitiveContentAppProtection()) {
+ updateSensitiveViewsCountIfNeeded(isAggregatedVisible());
+ }
+ }
+
+ /**
+ * Gets content sensitivity mode to determine whether this view displays sensitive content.
+ *
+ * See {@link #setContentSensitivity(int)} and
+ * {@link #isContentSensitive()} for more info about this mode.
+ *
+ * @return {@link #CONTENT_SENSITIVITY_AUTO} by default, or value passed to
+ * {@link #setContentSensitivity(int)}.
+ */
+ @FlaggedApi(FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API)
+ public @ContentSensitivity final int getContentSensitivity() {
+ return (mPrivateFlags4 & PFLAG4_CONTENT_SENSITIVITY_MASK)
+ >> PFLAG4_CONTENT_SENSITIVITY_SHIFT;
+ }
+
+ /**
+ * Returns whether this view displays sensitive content, based
+ * on the value explicitly set by {@link #setContentSensitivity(int)}.
+ *
+ * @return whether the view displays sensitive content.
+ *
+ * @see #setContentSensitivity(int)
+ * @see #CONTENT_SENSITIVITY_AUTO
+ * @see #CONTENT_SENSITIVITY_SENSITIVE
+ * @see #CONTENT_SENSITIVITY_NOT_SENSITIVE
+ */
+ @FlaggedApi(FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API)
+ public final boolean isContentSensitive() {
+ final int contentSensitivity = getContentSensitivity();
+ if (contentSensitivity == CONTENT_SENSITIVITY_SENSITIVE) {
+ return true;
+ } else if (contentSensitivity == CONTENT_SENSITIVITY_NOT_SENSITIVE) {
+ return false;
+ } else if (sensitiveContentAppProtection()) {
+ return SensitiveAutofillHintsHelper
+ .containsSensitiveAutofillHint(getAutofillHints());
+ }
+ return false;
+ }
+
+ /**
+ * Helper used to track sensitive views when they are added or removed from the window
+ * based on whether it's laid out and visible.
+ *
+ * This method is called from many places (visibility changed, view laid out, view attached
+ * or detached to/from window, etc...)
+ */
+ private void updateSensitiveViewsCountIfNeeded(boolean appeared) {
+ if (!sensitiveContentAppProtection() || mAttachInfo == null) {
+ return;
+ }
+
+ if (appeared && isContentSensitive()) {
+ if ((mPrivateFlags4 & PFLAG4_IS_COUNTED_AS_SENSITIVE) == 0) {
+ mPrivateFlags4 |= PFLAG4_IS_COUNTED_AS_SENSITIVE;
+ mAttachInfo.increaseSensitiveViewsCount();
+ }
+ } else {
+ if ((mPrivateFlags4 & PFLAG4_IS_COUNTED_AS_SENSITIVE) != 0) {
+ mPrivateFlags4 &= ~PFLAG4_IS_COUNTED_AS_SENSITIVE;
+ mAttachInfo.decreaseSensitiveViewsCount();
+ }
+ }
+ }
+
/**
* Gets the mode for determining whether this view is important for content capture.
*
@@ -9979,6 +10889,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
setNotifiedContentCaptureAppeared();
if (ai != null) {
+ makeParentImportantAndNotifyAppearedEventIfNeed();
ai.delayNotifyContentCaptureEvent(session, this, appeared);
} else {
if (DEBUG_CONTENT_CAPTURE) {
@@ -10006,6 +10917,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+ private void makeParentImportantAndNotifyAppearedEventIfNeed() {
+ // If view sent the appeared event to Content Capture, Content Capture also
+ // would like to receive its parents' appeared events. So checks its parents
+ // whether the appeared event is sent or not. If not, send the appeared event.
+ final ViewParent parent = getParent();
+ if (parent instanceof View) {
+ View p = ((View) parent);
+ if (p.getNotifiedContentCaptureAppeared()) {
+ return;
+ }
+ // Set important for content capture in the cache.
+ p.mPrivateFlags4 |= PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
+ p.notifyAppearedOrDisappearedForContentCaptureIfNeeded(/* appeared */ true);
+ }
+ }
+
private void setNotifiedContentCaptureAppeared() {
mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
@@ -10097,36 +11024,119 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return mContext.getSystemService(AutofillManager.class);
}
+ /**
+ * Check whether current activity / package is in autofill denylist.
+ *
+ * Called by viewGroup#populateChildrenForAutofill() to determine whether to include view in
+ * assist structure
+ */
+ final boolean isActivityDeniedForAutofillForUnimportantView() {
+ final AutofillManager afm = getAutofillManager();
+ if (afm == null) return false;
+ return afm.isActivityDeniedForAutofill();
+ }
+
+ /**
+ * Check whether current view matches autofillable heuristics
+ *
+ * Called by viewGroup#populateChildrenForAutofill() to determine whether to include view in
+ * assist structure
+ */
+ final boolean isMatchingAutofillableHeuristics() {
+ final AutofillManager afm = getAutofillManager();
+ if (afm == null) return false;
+ // check the flag to see if trigger fill request on not important views is enabled
+ return afm.isTriggerFillRequestOnUnimportantViewEnabled()
+ ? afm.isAutofillable(this) : false;
+ }
+
+ /**
+ * Returns whether the view is autofillable.
+ *
+ * @return whether the view is autofillable, and should send out autofill request to provider.
+ */
private boolean isAutofillable() {
- if (getAutofillType() == AUTOFILL_TYPE_NONE) return false;
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isAutofillable() entered.");
+ }
+ if (getAutofillType() == AUTOFILL_TYPE_NONE) {
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "getAutofillType() returns AUTOFILL_TYPE_NONE");
+ }
+ return false;
+ }
- if (!isImportantForAutofill()) {
- // View is not important for "regular" autofill, so we must check if Augmented Autofill
- // is enabled for the activity
- final AutofillOptions options = mContext.getAutofillOptions();
- if (options == null || !options.isAugmentedAutofillEnabled(mContext)) {
- return false;
+ final AutofillManager afm = getAutofillManager();
+ if (afm == null) {
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "AutofillManager is null");
}
- final AutofillManager afm = getAutofillManager();
- if (afm == null) return false;
- afm.notifyViewEnteredForAugmentedAutofill(this);
+ return false;
+ }
+
+ // Check whether view is not part of an activity. If it's not, return false.
+ if (getAutofillViewId() <= LAST_APP_AUTOFILL_ID) {
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "getAutofillViewId()<=LAST_APP_AUTOFILL_ID");
+ }
+ return false;
}
- return getAutofillViewId() > LAST_APP_AUTOFILL_ID;
+ // If view is important and filter important view flag is turned on, or view is not
+ // important and trigger fill request on not important view flag is turned on, then use
+ // AutofillManager.isAutofillable() to decide whether view is autofillable instead.
+ if ((isImportantForAutofill() && afm.isTriggerFillRequestOnFilteredImportantViewsEnabled())
+ || (!isImportantForAutofill()
+ && afm.isTriggerFillRequestOnUnimportantViewEnabled())) {
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill()
+ + "afm.isAutofillable(): " + afm.isAutofillable(this));
+ }
+ return afm.isAutofillable(this) ? true : notifyAugmentedAutofillIfNeeded(afm);
+ }
+
+ // If the previous condition is not met, fall back to the previous way to trigger fill
+ // request based on autofill importance instead.
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill());
+ }
+ return isImportantForAutofill() ? true : notifyAugmentedAutofillIfNeeded(afm);
+ }
+
+ private boolean notifyAugmentedAutofillIfNeeded(AutofillManager afm) {
+ final AutofillOptions options = mContext.getAutofillOptions();
+ if (options == null || !options.isAugmentedAutofillEnabled(mContext)) {
+ return false;
+ }
+ afm.notifyViewEnteredForAugmentedAutofill(this);
+ return true;
}
/** @hide */
public boolean canNotifyAutofillEnterExitEvent() {
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "canNotifyAutofillEnterExitEvent() entered. "
+ + " isAutofillable(): " + isAutofillable()
+ + " isAttachedToWindow(): " + isAttachedToWindow());
+ }
return isAutofillable() && isAttachedToWindow();
}
private void populateVirtualStructure(ViewStructure structure,
AccessibilityNodeProvider provider, AccessibilityNodeInfo info,
- boolean forAutofill) {
+ @Nullable AccessibilityNodeInfo parentInfo, boolean forAutofill) {
structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
null, null, info.getViewIdResourceName());
Rect rect = structure.getTempRect();
- info.getBoundsInParent(rect);
+ // The bounds in parent for Jetpack Compose views aren't set as setBoundsInParent is
+ // deprecated, and only setBoundsInScreen is called.
+ // The bounds in parent can be calculated by diff'ing the child view's bounds in screen with
+ // the parent's.
+ if (sCalculateBoundsInParentFromBoundsInScreenFlagValue) {
+ getBoundsInParent(info, parentInfo, rect);
+ } else {
+ info.getBoundsInParent(rect);
+ }
structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
structure.setVisibility(VISIBLE);
structure.setEnabled(info.isEnabled());
@@ -10161,6 +11171,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
structure.setAutofillId(new AutofillId(getAutofillId(),
AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId())));
}
+ if (getViewCredentialHandler() != null) {
+ structure.setPendingCredentialRequest(
+ getViewCredentialHandler().getRequest(),
+ getViewCredentialHandler().getCallback());
+ }
CharSequence cname = info.getClassName();
structure.setClassName(cname != null ? cname.toString() : null);
structure.setContentDescription(info.getContentDescription());
@@ -10205,13 +11220,32 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
if (cinfo != null) {
ViewStructure child = structure.newChild(i);
- populateVirtualStructure(child, provider, cinfo, forAutofill);
+ populateVirtualStructure(child, provider, cinfo, info, forAutofill);
cinfo.recycle();
}
}
}
}
+ private void getBoundsInParent(@NonNull AccessibilityNodeInfo info,
+ @Nullable AccessibilityNodeInfo parentInfo, @NonNull Rect rect) {
+ info.getBoundsInParent(rect);
+ // Fallback to calculate bounds in parent by diffing the bounds in
+ // screen if it's all 0.
+ if ((rect.left | rect.top | rect.right | rect.bottom) == 0) {
+ if (parentInfo != null) {
+ Rect parentBoundsInScreen = parentInfo.getBoundsInScreen();
+ Rect boundsInScreen = info.getBoundsInScreen();
+ rect.set(boundsInScreen.left - parentBoundsInScreen.left,
+ boundsInScreen.top - parentBoundsInScreen.top,
+ boundsInScreen.right - parentBoundsInScreen.left,
+ boundsInScreen.bottom - parentBoundsInScreen.top);
+ } else {
+ info.getBoundsInScreen(rect);
+ }
+ }
+ }
+
/**
* Dispatch creation of {@link ViewStructure} down the hierarchy. The default
* implementation calls {@link #onProvideStructure} and
@@ -10321,11 +11355,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return;
}
- session.internalNotifyViewTreeEvent(/* started= */ true);
+ session.notifyViewTreeEvent(/* started= */ true);
try {
dispatchProvideContentCaptureStructure();
} finally {
- session.internalNotifyViewTreeEvent(/* started= */ false);
+ session.notifyViewTreeEvent(/* started= */ false);
}
}
@@ -10359,6 +11393,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
getBoundsOnScreen(bounds, true);
info.setBoundsInScreen(bounds);
+ getBoundsInWindow(bounds, true);
+ info.setBoundsInWindow(bounds);
ViewParent parent = getParentForAccessibility();
if (parent instanceof View) {
@@ -10373,11 +11409,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
View label = rootView.findLabelForView(this, mID);
if (label != null) {
- info.setLabeledBy(label);
+ if (supportMultipleLabeledby()) {
+ info.addLabeledBy(label);
+ } else {
+ info.setLabeledBy(label);
+ }
}
if ((mAttachInfo.mAccessibilityFetchFlags
- & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0
+ & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS) != 0
&& Resources.resourceHasPackage(mID)) {
try {
String viewId = getResources().getResourceName(mID);
@@ -10426,6 +11466,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
info.setVisibleToUser(isVisibleToUser());
info.setImportantForAccessibility(isImportantForAccessibility());
+ info.setAccessibilityDataSensitive(isAccessibilityDataSensitive());
info.setPackageName(mContext.getPackageName());
info.setClassName(getAccessibilityClassName());
info.setStateDescription(getStateDescription());
@@ -10844,6 +11885,39 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return mContentDescription;
}
+ /**
+ * Returns the {@link View}'s supplemental description.
+ *
+ * A supplemental description provides
+ * brief supplemental information for this node, such as the purpose of the node when
+ * that purpose is not conveyed within its textual representation. For example, if a
+ * dropdown select has a purpose of setting font family, the supplemental description
+ * could be "font family". If this node has children, its supplemental description serves
+ * as additional information and is not intended to replace any existing information
+ * in the subtree. This is different from the {@link #getContentDescription()} in that
+ * this description is purely supplemental while a content description may be used
+ * to replace a description for a node or its subtree that an assistive technology
+ * would otherwise compute based on other properties of the node and its descendants.
+ *
+ *
+ * Note: Do not override this method, as it will have no
+ * effect on the supplemental description presented to accessibility services.
+ * You must call {@link #setSupplementalDescription(CharSequence)} to modify the
+ * supplemental description.
+ *
+ * @return the supplemental description
+ * @see #setSupplementalDescription(CharSequence)
+ * @see #getContentDescription()
+ * @attr ref android.R.styleable#View_supplementalDescription
+ */
+ @FlaggedApi(FLAG_SUPPLEMENTAL_DESCRIPTION)
+ @ViewDebug.ExportedProperty(category = "accessibility")
+ @InspectableProperty
+ @Nullable
+ public CharSequence getSupplementalDescription() {
+ return mSupplementalDescription;
+ }
+
/**
* Sets the {@link View}'s state description.
*
@@ -10858,6 +11932,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @param stateDescription The state description.
* @see #getStateDescription()
+ * @see #setContentDescription(CharSequence) for the difference between content and
+ * state descriptions.
*/
@RemotableViewMethod
public void setStateDescription(@Nullable CharSequence stateDescription) {
@@ -10893,8 +11969,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* description. An image of a floppy disk that is used to save a file may
* use "Save".
*
+ *
+ * This should omit role or state. Role refers to the kind of user-interface element the View
+ * is, such as a Button or Checkbox. State refers to a frequently changing property of the View,
+ * such as an On/Off state of a button or the audio level of a volume slider.
+ *
+ *
+ * Content description updates are not frequent, and are used when the semantic content - not
+ * the state - of the element changes. For example, a Play button might change to a Pause
+ * button during music playback.
+ *
* @param contentDescription The content description.
* @see #getContentDescription()
+ * @see #setStateDescription(CharSequence)} for state changes.
* @attr ref android.R.styleable#View_contentDescription
*/
@RemotableViewMethod
@@ -10918,26 +12005,83 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Sets the id of a view before which this one is visited in accessibility traversal.
- * A screen-reader must visit the content of this view before the content of the one
- * it precedes. For example, if view B is set to be before view A, then a screen-reader
- * will traverse the entire content of B before traversing the entire content of A,
- * regardles of what traversal strategy it is using.
+ * Sets the {@link View}'s supplemental description.
*
- * Views that do not have specified before/after relationships are traversed in order
- * determined by the screen-reader.
- *
- * Setting that this view is before a view that is not important for accessibility
- * or if this view is not important for accessibility will have no effect as the
- * screen-reader is not aware of unimportant views.
- *
+ *
+ * For example, if view B should be visited before view A, with
+ * B.setAccessibilityTraversalBefore(A), this requests that screen readers visit and traverse
+ * view B before visiting view A.
+ *
+ *
+ * Note: Views are visited in the order determined by the screen reader. Avoid
+ * explicitly manipulating focus order, as this may result in inconsistent user
+ * experiences between apps. Instead, use other semantics, such as restructuring the view
+ * hierarchy layout, to communicate order.
+ *
+ *
+ * Setting this view to be after a view that is not important for accessibility,
+ * or if this view is not important for accessibility, means this method will have no effect if
+ * the service is not aware of unimportant views.
+ *
+ *
+ * To avoid a risk of loops, set clear relationships between views. For example, if focus order
+ * should be B -> A, and B.setAccessibilityTraversalBefore(A), then also call
+ * A.setAccessibilityTraversalAfter(B).
*
* @param beforeId The id of a view this one precedes in accessibility traversal.
*
* @attr ref android.R.styleable#View_accessibilityTraversalBefore
*
* @see #setImportantForAccessibility(int)
+ * @see #setAccessibilityTraversalAfter(int)
*/
@RemotableViewMethod
public void setAccessibilityTraversalBefore(@IdRes int beforeId) {
@@ -10964,26 +12108,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Sets the id of a view after which this one is visited in accessibility traversal.
- * A screen-reader must visit the content of the other view before the content of this
- * one. For example, if view B is set to be after view A, then a screen-reader
- * will traverse the entire content of A before traversing the entire content of B,
- * regardles of what traversal strategy it is using.
+ * Sets the id of a view that screen readers are requested to visit before this view.
+ *
*
- * Views that do not have specified before/after relationships are traversed in order
- * determined by the screen-reader.
- *
- * Setting that this view is after a view that is not important for accessibility
- * or if this view is not important for accessibility will have no effect as the
- * screen-reader is not aware of unimportant views.
- *
+ * Setting this view to be after a view that is not important for accessibility,
+ * or if this view is not important for accessibility, means this method will have no effect if
+ * the service is not aware of unimportant views.
+ *
+ *
+ * To avoid a risk of loops, set clear relationships between views. For example, if focus order
+ * should be B -> A, and B.setAccessibilityTraversalBefore(A), then also call
+ * A.setAccessibilityTraversalAfter(B).
*
- * @param afterId The id of a view this one succedees in accessibility traversal.
+ * @param afterId The id of a view this one succeeds in accessibility traversal.
*
* @attr ref android.R.styleable#View_accessibilityTraversalAfter
*
* @see #setImportantForAccessibility(int)
+ * @see #setAccessibilityTraversalBefore(int)
*/
@RemotableViewMethod
public void setAccessibilityTraversalAfter(@IdRes int afterId) {
@@ -11738,15 +12890,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (rects.isEmpty() && mListenerInfo == null) return;
final ListenerInfo info = getListenerInfo();
+ final boolean rectsChanged = !reduceChangedExclusionRectsMsgs()
+ || !Objects.equals(info.mSystemGestureExclusionRects, rects);
if (info.mSystemGestureExclusionRects != null) {
- info.mSystemGestureExclusionRects.clear();
- info.mSystemGestureExclusionRects.addAll(rects);
+ if (rectsChanged) {
+ info.mSystemGestureExclusionRects.clear();
+ info.mSystemGestureExclusionRects.addAll(rects);
+ }
} else {
info.mSystemGestureExclusionRects = new ArrayList<>(rects);
}
-
- updatePositionUpdateListener();
- postUpdate(this::updateSystemGestureExclusionRects);
+ if (rectsChanged) {
+ updatePositionUpdateListener();
+ postUpdate(this::updateSystemGestureExclusionRects);
+ }
}
private void updatePositionUpdateListener() {
@@ -11754,7 +12911,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (getSystemGestureExclusionRects().isEmpty()
&& collectPreferKeepClearRects().isEmpty()
&& collectUnrestrictedPreferKeepClearRects().isEmpty()
- && (info.mHandwritingArea == null || !isAutoHandwritingEnabled())) {
+ && (info.mHandwritingArea == null || !shouldTrackHandwritingArea())) {
if (info.mPositionUpdateListener != null) {
mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener);
info.mPositionUpdateListener = null;
@@ -12006,12 +13163,91 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Set a list of handwriting areas in this view. If there is any stylus {@link MotionEvent}
- * occurs within those areas, it will trigger stylus handwriting mode. This can be disabled by
+ * Set the amount of offset applied to this view's stylus handwriting bounds. A positive offset
+ * will offset the edge outwards.The base handwriting bounds of a view is its visible bounds.
+ * The handwriting bounds offsets are applied to the base handwriting bounds to determine the
+ * final handwriting bounds.
+ * This method is mainly used to enlarge the view's handwriting bounds for a better user
+ * experience.
+ * Note that when the view is clipped (e.g. the view is in a
+ * {@link android.widget.ScrollView}), the offsets are applied after the view's handwriting
+ * bounds is clipped.
+ *
+ * @param offsetLeft the amount of pixel offset applied to the left edge outwards of the view's
+ * handwriting bounds.
+ * @param offsetTop the amount of pixel offset applied to the top edge outwards of the view's
+ * handwriting bounds.
+ * @param offsetRight the amount of pixel offset applied to the right edge outwards of the
+ * view's handwriting bounds.
+ * @param offsetBottom the amount of pixel offset applied to the bottom edge outwards of the
+ * view's handwriting bounds.
+ *
+ * @see #setAutoHandwritingEnabled(boolean)
+ * @see #getHandwritingBoundsOffsetLeft()
+ * @see #getHandwritingBoundsOffsetTop()
+ * @see #getHandwritingBoundsOffsetRight()
+ * @see #getHandwritingBoundsOffsetBottom()
+ */
+ public void setHandwritingBoundsOffsets(float offsetLeft, float offsetTop,
+ float offsetRight, float offsetBottom) {
+ mHandwritingBoundsOffsetLeft = offsetLeft;
+ mHandwritingBoundsOffsetTop = offsetTop;
+ mHandwritingBoundsOffsetRight = offsetRight;
+ mHandwritingBoundsOffsetBottom = offsetBottom;
+ }
+
+ /**
+ * Return the amount of offset applied to the left edge of this view's handwriting bounds,
+ * in the unit of pixel.
+ *
+ * @see #setAutoHandwritingEnabled(boolean)
+ * @see #setHandwritingBoundsOffsets(float, float, float, float)
+ */
+ public float getHandwritingBoundsOffsetLeft() {
+ return mHandwritingBoundsOffsetLeft;
+ }
+
+ /**
+ * Return the amount of offset applied to the top edge of this view's handwriting bounds,
+ * in the unit of pixel.
+ *
+ * @see #setAutoHandwritingEnabled(boolean)
+ * @see #setHandwritingBoundsOffsets(float, float, float, float)
+ */
+ public float getHandwritingBoundsOffsetTop() {
+ return mHandwritingBoundsOffsetTop;
+ }
+
+ /**
+ * Return the amount of offset applied to the right edge of this view's handwriting bounds, in
+ * the unit of pixel.
+ *
+ * @see #setAutoHandwritingEnabled(boolean)
+ * @see #setHandwritingBoundsOffsets(float, float, float, float)
+ */
+ public float getHandwritingBoundsOffsetRight() {
+ return mHandwritingBoundsOffsetRight;
+ }
+
+ /**
+ * Return the amount of offset applied to the bottom edge of this view's handwriting bounds, in
+ * the unit of pixel.
+ *
+ * @see #setAutoHandwritingEnabled(boolean)
+ * @see #setHandwritingBoundsOffsets(float, float, float, float)
+ */
+ public float getHandwritingBoundsOffsetBottom() {
+ return mHandwritingBoundsOffsetBottom;
+ }
+
+
+ /**
+ * Set a handwriting area in this view. If there is any stylus {@link MotionEvent}
+ * occurs within this area, it will trigger stylus handwriting mode. This can be disabled by
* disabling the auto handwriting initiation by calling
* {@link #setAutoHandwritingEnabled(boolean)} with false.
*
- * @attr rects a list of handwriting area in the view's local coordiniates.
+ * @attr rect the handwriting area in the view's local coordiniates.
*
* @see android.view.inputmethod.InputMethodManager#startStylusHandwriting(View)
* @see #setAutoHandwritingEnabled(boolean)
@@ -12042,13 +13278,197 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
void updateHandwritingArea() {
// If autoHandwritingArea is not enabled, do nothing.
- if (!isAutoHandwritingEnabled()) return;
+ if (!shouldTrackHandwritingArea()) return;
final AttachInfo ai = mAttachInfo;
if (ai != null) {
ai.mViewRootImpl.getHandwritingInitiator().updateHandwritingAreasForView(this);
}
}
+ /**
+ * Returns true if a stylus {@link MotionEvent} within this view's bounds should initiate
+ * handwriting mode, either for this view ({@link #isAutoHandwritingEnabled()} is {@code true})
+ * or for a handwriting delegate view ({@link #getHandwritingDelegatorCallback()} is not {@code
+ * null}).
+ */
+ boolean shouldInitiateHandwriting() {
+ return isAutoHandwritingEnabled() || getHandwritingDelegatorCallback() != null;
+ }
+
+ /**
+ * Returns whether the handwriting initiator should track the handwriting area for this view,
+ * either to initiate handwriting mode, or to prepare handwriting delegation, or to show the
+ * handwriting unsupported message.
+ * @hide
+ */
+ public boolean shouldTrackHandwritingArea() {
+ return shouldInitiateHandwriting();
+ }
+
+ /**
+ * Sets a callback which should be called when a stylus {@link MotionEvent} occurs within this
+ * view's bounds. The callback will be called from the UI thread.
+ *
+ * Setting a callback allows this view to act as a handwriting delegator, so that handwriting
+ * mode for a delegate editor view can be initiated by stylus movement on this delegator view.
+ * The callback implementation is expected to show and focus the delegate editor view. If a view
+ * which returns {@code true} for {@link #isHandwritingDelegate()} creates an input connection
+ * while the same stylus {@link MotionEvent} sequence is ongoing, handwriting mode will be
+ * initiated for that view.
+ *
+ * A common use case is a custom view which looks like a text editor but does not actually
+ * support text editing itself, and clicking on the custom view causes an EditText to be shown.
+ * To support handwriting initiation in this case, this method can be called on the custom view
+ * to configure it as a delegator. The EditText should call {@link #setIsHandwritingDelegate} to
+ * set it as a delegate. The {@code callback} implementation is typically the same as the click
+ * listener implementation which shows the EditText.
+ *
+ * If {@code null} is passed, this view will no longer act as a handwriting initiation
+ * delegator.
+ *
+ * @param callback a callback which should be called when a stylus {@link MotionEvent} occurs
+ * within this view's bounds
+ */
+ public void setHandwritingDelegatorCallback(@Nullable Runnable callback) {
+ mHandwritingDelegatorCallback = callback;
+ if (callback != null) {
+ setHandwritingArea(new Rect(0, 0, getWidth(), getHeight()));
+ }
+ }
+
+ /**
+ * Returns the callback set by {@link #setHandwritingDelegatorCallback} which should be called
+ * when a stylus {@link MotionEvent} occurs within this view's bounds. The callback should only
+ * be called from the UI thread.
+ */
+ @Nullable
+ public Runnable getHandwritingDelegatorCallback() {
+ return mHandwritingDelegatorCallback;
+ }
+
+ /**
+ * Specifies that this view may act as a handwriting initiation delegator for a delegate editor
+ * view from the specified package. If this method is not called, delegators may only be used to
+ * initiate handwriting mode for a delegate editor view from the same package as the delegator
+ * view. This method allows specifying a different trusted package which may contain a delegate
+ * editor view linked to this delegator view.
+ *
+ * This method has no effect unless {@link #setHandwritingDelegatorCallback} is also called
+ * to configure this view to act as a handwriting delegator.
+ *
+ * If this method is called on the delegator view, then {@link
+ * #setAllowedHandwritingDelegatorPackage} should also be called on the delegate editor view.
+ *
+ * For example, to configure a delegator view in package 1:
+ *
+ * This method has no effect unless {@link #setIsHandwritingDelegate} is also called to
+ * configure this view to act as a handwriting delegate.
+ *
+ * If this method is called on the delegate editor view, then {@link
+ * #setAllowedHandwritingDelegatePackage} should also be called on the delegator view.
+ *
+ * @param allowedPackageName the package name of a delegator view linked to this delegate editor
+ * view, or {@code null} to restore the default behavior of only allowing delegator views
+ * from the same package as this delegate editor view
+ */
+ public void setAllowedHandwritingDelegatorPackage(@Nullable String allowedPackageName) {
+ mAllowedHandwritingDelegatorPackageName = allowedPackageName;
+ }
+
+ /**
+ * Returns the allowed package for views which may act as a handwriting delegator for this
+ * delegate editor view, as set by {@link #setAllowedHandwritingDelegatorPackage}. If {@link
+ * #setAllowedHandwritingDelegatorPackage} has not been called, or called with {@code null}
+ * argument, this will return {@code null}, meaning that only views from the same package as
+ * this delegator editor view may act as a handwriting delegator.
+ */
+ @Nullable
+ public String getAllowedHandwritingDelegatorPackageName() {
+ return mAllowedHandwritingDelegatorPackageName;
+ }
+
+ /**
+ * Sets flags configuring the handwriting delegation behavior for this delegate editor view.
+ *
+ * This method has no effect unless {@link #setIsHandwritingDelegate} is also called to
+ * configure this view to act as a handwriting delegate.
+ *
+ * @param flags {@link InputMethodManager#HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or
+ * {@code 0}
+ */
+ @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
+ public void setHandwritingDelegateFlags(
+ @InputMethodManager.HandwritingDelegateFlags int flags) {
+ mHandwritingDelegateFlags = flags;
+ }
+
+ /**
+ * Returns flags configuring the handwriting delegation behavior for this delegate editor view,
+ * as set by {@link #setHandwritingDelegateFlags}.
+ */
+ @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
+ public @InputMethodManager.HandwritingDelegateFlags int getHandwritingDelegateFlags() {
+ return mHandwritingDelegateFlags;
+ }
+
/**
* Gets the coordinates of this view in the coordinate space of the
* {@link Surface} that contains the view.
@@ -12174,6 +13594,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+ /**
+ * @return True if the window has the {@link OnContentApplyWindowInsetsListener}, and this means
+ * the framework will apply window insets on the content of the window.
+ * @hide
+ */
+ protected boolean hasContentOnApplyWindowInsetsListener() {
+ return mAttachInfo != null && mAttachInfo.mContentOnApplyWindowInsetsListener != null;
+ }
+
/**
* Sets whether or not this view should account for system screen decorations
* such as the status bar and inset its content; that is, controlling whether
@@ -12333,6 +13762,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (!enabled) {
cancelPendingInputEvents();
}
+ notifyViewAccessibilityStateChangedIfNeeded(
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED);
}
/**
@@ -12431,6 +13862,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
} else {
mAutofillHints = autofillHints;
}
+ if (sensitiveContentAppProtection()) {
+ if (getContentSensitivity() == CONTENT_SENSITIVITY_AUTO) {
+ updateSensitiveViewsCountIfNeeded(isAggregatedVisible());
+ }
+ }
}
/**
@@ -12600,11 +14036,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
})
@ResolvedLayoutDir
public int getLayoutDirection() {
- final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
- if (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1) {
- mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
- return LAYOUT_DIRECTION_RESOLVED_DEFAULT;
- }
return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ==
PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
}
@@ -12709,7 +14140,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (mViewTranslationCallback != null) {
mViewTranslationCallback.onClearTranslation(this);
}
- clearViewTranslationCallback();
clearViewTranslationResponse();
if (hasTranslationTransientState()) {
setHasTransientState(false);
@@ -13079,6 +14509,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public void setFilterTouchesWhenObscured(boolean enabled) {
setFlags(enabled ? FILTER_TOUCHES_WHEN_OBSCURED : 0,
FILTER_TOUCHES_WHEN_OBSCURED);
+ calculateAccessibilityDataSensitive();
}
/**
@@ -13161,6 +14592,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Returns whether the view should be treated as a focusable unit by screen reader
* accessibility tools.
+ *
+ * Note: Use
+ * {@link androidx.core.view.ViewCompat#setScreenReaderFocusable(View, boolean)}
+ * for backwards-compatibility.
* @see #setScreenReaderFocusable(boolean)
*
* @return Whether the view should be treated as a focusable unit by screen reader.
@@ -13202,6 +14637,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Set if view is a heading for a section of content for accessibility purposes.
+ *
+ * Users of some accessibility services can choose to navigate between headings
+ * instead of between paragraphs, words, etc. Apps that provide headings on
+ * sections of text can help the text navigation experience.
+ *
+ * Note: Use {@link androidx.core.view.ViewCompat#setAccessibilityHeading(View, boolean)}
+ * for backwards-compatibility.
*
* @param isHeading {@code true} if the view is a heading, {@code false} otherwise.
*
@@ -13712,6 +15154,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* See also {@link #focusSearch(int)}, which is what you call to say that you
* have focus, and you want your parent to look for the next one.
*
+ *
+ * Note: Avoid setting accessibility focus. This is intended to be controlled by screen
+ * readers. Apps changing focus can confuse screen readers, so the resulting behavior can vary
+ * by device and screen reader version.
+ *
* @return Whether this view actually took accessibility focus.
*
* @hide
@@ -13775,7 +15222,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// working solution.
View source = this;
while (true) {
- if (source.includeForAccessibility()) {
+ if (source.includeForAccessibility(false)) {
source.sendAccessibilityEvent(eventType);
return;
}
@@ -14044,19 +15491,57 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* to the view's content description or text, or to the content descriptions
* or text of the view's children (where applicable).
*
- * For example, in a login screen with a TextView that displays an "incorrect
- * password" notification, that view should be marked as a live region with
- * mode {@link #ACCESSIBILITY_LIVE_REGION_POLITE}.
+ * Different priority levels are available:
+ *
- * To disable change notifications for this view, use
- * {@link #ACCESSIBILITY_LIVE_REGION_NONE}. This is the default live region
- * mode for most views.
+ * Examples:
+ *
+ * For error notifications, like an "incorrect password" warning in a login screen, views
+ * should send a {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}
+ * {@code AccessibilityEvent} with a content change type
+ * {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR} and set
+ * {@link AccessibilityNodeInfo#setError(CharSequence)}. Custom widgets should provide
+ * error-setting methods that support accessibility. For example, use
+ * {@link android.widget.TextView#setError(CharSequence)} instead of explicitly sending events.
*
- * To indicate that the user should be notified of changes, use
- * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}.
+ * Don't use live regions for frequently-updating UI elements (e.g., progress bars), as this can
+ * overwhelm the user with feedback from accessibility services. If necessary, use
+ * {@link AccessibilityNodeInfo#setMinDurationBetweenContentChanges(Duration)} to throttle
+ * feedback and reduce disruptions.
*
- * If the view's changes should interrupt ongoing speech and notify the user
- * immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}.
+ *
*
* @param mode The live region mode for this view, one of:
*
+ * If this decision is used for generating the accessibility node tree then this returns false
+ * for {@link #isAccessibilityDataPrivate()} views queried by non-accessibility tools.
+ *
+ * Otherwise, a view is regarded for accessibility if:
+ *
+ * See default behavior provided by {@link #ACCESSIBILITY_DATA_SENSITIVE_AUTO}. Otherwise,
+ * returns true for {@link #ACCESSIBILITY_DATA_SENSITIVE_YES} or false for {@link
+ * #ACCESSIBILITY_DATA_SENSITIVE_NO}.
+ *
+ * Note: This method needs to be called any time one of the below conditions
+ * changes, to recalculate the new value.
+ *
+ *
+ *
- *
- *
- *
- *
+ *
+ *
*
* @return whether the view is considered important for autofill.
*
@@ -9753,6 +10576,93 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return false;
}
+ /**
+ * Sets content sensitivity mode to determine whether this view displays sensitive content
+ * (e.g. username, password etc.). The system will improve user privacy i.e. hide content
+ * drawn by a sensitive view from screen sharing and recording.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * delegatorView.setHandwritingDelegatorCallback(callback);
+ * delegatorView.setAllowedHandwritingDelegatePackage(package2);
+ *
+ * Then to configure the corresponding delegate editor view in package 2:
+ *
+ *
+ * delegateEditorView.setIsHandwritingDelegate(true);
+ * delegateEditorView.setAllowedHandwritingDelegatorPackage(package1);
+ *
+ * @param allowedPackageName the package name of a delegate editor view linked to this delegator
+ * view, or {@code null} to restore the default behavior of only allowing delegate editor
+ * views from the same package as this delegator view
+ */
+ public void setAllowedHandwritingDelegatePackage(@Nullable String allowedPackageName) {
+ mAllowedHandwritingDelegatePackageName = allowedPackageName;
+ }
+
+ /**
+ * Returns the allowed package for delegate editor views for which this view may act as a
+ * handwriting delegator, as set by {@link #setAllowedHandwritingDelegatePackage}. If {@link
+ * #setAllowedHandwritingDelegatePackage} has not been called, or called with {@code null}
+ * argument, this will return {@code null}, meaning that this delegator view may only be used to
+ * initiate handwriting mode for a delegate editor view from the same package as this delegator
+ * view.
+ */
+ @Nullable
+ public String getAllowedHandwritingDelegatePackageName() {
+ return mAllowedHandwritingDelegatePackageName;
+ }
+
+ /**
+ * Sets this view to be a handwriting delegate. If a delegate view creates an input connection
+ * while a stylus {@link MotionEvent} sequence from a delegator view is ongoing, handwriting
+ * mode will be initiated for the delegate view.
+ *
+ * @param isHandwritingDelegate whether this view is a handwriting initiation delegate
+ * @see #setHandwritingDelegatorCallback(Runnable)
+ */
+ public void setIsHandwritingDelegate(boolean isHandwritingDelegate) {
+ mIsHandwritingDelegate = isHandwritingDelegate;
+ }
+
+ /**
+ * Returns whether this view has been set as a handwriting delegate by {@link
+ * #setIsHandwritingDelegate}.
+ */
+ public boolean isHandwritingDelegate() {
+ return mIsHandwritingDelegate;
+ }
+
+ /**
+ * Specifies that a view from the specified package may act as a handwriting delegator for this
+ * delegate editor view. If this method is not called, only views from the same package as this
+ * delegate editor view may act as a handwriting delegator. This method allows specifying a
+ * different trusted package which may contain a delegator view linked to this delegate editor
+ * view.
+ *
+ *
+ *
*
+ *
+ *
@@ -14129,11 +15614,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// importance, since we'll need to check it later to make sure.
final boolean maySkipNotify = oldMode == IMPORTANT_FOR_ACCESSIBILITY_AUTO
|| mode == IMPORTANT_FOR_ACCESSIBILITY_AUTO;
- final boolean oldIncludeForAccessibility = maySkipNotify && includeForAccessibility();
+ final boolean oldIncludeForAccessibility =
+ maySkipNotify && includeForAccessibility(false);
mPrivateFlags2 &= ~PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK;
mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT)
& PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK;
- if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility()) {
+ if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility(false)) {
notifySubtreeAccessibilityStateChangedIfNeeded();
} else {
notifyViewAccessibilityStateChangedIfNeeded(
@@ -14187,8 +15673,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* view satisfies any of the following:
*
*
*
+ * Note: Avoid setting accessibility focus with + * {@link AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS}. This is intended to be controlled + * by screen readers. Apps changing focus can confuse screen readers, so the resulting behavior + * can vary by device and screen reader version. + * * @param action The action to perform. * @param arguments Optional action arguments. * @return Whether the action was performed. @@ -14496,13 +16082,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - /** - * @see #performAccessibilityAction(int, Bundle) - * - * Note: Called from the default {@link AccessibilityDelegate}. - * - * @hide - */ + /** + * @see #performAccessibilityAction(int, Bundle) + * + * Note: Called from the default {@link AccessibilityDelegate}. + * + * @hide + */ @UnsupportedAppUsage public boolean performAccessibilityActionInternal(int action, @Nullable Bundle arguments) { if (isNestedScrollingEnabled() @@ -14966,6 +16552,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. + * + * @see #onTouchEvent(MotionEvent) */ public boolean dispatchTouchEvent(MotionEvent event) { // If the event should be handled by accessibility focus first. @@ -14990,20 +16578,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (onFilterTouchEventForSecurity(event)) { - if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { - result = true; - } - //noinspection SimplifiableIfStatement - ListenerInfo li = mListenerInfo; - if (li != null && li.mOnTouchListener != null - && (mViewFlags & ENABLED_MASK) == ENABLED - && li.mOnTouchListener.onTouch(this, event)) { - result = true; - } - - if (!result && onTouchEvent(event)) { - result = true; - } + result = performOnTouchCallback(event); } if (!result && mInputEventConsistencyVerifier != null) { @@ -15022,6 +16597,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return result; } + /** + * Returns {@code true} if the {@link MotionEvent} from {@link #dispatchTouchEvent} was + * handled by this view. + */ + private boolean performOnTouchCallback(MotionEvent event) { + boolean handled = false; + if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { + handled = true; + } + //noinspection SimplifiableIfStatement + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED) { + try { + if (Trace.isTagEnabled(TRACE_TAG_VIEW)) { + Trace.traceBegin(TRACE_TAG_VIEW, + "View.onTouchListener#onTouch - " + getClass().getSimpleName() + + ", eventId - " + event.getId()); + } + handled = li.mOnTouchListener.onTouch(this, event); + } finally { + Trace.traceEnd(TRACE_TAG_VIEW); + } + } + if (handled) { + return true; + } + try { + Trace.traceBegin(TRACE_TAG_VIEW, "View#onTouchEvent"); + return onTouchEvent(event); + } finally { + Trace.traceEnd(TRACE_TAG_VIEW); + } + } + boolean isAccessibilityFocusedViewOrHost() { return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl() .getAccessibilityFocusedHost() == this); @@ -15052,6 +16661,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Window is obscured, drop this touch. return false; } + if (android.view.accessibility.Flags.preventA11yNontoolFromInjectingIntoSensitiveViews()) { + if (event.isInjectedFromAccessibilityService() + // If the event came from an Accessibility Service that does *not* declare + // itself as AccessibilityServiceInfo#isAccessibilityTool and this View is + // declared sensitive then drop the event. + // Only Accessibility Tools are allowed to interact with sensitive Views. + && !event.isInjectedFromAccessibilityTool() && isAccessibilityDataSensitive()) { + return false; + } + } return true; } @@ -15060,6 +16679,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. + * + * @see #onTrackballEvent(MotionEvent) */ public boolean dispatchTrackballEvent(MotionEvent event) { if (mInputEventConsistencyVerifier != null) { @@ -15094,11 +16715,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Generic motion events with source class {@link InputDevice#SOURCE_CLASS_POINTER} * are delivered to the view under the pointer. All other generic motion events are * delivered to the focused view. Hover events are handled specially and are delivered - * to {@link #onHoverEvent(MotionEvent)}. + * to {@link #onHoverEvent(MotionEvent)} first. *
* * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. + * + * @see #onHoverEvent(MotionEvent) + * @see #onGenericMotionEvent(MotionEvent) */ public boolean dispatchGenericMotionEvent(MotionEvent event) { if (mInputEventConsistencyVerifier != null) { @@ -15132,6 +16756,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } private boolean dispatchGenericMotionEventInternal(MotionEvent event) { + final boolean isRotaryEncoderEvent = event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER); + if (isRotaryEncoderEvent) { + // Determine and cache rotary scroll haptics support if it's not yet determined. + // Caching the support is important for two reasons: + // 1) Limits call to `ViewConfiguration#get`, which we should avoid if possible. + // 2) Limits latency from the `ViewConfiguration` API, which may be slow due to feature + // flag querying. + if ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_DETERMINED) == 0) { + if (ViewConfiguration.get(mContext) + .isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) { + mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_ENABLED; + } + mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_DETERMINED; + } + } + if (isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0)) { + mPrivateFlags4 &= ~PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT; + mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_WAITING_FOR_SCROLL_EVENT; + } + //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnGenericMotionListener != null @@ -15140,7 +16784,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return true; } - if (onGenericMotionEvent(event)) { + final boolean onGenericMotionEventResult = onGenericMotionEvent(event); + // Process scroll haptics after `onGenericMotionEvent`, since that's where scrolling usually + // happens. Some views may return false from `onGenericMotionEvent` even if they have done + // scrolling, so disregard the return value when processing for scroll haptics. + // Check for `PFLAG4_ROTARY_HAPTICS_ENABLED` again, because the View implementation may + // call `disableRotaryScrollFeedback` in `onGenericMotionEvent`, which could change the + // value of `PFLAG4_ROTARY_HAPTICS_ENABLED`. + if (isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0)) { + if ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT) != 0) { + doRotaryProgressForScrollHaptics(event); + } else { + doRotaryLimitForScrollHaptics(event); + } + } + if (onGenericMotionEventResult) { return true; } @@ -15301,6 +16959,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, onFocusLost(); } else if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { notifyFocusChangeToImeFocusController(true /* hasFocus */); + ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot != null && initiationWithoutInputConnection() && onCheckIsTextEditor()) { + viewRoot.getHandwritingInitiator().onEditorFocused(this); + } } refreshDrawableState(); @@ -15459,7 +17121,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (fg != null && isVisible != fg.isVisible()) { fg.setVisible(isVisible, false); } + notifyAutofillManagerViewVisibilityChanged(isVisible); + if (isVisible != oldVisible) { + if (isAccessibilityPane()) { + notifyViewAccessibilityStateChangedIfNeeded(isVisible + ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED + : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED); + } + + notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible); + updateSensitiveViewsCountIfNeeded(isVisible); + + if (!getSystemGestureExclusionRects().isEmpty()) { + postUpdate(this::updateSystemGestureExclusionRects); + } + + if (!collectPreferKeepClearRects().isEmpty()) { + postUpdate(this::updateKeepClearRects); + } + } + } + private void notifyAutofillManagerViewVisibilityChanged(boolean isVisible) { if (isAutofillable()) { AutofillManager afm = getAutofillManager(); @@ -15483,24 +17166,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } } - - if (isVisible != oldVisible) { - if (isAccessibilityPane()) { - notifyViewAccessibilityStateChangedIfNeeded(isVisible - ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED - : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED); - } - - notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible); - - if (!getSystemGestureExclusionRects().isEmpty()) { - postUpdate(this::updateSystemGestureExclusionRects); - } - - if (!collectPreferKeepClearRects().isEmpty()) { - postUpdate(this::updateKeepClearRects); - } - } } /** @@ -15531,10 +17196,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mViewRootImpl.getWindowVisibleDisplayFrame(outRect); return; } - // The view is not attached to a display so we don't have a context. - // Make a best guess about the display size. - Display d = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); - d.getRectSize(outRect); + // TODO (b/327559224): Refine the behavior to better reflect the window environment with API + // doc updates. + final WindowManager windowManager = mContext.getSystemService(WindowManager.class); + final WindowMetrics metrics = windowManager.getMaximumWindowMetrics(); + final Insets insets = metrics.getWindowInsets().getInsets( + WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()); + outRect.set(metrics.getBounds()); + outRect.inset(insets); + outRect.offsetTo(0, 0); } /** @@ -15544,7 +17214,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ @UnsupportedAppUsage - public void getWindowDisplayFrame(Rect outRect) { + @TestApi + public void getWindowDisplayFrame(@NonNull Rect outRect) { if (mAttachInfo != null) { mAttachInfo.mViewRootImpl.getDisplayFrame(outRect); return; @@ -15613,19 +17284,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Returns whether the device is currently in touch mode. Touch mode is entered - * once the user begins interacting with the device by touch, and affects various - * things like whether focus is always visible to the user. + * Returns the touch mode state associated with this view. + * + * Attached views return the touch mode state from the associated window's display. + * Detached views just return the default touch mode value defined in + * {@code com.android.internal.R.bool.config_defaultInTouchMode}. * - * @return Whether the device is in touch mode. + * Touch mode is entered once the user begins interacting with the device by touch, and + * affects various things like whether focus highlight is always visible to the user. + * + * @return the touch mode state associated with this view */ @ViewDebug.ExportedProperty public boolean isInTouchMode() { if (mAttachInfo != null) { return mAttachInfo.mInTouchMode; - } else { - return ViewRootImpl.isInTouchMode(); } + return mResources.getBoolean(com.android.internal.R.bool.config_defaultInTouchMode); } /** @@ -15642,10 +17317,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Handle a key event before it is processed by any input method - * associated with the view hierarchy. This can be used to intercept + * associated with the view hierarchy. This can be used to intercept * key events in special situations before the IME consumes them; a * typical example would be handling the BACK key to update the application's - * UI instead of allowing the IME to see it and close itself. + * UI instead of allowing the IME to see it and close itself. Due to a bug, + * this function is not called for BACK key events on Android T and U, when + * the IME is shown. * * @param keyCode The value in event.getKeyCode(). * @param event Description of the key event. @@ -15929,13 +17606,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Implement this method to handle trackball motion events. The - * relative movement of the trackball since the last event + * Implement this method to handle trackball motion events. + *+ * The relative movement of the trackball since the last event * can be retrieve with {@link MotionEvent#getX MotionEvent.getX()} and * {@link MotionEvent#getY MotionEvent.getY()}. These are normalized so * that a movement of 1 corresponds to the user pressing one DPAD key (so * they will often be fractional values, representing the more fine-grained * movement information available from a trackball). + *
* * @param event The motion event. * @return True if the event was handled, false otherwise. @@ -15947,9 +17626,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Implement this method to handle generic motion events. *- * Generic motion events describe joystick movements, mouse hovers, track pad - * touches, scroll wheel movements and other input events. The - * {@link MotionEvent#getSource() source} of the motion event specifies + * Generic motion events describe joystick movements, hover events from mouse or stylus + * devices, trackpad touches, scroll wheel movements and other motion events not handled + * by {@link #onTouchEvent(MotionEvent)} or {@link #onTrackballEvent(MotionEvent)}. + * The {@link MotionEvent#getSource() source} of the motion event specifies * the class of input that was received. Implementations of this method * must examine the bits in the source before processing the event. * The following code example shows how this is done. @@ -15968,7 +17648,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { * switch (event.getAction()) { * case MotionEvent.ACTION_HOVER_MOVE: - * // process the mouse hover movement... + * // process the hover movement... * return true; * case MotionEvent.ACTION_SCROLL: * // process the scroll wheel movement... @@ -15989,9 +17669,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Dispatching hover events to {@link TouchDelegate} to improve accessibility. *
* This method is dispatching hover events to the delegate target to support explore by touch. - * Similar to {@link ViewGroup#dispatchTouchEvent}, this method send proper hover events to + * Similar to {@link ViewGroup#dispatchTouchEvent}, this method sends proper hover events to * the delegate target according to the pointer and the touch area of the delegate while touch - * exploration enabled. + * exploration is enabled. *
* * @param event The motion event dispatch to the delegate target. @@ -16023,17 +17703,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // hover events but receive accessibility focus, it should also not delegate to these // views when hovered. if (!oldHoveringTouchDelegate) { - if ((action == MotionEvent.ACTION_HOVER_ENTER - || action == MotionEvent.ACTION_HOVER_MOVE) - && !pointInHoveredChild(event) - && pointInDelegateRegion) { - mHoveringTouchDelegate = true; + if (removeChildHoverCheckForTouchExploration()) { + if ((action == MotionEvent.ACTION_HOVER_ENTER + || action == MotionEvent.ACTION_HOVER_MOVE) && pointInDelegateRegion) { + mHoveringTouchDelegate = true; + } + } else { + if ((action == MotionEvent.ACTION_HOVER_ENTER + || action == MotionEvent.ACTION_HOVER_MOVE) + && !pointInHoveredChild(event) + && pointInDelegateRegion) { + mHoveringTouchDelegate = true; + } } } else { - if (action == MotionEvent.ACTION_HOVER_EXIT - || (action == MotionEvent.ACTION_HOVER_MOVE + if (removeChildHoverCheckForTouchExploration()) { + if (action == MotionEvent.ACTION_HOVER_EXIT + || (action == MotionEvent.ACTION_HOVER_MOVE)) { + if (!pointInDelegateRegion) { + mHoveringTouchDelegate = false; + } + } + } else { + if (action == MotionEvent.ACTION_HOVER_EXIT + || (action == MotionEvent.ACTION_HOVER_MOVE && (pointInHoveredChild(event) || !pointInDelegateRegion))) { - mHoveringTouchDelegate = false; + mHoveringTouchDelegate = false; + } } } switch (action) { @@ -16355,7 +18051,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Implement this method to handle touch screen motion events. + * Implement this method to handle pointer events. + *+ * This method is called to handle motion events where pointers are down on + * the view. For example, this could include touchscreen touches, stylus + * touches, or click-and-drag events from a mouse. However, it is not called + * for motion events that do not involve pointers being down, such as hover + * events or mouse scroll wheel movements. *
* If this method is used to detect click actions, it is recommended that * the actions be performed by implementing and calling @@ -16580,6 +18282,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return false; } + /** + * Called by {@link #measure(int, int)} to check if the current frame presentation got + * delayed by an expensive view mesures during the input event dispatching. (e.g. scrolling) + */ + private boolean hasExpensiveMeasuresDuringInputEvent() { + final AttachInfo attachInfo = mAttachInfo; + if (attachInfo == null || attachInfo.mRootView == null) { + return false; + } + if (!attachInfo.mHandlingPointerEvent) { + return false; + } + final ViewFrameInfo info = attachInfo.mViewRootImpl.mViewFrameInfo; + final long durationFromVsyncTimeMs = (System.nanoTime() + - Choreographer.getInstance().getLastFrameTimeNanos()) / TimeUtils.NANOS_PER_MS; + return durationFromVsyncTimeMs > 3L || info.getAndIncreaseViewMeasuredCount() > 10; + } + /** * @hide */ @@ -16619,7 +18339,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return attachInfo.mHandler.hasCallbacks(mPendingCheckForLongPress); } - /** + /** * Remove the pending click action */ @UnsupportedAppUsage @@ -16714,6 +18434,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * This is similar to {@link View#requestUnbufferedDispatch(MotionEvent)}, but does not * automatically terminate, and allows the specification of arbitrary input source classes. * + *
Prior to {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, calling this method + * will not result in any behavioral changes when this View is not attached to a window. + * * @param source The combined input source class to request unbuffered dispatch for. All * events coming from these source classes will not be buffered. Set to * {@link InputDevice#SOURCE_CLASS_NONE} in order to return to default behaviour. @@ -16751,7 +18474,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, void setFlags(int flags, int mask) { final boolean accessibilityEnabled = AccessibilityManager.getInstance(mContext).isEnabled(); - final boolean oldIncludeForAccessibility = accessibilityEnabled && includeForAccessibility(); + final boolean oldIncludeForAccessibility = + accessibilityEnabled && includeForAccessibility(false); int old = mViewFlags; mViewFlags = (mViewFlags & ~mask) | (flags & mask); @@ -16977,7 +18701,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if ((changed & FOCUSABLE) != 0 || (changed & VISIBILITY_MASK) != 0 || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0 || (changed & CONTEXT_CLICKABLE) != 0) { - if (oldIncludeForAccessibility != includeForAccessibility()) { + if (oldIncludeForAccessibility != includeForAccessibility(false)) { notifySubtreeAccessibilityStateChangedIfNeeded(); } else { notifyViewAccessibilityStateChangedIfNeeded( @@ -17007,6 +18731,53 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + private HapticScrollFeedbackProvider getScrollFeedbackProvider() { + if (mScrollFeedbackProvider == null) { + mScrollFeedbackProvider = new HapticScrollFeedbackProvider(this, + ViewConfiguration.get(mContext), /* isFromView= */ true); + } + return mScrollFeedbackProvider; + } + + private void doRotaryProgressForScrollHaptics(MotionEvent rotaryEvent) { + final float axisScrollValue = rotaryEvent.getAxisValue(MotionEvent.AXIS_SCROLL); + final float verticalScrollFactor = + ViewConfiguration.get(mContext).getScaledVerticalScrollFactor(); + final int scrollAmount = -Math.round(axisScrollValue * verticalScrollFactor); + getScrollFeedbackProvider().onScrollProgress( + rotaryEvent.getDeviceId(), InputDevice.SOURCE_ROTARY_ENCODER, + MotionEvent.AXIS_SCROLL, scrollAmount); + } + + private void doRotaryLimitForScrollHaptics(MotionEvent rotaryEvent) { + final boolean isStart = rotaryEvent.getAxisValue(MotionEvent.AXIS_SCROLL) > 0; + getScrollFeedbackProvider().onScrollLimit( + rotaryEvent.getDeviceId(), InputDevice.SOURCE_ROTARY_ENCODER, + MotionEvent.AXIS_SCROLL, isStart); + } + + private void processScrollEventForRotaryEncoderHaptics() { + if ((mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_WAITING_FOR_SCROLL_EVENT) != 0) { + mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT; + mPrivateFlags4 &= ~PFLAG4_ROTARY_HAPTICS_WAITING_FOR_SCROLL_EVENT; + } + } + + /** + * Disables the rotary scroll feedback implementation of the View class. + * + *
Note that this does NOT disable all rotary scroll feedback; it just disables the logic + * implemented within the View class. The child implementation of the View may implement its own + * rotary scroll feedback logic or use {@link ScrollFeedbackProvider} to generate rotary scroll + * feedback. + */ + void disableRotaryScrollFeedback() { + // Force set PFLAG4_ROTARY_HAPTICS_DETERMINED to avoid recalculating + // PFLAG4_ROTARY_HAPTICS_ENABLED under any circumstance. + mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_DETERMINED; + mPrivateFlags4 &= ~PFLAG4_ROTARY_HAPTICS_ENABLED; + } + /** * This is called in response to an internal scroll in this view (i.e., the * view scrolled its own contents). This is typically as a result of @@ -17022,6 +18793,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, notifySubtreeAccessibilityStateChangedIfNeeded(); postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt); + processScrollEventForRotaryEncoderHaptics(); + mBackgroundSizeChanged = true; mDefaultFocusHighlightSizeChanged = true; if (mForegroundInfo != null) { @@ -17104,7 +18877,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * (but after its own view has been drawn). * @param canvas the canvas on which to draw the view */ - protected void dispatchDraw(Canvas canvas) { + protected void dispatchDraw(@NonNull Canvas canvas) { } @@ -18342,6 +20115,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @RemotableViewMethod public void setTranslationX(float translationX) { if (translationX != getTranslationX()) { + mPrivateFlags4 |= PFLAG4_HAS_MOVED; invalidateViewProperty(true, false); mRenderNode.setTranslationX(translationX); invalidateViewProperty(false, true); @@ -18378,6 +20152,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @RemotableViewMethod public void setTranslationY(float translationY) { if (translationY != getTranslationY()) { + mPrivateFlags4 |= PFLAG4_HAS_MOVED; invalidateViewProperty(true, false); mRenderNode.setTranslationY(translationY); invalidateViewProperty(false, true); @@ -18563,8 +20338,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #getOutlineProvider() */ public void setOutlineProvider(ViewOutlineProvider provider) { - mOutlineProvider = provider; - invalidateOutline(); + if (mOutlineProvider != provider) { + mOutlineProvider = provider; + invalidateOutline(); + } } /** @@ -19389,6 +21166,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { damageInParent(); } + mPrivateFlags4 |= PFLAG4_HAS_VIEW_PROPERTY_INVALIDATION; } /** @@ -20387,7 +22165,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, out.bottom = mScrollY + mBottom - mTop; } - private void onDrawScrollIndicators(Canvas c) { + private void onDrawScrollIndicators(@NonNull Canvas c) { if ((mPrivateFlags3 & SCROLL_INDICATORS_PFLAG3_MASK) == 0) { // No scroll indicators enabled. return; @@ -20496,21 +22274,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mRoundScrollbarRenderer == null) { getStraightVerticalScrollBarBounds(bounds, touchBounds); } else { - getRoundVerticalScrollBarBounds(bounds != null ? bounds : touchBounds); + mRoundScrollbarRenderer.getRoundVerticalScrollBarBounds( + bounds != null ? bounds : touchBounds); } } - private void getRoundVerticalScrollBarBounds(Rect bounds) { - final int width = mRight - mLeft; - final int height = mBottom - mTop; - // Do not take padding into account as we always want the scrollbars - // to hug the screen for round wearable devices. - bounds.left = mScrollX; - bounds.top = mScrollY; - bounds.right = bounds.left + width; - bounds.bottom = mScrollY + height; - } - private void getStraightVerticalScrollBarBounds(@Nullable Rect drawBounds, @Nullable Rect touchBounds) { final Rect bounds = drawBounds != null ? drawBounds : touchBounds; @@ -20571,7 +22339,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #awakenScrollBars(int) */ - protected final void onDrawScrollBars(Canvas canvas) { + protected final void onDrawScrollBars(@NonNull Canvas canvas) { // scrollbars are drawn only when the animation is running final ScrollabilityCache cache = mScrollCache; @@ -20620,8 +22388,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (drawVerticalScrollBar) { final Rect bounds = cache.mScrollBarBounds; getVerticalScrollBarBounds(bounds, null); + boolean shouldDrawScrollbarAtLeft = + (mVerticalScrollbarPosition == SCROLLBAR_POSITION_LEFT) + || (mVerticalScrollbarPosition == SCROLLBAR_POSITION_DEFAULT + && isLayoutRtl()); + mRoundScrollbarRenderer.drawRoundScrollbars( - canvas, (float) cache.scrollBar.getAlpha() / 255f, bounds); + canvas, (float) cache.scrollBar.getAlpha() / 255f, bounds, + shouldDrawScrollbarAtLeft); if (invalidate) { invalidate(); } @@ -20683,7 +22457,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar, + protected void onDrawHorizontalScrollBar(@NonNull Canvas canvas, Drawable scrollBar, int l, int t, int r, int b) { scrollBar.setBounds(l, t, r, b); scrollBar.draw(canvas); @@ -20703,7 +22477,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ @UnsupportedAppUsage - protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, + protected void onDrawVerticalScrollBar(@NonNull Canvas canvas, Drawable scrollBar, int l, int t, int r, int b) { scrollBar.setBounds(l, t, r, b); scrollBar.draw(canvas); @@ -20714,7 +22488,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @param canvas the canvas on which the background will be drawn */ - protected void onDraw(Canvas canvas) { + protected void onDraw(@NonNull Canvas canvas) { } /* @@ -20761,6 +22535,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (isFocused()) { notifyFocusChangeToImeFocusController(true /* hasFocus */); } + + if (sTraceLayoutSteps) { + setTraversalTracingEnabled(true); + } + if (sTraceRequestLayoutClass != null + && sTraceRequestLayoutClass.equals(getClass().getSimpleName())) { + setRelayoutTracingEnabled(true); + } } /** @@ -20875,8 +22657,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * RTL not supported) */ private boolean isRtlCompatibilityMode() { - final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; - return targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1 || !hasRtlSupport(); + return !hasRtlSupport(); } /** @@ -21143,6 +22924,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mCurrentAnimation = null; if ((mViewFlags & TOOLTIP) == TOOLTIP) { + removeCallbacks(mTooltipInfo.mShowTooltipRunnable); + removeCallbacks(mTooltipInfo.mHideTooltipRunnable); hideTooltip(); } @@ -21175,6 +22958,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Retrieve a unique token identifying the window this view is attached to. * @return Return the window's token for use in * {@link WindowManager.LayoutParams#token WindowManager.LayoutParams.token}. + * This token maybe null if this view is not attached to a window. + * @see #isAttachedToWindow() for current window attach state + * @see OnAttachStateChangeListener to listen to window attach/detach state changes */ public IBinder getWindowToken() { return mAttachInfo != null ? mAttachInfo.mWindowToken : null; @@ -21258,6 +23044,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return Math.max(vis1, vis2); } + private boolean mShouldFakeFocus = false; + + /** + * Fake send a focus event after attaching to window. + * See {@link android.view.ViewRootImpl#dispatchCompatFakeFocus()} for details. + * @hide + */ + public void fakeFocusAfterAttachingToWindow() { + mShouldFakeFocus = true; + } + /** * @param info the {@link android.view.View.AttachInfo} to associated with * this view @@ -21326,6 +23123,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, notifyEnterOrExitForAutoFillIfNeeded(true); notifyAppearedOrDisappearedForContentCaptureIfNeeded(true); + + if (mShouldFakeFocus) { + getViewRootImpl().dispatchCompatFakeFocus(); + mShouldFakeFocus = false; + } } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) @@ -21339,6 +23141,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Invoking onVisibilityAggregated directly here since the subtree // will also receive detached from window onVisibilityAggregated(false); + } else { + notifyAutofillManagerViewVisibilityChanged(false); } } } @@ -21369,6 +23173,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } notifyAppearedOrDisappearedForContentCaptureIfNeeded(false); + updateSensitiveViewsCountIfNeeded(false); mAttachInfo = null; if (mOverlay != null) { @@ -21748,6 +23553,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + /** + * Configure the {@link android.graphics.RenderEffect} to apply to the backdrop contents of this + * View. This will apply a visual effect to the result of the backdrop contents of this View + * before it is drawn. For example if + * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, Shader.TileMode)} + * is provided, the previous content behind this View will be blurred before this View is drawn. + * @param renderEffect to be applied to the View. Passing null clears the previously configured + * {@link RenderEffect} + * @hide + */ + public void setBackdropRenderEffect(@Nullable RenderEffect renderEffect) { + if (mRenderNode.setBackdropRenderEffect(renderEffect)) { + invalidateViewProperty(true, true); + } + } + /** * Updates the {@link Paint} object used with the current layer (used only if the current * layer type is not set to {@link #LAYER_TYPE_NONE}). Changed properties of the Paint @@ -21807,6 +23628,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @EnumEntry(value = LAYER_TYPE_SOFTWARE, name = "software"), @EnumEntry(value = LAYER_TYPE_HARDWARE, name = "hardware") }) + @ViewDebug.ExportedProperty(category = "drawing", mapping = { + @ViewDebug.IntToString(from = LAYER_TYPE_NONE, to = "NONE"), + @ViewDebug.IntToString(from = LAYER_TYPE_SOFTWARE, to = "SOFTWARE"), + @ViewDebug.IntToString(from = LAYER_TYPE_HARDWARE, to = "HARDWARE") + }) @LayerType public int getLayerType() { return mLayerType; @@ -21850,6 +23676,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + /** + * Determines whether an unprocessed input event is available on the window. + * + * This is only a performance hint (a.k.a. the Input Hint) and may return false negative + * results. Callers should not rely on availability of the input event based on the return + * value of this method. + * + * The Input Hint functionality is experimental, and can be removed in the future OS releases. + * + * This method only returns nontrivial results on a View that is attached to a Window. Such View + * can be acquired using `Activity.getWindow().getDecorView()`, and only after the view + * hierarchy is attached (via {@link android.app.Activity#setContentView(android.view.View)}). + * + * In multi-window mode the View can provide the Input Hint only for the window it is attached + * to. Therefore, checking input availability for the whole application would require asking + * for the hint from more than one View. + * + * The initial implementation does not return false positives, but callers should not rely on + * it: false positives may occur in future OS releases. + * + * @hide + */ + public boolean probablyHasInput() { + ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl == null) { + return false; + } + return viewRootImpl.probablyHasInput(); + } + /** * Destroys all hardware rendering resources. This method is invoked * when the system needs to reclaim resources. Upon execution of this @@ -22048,6 +23904,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; + mPrivateFlags4 |= PFLAG4_HAS_DRAWN; + // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); @@ -22061,15 +23919,32 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { draw(canvas); } + + // For VRR to vote the preferred frame rate + if (sToolkitSetFrameRateReadOnlyFlagValue + && sToolkitFrameRateViewEnablingReadOnlyFlagValue) { + votePreferredFrameRate(); + } } } finally { renderNode.endRecording(); setDisplayListProperties(renderNode); } } else { + if ((mPrivateFlags4 & PFLAG4_HAS_VIEW_PROPERTY_INVALIDATION) + == PFLAG4_HAS_VIEW_PROPERTY_INVALIDATION) { + // For VRR to vote the preferred frame rate + if (sToolkitSetFrameRateReadOnlyFlagValue + && sToolkitFrameRateViewEnablingReadOnlyFlagValue) { + votePreferredFrameRate(); + } + mPrivateFlags4 &= ~PFLAG4_HAS_VIEW_PROPERTY_INVALIDATION; + } mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; } + mPrivateFlags4 &= ~PFLAG4_HAS_MOVED; + mFrameContentVelocity = -1; return renderNode; } @@ -22311,14 +24186,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void buildDrawingCache(boolean autoScale) { if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ? mDrawingCache == null : mUnscaledDrawingCache == null)) { - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, + if (Trace.isTagEnabled(TRACE_TAG_VIEW)) { + Trace.traceBegin(TRACE_TAG_VIEW, "buildDrawingCache/SW Layer for " + getClass().getSimpleName()); } try { buildDrawingCacheImpl(autoScale); } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Trace.traceEnd(TRACE_TAG_VIEW); } } } @@ -22747,6 +24622,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidationTransform = t; } + // Increase the frame rate if there is a transformation that applies a matrix. + if (sToolkitFrameRateAnimationBugfix25q1FlagValue + && ((t.getTransformationType() & Transformation.TYPE_MATRIX) != 0)) { + mPrivateFlags4 |= PFLAG4_HAS_VIEW_PROPERTY_INVALIDATION; + mPrivateFlags4 |= PFLAG4_HAS_MOVED; + } + if (more) { if (!a.willChangeBounds()) { if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) == @@ -22829,7 +24711,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ - protected final boolean drawsWithRenderNode(Canvas canvas) { + protected final boolean drawsWithRenderNode(@NonNull Canvas canvas) { return mAttachInfo != null && mAttachInfo.mHardwareAccelerated && canvas.isHardwareAccelerated(); @@ -22841,7 +24723,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * This is where the View specializes rendering behavior based on layer type, * and hardware acceleration. */ - boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { + boolean draw(@NonNull Canvas canvas, ViewGroup parent, long drawingTime) { final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated(); @@ -23129,7 +25011,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return (int) (dips * scale + 0.5f); } - final private void debugDrawFocus(Canvas canvas) { + private void debugDrawFocus(@NonNull Canvas canvas) { if (isFocused()) { final int cornerSquareSize = dipsToPixels(DEBUG_CORNERS_SIZE_DIP); final int l = mScrollX; @@ -23164,7 +25046,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param canvas The Canvas to which the View is rendered. */ @CallSuper - public void draw(Canvas canvas) { + public void draw(@NonNull Canvas canvas) { final int privateFlags = mPrivateFlags; mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; @@ -23399,7 +25281,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param canvas Canvas on which to draw the background */ @UnsupportedAppUsage - private void drawBackground(Canvas canvas) { + private void drawBackground(@NonNull Canvas canvas) { final Drawable background = mBackground; if (background == null) { return; @@ -23644,6 +25526,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical(); } + /** + * Enable measure/layout debugging on traces. + * + * @see Trace + * @hide + */ + public static void setTraceLayoutSteps(boolean traceLayoutSteps) { + sTraceLayoutSteps = traceLayoutSteps; + } + + /** + * Enable request layout tracing classes with {@code s} simple name. + *
+ * When set, a {@link Trace} instant event and a log with the stacktrace is emitted every
+ * time a requestLayout of a class matching {@code s} name happens.
+ * This applies only to views attached from this point onwards.
+ *
+ * @see Trace#instant(long, String)
+ * @hide
+ */
+ public static void setTracedRequestLayoutClassClass(String s) {
+ sTraceRequestLayoutClass = s;
+ }
+
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
@@ -23678,7 +25584,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
+ if (isTraversalTracingEnabled()) {
+ Trace.beginSection(mTracingStrings.onMeasureBeforeLayout);
+ }
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
+ if (isTraversalTracingEnabled()) {
+ Trace.endSection();
+ }
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
@@ -23691,7 +25603,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
+ if (isTraversalTracingEnabled()) {
+ Trace.beginSection(mTracingStrings.onLayout);
+ }
onLayout(changed, l, t, r, b);
+ if (isTraversalTracingEnabled()) {
+ Trace.endSection();
+ }
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
@@ -23879,6 +25797,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
+ if (mAttachInfo != null && sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
+ boolean isSmall;
+ if (sToolkitFrameRateSmallUsesPercentReadOnlyFlagValue) {
+ int size = newWidth * newHeight;
+ float percent = size / mAttachInfo.mDisplayPixelCount;
+ isSmall = percent <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD;
+ } else {
+ float density = mAttachInfo.mDensity;
+ int narrowSize = (int) (density * FRAME_RATE_NARROW_SIZE_DP);
+ int smallSize = (int) (density * FRAME_RATE_SQUARE_SMALL_SIZE_DP);
+ isSmall = newWidth <= narrowSize || newHeight <= narrowSize
+ || (newWidth <= smallSize && newHeight <= smallSize);
+ }
+ if (isSmall) {
+ int category = sToolkitFrameRateBySizeReadOnlyFlagValue
+ ? FRAME_RATE_CATEGORY_LOW : FRAME_RATE_CATEGORY_NORMAL;
+ mSizeBasedFrameRateCategoryAndReason = category | FRAME_RATE_CATEGORY_REASON_SMALL;
+ } else {
+ int category = sToolkitFrameRateDefaultNormalReadOnlyFlagValue
+ ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
+ mSizeBasedFrameRateCategoryAndReason = category | FRAME_RATE_CATEGORY_REASON_LARGE;
+ }
+ mPrivateFlags4 |= PFLAG4_HAS_MOVED;
+ }
+
onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
if (mOverlay != null) {
mOverlay.getOverlayView().setRight(newWidth);
@@ -23904,6 +25847,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
rebuildOutline();
+ if (onCheckIsTextEditor() || mHandwritingDelegatorCallback != null) {
+ setHandwritingArea(new Rect(0, 0, newWidth, newHeight));
+ }
}
/**
@@ -24296,7 +26242,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Draw the default focus highlight onto the canvas if there is one and this view is focused.
* @param canvas the canvas where we're drawing the highlight.
*/
- private void drawDefaultFocusHighlight(Canvas canvas) {
+ private void drawDefaultFocusHighlight(@NonNull Canvas canvas) {
if (mDefaultFocusHighlight != null && isFocused()) {
if (mDefaultFocusHighlightSizeChanged) {
mDefaultFocusHighlightSizeChanged = false;
@@ -25094,7 +27040,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @param canvas canvas to draw into
*/
- public void onDrawForeground(Canvas canvas) {
+ public void onDrawForeground(@NonNull Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
@@ -25636,6 +27582,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+ /**
+ * Modifiers the input matrix such that it maps root view's coordinates to view-local
+ * coordinates.
+ *
+ * @param matrix input matrix to modify
+ * @hide
+ */
+ public void transformMatrixRootToLocal(@NonNull Matrix matrix) {
+ final ViewParent parent = mParent;
+ if (parent instanceof final View vp) {
+ vp.transformMatrixRootToLocal(matrix);
+ matrix.postTranslate(vp.mScrollX, vp.mScrollY);
+ }
+ // This method is different from transformMatrixToLocal that it doesn't perform any
+ // transformation for ViewRootImpl
+
+ matrix.postTranslate(-mLeft, -mTop);
+
+ if (!hasIdentityMatrix()) {
+ matrix.postConcat(getInverseMatrix());
+ }
+ }
+
/**
* @hide
*/
@@ -25684,6 +27653,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (info != null) {
outLocation[0] += info.mWindowLeft;
outLocation[1] += info.mWindowTop;
+ // If OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS override is enabled,
+ // applyViewLocationSandboxingIfNeeded sandboxes outLocation within window bounds.
+ info.mViewRootImpl.applyViewLocationSandboxingIfNeeded(outLocation);
}
}
@@ -25821,7 +27793,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return a view with given ID if found, or {@code null} otherwise
* @see View#requireViewById(int)
*/
- @Nullable
+ // Strictly speaking this should be marked as @Nullable but the nullability of the return value
+ // is deliberately left unspecified as idiomatically correct code can make assumptions either
+ // way based on local context, e.g. layout specification.
public final Provide haptic feedback to the user for this view.
+ *
+ * Call this method (vs {@link #performHapticFeedback(int)}) to specify more details about
+ * the {@link InputDevice} that caused this haptic feedback. The framework will choose and
+ * provide a haptic feedback based on these details.
+ *
+ * The feedback will only be performed if {@link #isHapticFeedbackEnabled()} is {@code true}.
+ *
+ * @param feedbackConstant One of the constants defined in {@link HapticFeedbackConstants}.
+ * @param inputDeviceId The ID of the {@link InputDevice} that generated the event which
+ * triggered this haptic feedback request.
+ * @param inputSource The input source of the event which triggered this haptic feedback
+ * request, defined as {@code InputDevice#SOURCE_*}.
+ *
+ * @hide
+ */
+ public void performHapticFeedbackForInputDevice(int feedbackConstant, int inputDeviceId,
+ int inputSource, int flags) {
+ if (isPerformHapticFeedbackSuppressed(feedbackConstant, flags)) {
+ return;
+ }
+
+ int privFlags = computeHapticFeedbackPrivateFlags();
+ mAttachInfo.mRootCallbacks.performHapticFeedbackForInputDevice(
+ feedbackConstant, inputDeviceId, inputSource, flags, privFlags);
+ }
+
+ private boolean isPerformHapticFeedbackSuppressed(int feedbackConstant, int flags) {
+ if (feedbackConstant == HapticFeedbackConstants.NO_HAPTICS
+ || mAttachInfo == null
+ || mAttachInfo.mSession == null) {
+ return true;
+ }
//noinspection SimplifiableIfStatement
if ((flags & HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0
&& !isHapticFeedbackEnabled()) {
- return false;
+ return true;
+ }
+ return false;
+ }
+
+ private int computeHapticFeedbackPrivateFlags() {
+ int privFlags = 0;
+ if (mAttachInfo.mViewRootImpl != null
+ && mAttachInfo.mViewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD) {
+ privFlags = HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS;
}
- return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant,
- (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0);
+ return privFlags;
}
/**
@@ -27152,7 +29234,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @param canvas A {@link android.graphics.Canvas} object in which to draw the shadow image.
*/
- public void onDrawShadow(Canvas canvas) {
+ public void onDrawShadow(@NonNull Canvas canvas) {
final View view = mView.get();
if (view != null) {
view.draw(canvas);
@@ -27236,9 +29318,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
Log.w(VIEW_LOG_TAG, "startDragAndDrop called with an invalid surface.");
return false;
}
+ if ((flags & DRAG_FLAG_GLOBAL) != 0 && ((flags & DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0)) {
+ Log.w(VIEW_LOG_TAG, "startDragAndDrop called with both DRAG_FLAG_GLOBAL "
+ + "and DRAG_FLAG_GLOBAL_SAME_APPLICATION, the drag will default to "
+ + "DRAG_FLAG_GLOBAL_SAME_APPLICATION");
+ flags &= ~DRAG_FLAG_GLOBAL;
+ }
if (data != null) {
- data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0);
+ if (com.android.window.flags.Flags.delegateUnhandledDrags()) {
+ data.prepareToLeaveProcess(
+ (flags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) != 0);
+ if ((flags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) != 0) {
+ if (!hasActivityPendingIntents(data)) {
+ // Reset the flag if there is no launchable activity intent
+ flags &= ~DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;
+ Log.w(VIEW_LOG_TAG, "startDragAndDrop called with "
+ + "DRAG_FLAG_START_INTENT_ON_UNHANDLED_DRAG but the clip data "
+ + "contains non-activity PendingIntents");
+ }
+ }
+ } else {
+ data.prepareToLeaveProcess((flags & DRAG_FLAG_GLOBAL) != 0);
+ }
}
Rect bounds = new Rect();
@@ -27255,6 +29357,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
IBinder token = mAttachInfo.mSession.performDrag(
mAttachInfo.mWindow, flags, null,
mAttachInfo.mViewRootImpl.getLastTouchSource(),
+ mAttachInfo.mViewRootImpl.getLastTouchDeviceId(),
+ mAttachInfo.mViewRootImpl.getLastTouchPointerId(),
0f, 0f, 0f, 0f, data);
if (ViewDebug.DEBUG_DRAG) {
Log.d(VIEW_LOG_TAG, "startDragAndDrop via a11y action returned " + token);
@@ -27262,6 +29366,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (token != null) {
root.setLocalDragState(myLocalState);
mAttachInfo.mDragToken = token;
+ mAttachInfo.mDragData = data;
mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
setAccessibilityDragStarted(true);
}
@@ -27280,6 +29385,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|| (shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) {
throw new IllegalStateException("Drag shadow dimensions must not be negative");
}
+ final float overrideInvScale = CompatibilityInfo.getOverrideInvertedScale();
+ if (overrideInvScale != 1f) {
+ shadowTouchPoint.x = (int) (shadowTouchPoint.x / overrideInvScale);
+ shadowTouchPoint.y = (int) (shadowTouchPoint.y / overrideInvScale);
+ }
// Create 1x1 surface when zero surface size is specified because SurfaceControl.Builder
// does not accept zero size surface.
@@ -27296,18 +29406,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
+ " shadowX=" + shadowTouchPoint.x + " shadowY=" + shadowTouchPoint.y);
}
- final SurfaceSession session = new SurfaceSession();
- final SurfaceControl surfaceControl = new SurfaceControl.Builder(session)
+ final SurfaceControl surfaceControl = new SurfaceControl.Builder()
.setName("drag surface")
.setParent(root.getSurfaceControl())
.setBufferSize(shadowSize.x, shadowSize.y)
.setFormat(PixelFormat.TRANSLUCENT)
.setCallsite("View.startDragAndDrop")
.build();
+ if (overrideInvScale != 1f) {
+ final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ transaction.setMatrix(surfaceControl, 1 / overrideInvScale, 0, 0, 1 / overrideInvScale)
+ .apply();
+ }
final Surface surface = new Surface();
surface.copyFrom(surfaceControl);
IBinder token = null;
try {
+ Trace.traceBegin(TRACE_TAG_VIEW, "startDragAndDrop#drawDragShadow");
final Canvas canvas = isHardwareAccelerated()
? surface.lockHardwareCanvas()
: surface.lockCanvas(null);
@@ -27316,28 +29431,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
shadowBuilder.onDrawShadow(canvas);
} finally {
surface.unlockCanvasAndPost(canvas);
+ Trace.traceEnd(TRACE_TAG_VIEW);
}
- token = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, flags, surfaceControl,
- root.getLastTouchSource(), lastTouchPoint.x, lastTouchPoint.y,
- shadowTouchPoint.x, shadowTouchPoint.y, data);
- if (ViewDebug.DEBUG_DRAG) {
- Log.d(VIEW_LOG_TAG, "performDrag returned " + token);
- }
- if (token != null) {
- if (mAttachInfo.mDragSurface != null) {
- mAttachInfo.mDragSurface.release();
+ Trace.traceBegin(TRACE_TAG_VIEW, "startDragAndDrop#performDrag");
+ try {
+ token = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, flags, surfaceControl,
+ root.getLastTouchSource(), root.getLastTouchDeviceId(),
+ root.getLastTouchPointerId(), lastTouchPoint.x, lastTouchPoint.y,
+ shadowTouchPoint.x, shadowTouchPoint.y, data);
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "performDrag returned " + token);
}
- mAttachInfo.mDragSurface = surface;
- mAttachInfo.mDragToken = token;
- // Cache the local state object for delivery with DragEvents
- root.setLocalDragState(myLocalState);
- if (a11yEnabled) {
- // Set for AccessibilityEvents
- mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
+ if (token != null) {
+ if (mAttachInfo.mDragSurface != null) {
+ mAttachInfo.mDragSurface.release();
+ }
+ if (mAttachInfo.mDragData != null) {
+ // Clean up previous drag data intents
+ View.cleanUpPendingIntents(mAttachInfo.mDragData);
+ }
+ mAttachInfo.mDragSurface = surface;
+ mAttachInfo.mDragToken = token;
+ mAttachInfo.mDragData = data;
+ // Cache the local state object for delivery with DragEvents
+ root.setLocalDragState(myLocalState);
+ if (a11yEnabled) {
+ // Set for AccessibilityEvents
+ mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
+ }
}
+ return token != null;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_VIEW);
}
- return token != null;
} catch (Exception e) {
Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
return false;
@@ -27345,7 +29472,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (token == null) {
surface.destroy();
}
- session.kill();
+ surfaceControl.release();
+ }
+ }
+
+ /**
+ * Checks if this clip data has a pending intent that is an activity type.
+ * @hide
+ */
+ static boolean hasActivityPendingIntents(ClipData data) {
+ final int size = data.getItemCount();
+ for (int i = 0; i < size; i++) {
+ final ClipData.Item item = data.getItemAt(i);
+ if (item.getIntentSender() != null) {
+ final PendingIntent pi = new PendingIntent(item.getIntentSender().getTarget());
+ if (pi.isActivity()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Cleans up all pending intents in the ClipData.
+ * @hide
+ */
+ static void cleanUpPendingIntents(ClipData data) {
+ final int size = data.getItemCount();
+ for (int i = 0; i < size; i++) {
+ final ClipData.Item item = data.getItemAt(i);
+ if (item.getIntentSender() != null) {
+ final PendingIntent pi = new PendingIntent(item.getIntentSender().getTarget());
+ pi.cancel();
+ }
}
}
@@ -28709,6 +30869,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+ // Note that if the function returns true, it indicates aapt did not generate this id.
+ // However false value does not indicate that aapt did generated this id.
private static boolean isViewIdGenerated(int id) {
return (id & 0xFF000000) == 0 && (id & 0x00FFFFFF) != 0;
}
@@ -28741,45 +30903,87 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Returns the pointer icon for the motion event, or null if it doesn't specify the icon.
- * The default implementation does not care the location or event types, but some subclasses
- * may use it (such as WebViews).
- * @param event The MotionEvent from a mouse
- * @param pointerIndex The index of the pointer for which to retrieve the {@link PointerIcon}.
- * This will be between 0 and {@link MotionEvent#getPointerCount()}.
+ * Resolve the pointer icon that should be used for specified pointer in the motion event.
+ *
+ * The default implementation will resolve the pointer icon to one set using
+ * {@link #setPointerIcon(PointerIcon)} for mouse devices. Subclasses may override this to
+ * customize the icon for the given pointer.
+ *
+ * For example, to always show the PointerIcon.TYPE_HANDWRITING icon for a stylus pointer,
+ * the event can be resolved in the following way:
+ * See {@link #isCredential()}.
+ *
+ * @param isCredential Whether the view is a credential.
+ *
+ * @attr ref android.R.styleable#View_isCredential
+ */
+ public void setIsCredential(boolean isCredential) {
+ if (isCredential) {
+ mPrivateFlags4 |= PFLAG4_IMPORTANT_FOR_CREDENTIAL_MANAGER;
+ } else {
+ mPrivateFlags4 &= ~PFLAG4_IMPORTANT_FOR_CREDENTIAL_MANAGER;
+ }
+ }
+
+ /**
+ * Gets the mode for determining whether this view is a credential.
+ *
+ * See {@link #setIsCredential(boolean)}.
+ *
+ * @return false by default, or value passed to {@link #setIsCredential(boolean)}.
+ *
+ * @attr ref android.R.styleable#View_isCredential
+ */
+ public boolean isCredential() {
+ return ((mPrivateFlags4 & PFLAG4_IMPORTANT_FOR_CREDENTIAL_MANAGER)
+ == PFLAG4_IMPORTANT_FOR_CREDENTIAL_MANAGER);
+ }
+
+ // TODO(316208691): Revive following removed API docs.
+ // @see EditorInfo#setStylusHandwritingEnabled(boolean)
/**
* Set whether this view enables automatic handwriting initiation.
*
@@ -31500,6 +33844,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* {@link android.view.inputmethod.InputMethodManager#startStylusHandwriting(View)} when there
* is stylus movement detected.
*
+ * Note that this attribute has no effect on the View's children. For example, if a
+ * {@link ViewGroup} disables auto handwriting but its children set auto handwriting to true,
+ * auto handwriting will still work for the children, and vice versa.
+ *
* @see #onCreateInputConnection(EditorInfo)
* @see android.view.inputmethod.InputMethodManager#startStylusHandwriting(View)
* @param enabled whether auto handwriting initiation is enabled for this view.
@@ -31517,7 +33865,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Return whether the View allows automatic handwriting initiation. Returns true if automatic
- * handwriting initiation is enabled, and verse visa.
+ * handwriting initiation is enabled, and vice versa.
* @see #setAutoHandwritingEnabled(boolean)
*/
public boolean isAutoHandwritingEnabled() {
@@ -31525,6 +33873,47 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
== PFLAG4_AUTO_HANDWRITING_ENABLED;
}
+ /**
+ * Return whether the stylus handwriting is available for this View.
+ * @hide
+ */
+ public boolean isStylusHandwritingAvailable() {
+ return getContext().getSystemService(InputMethodManager.class)
+ .isStylusHandwritingAvailable();
+ }
+
+ private void setTraversalTracingEnabled(boolean enabled) {
+ if (enabled) {
+ if (mTracingStrings == null) {
+ mTracingStrings = new ViewTraversalTracingStrings(this);
+ }
+ mPrivateFlags4 |= PFLAG4_TRAVERSAL_TRACING_ENABLED;
+ } else {
+ mPrivateFlags4 &= ~PFLAG4_TRAVERSAL_TRACING_ENABLED;
+ }
+ }
+
+ private boolean isTraversalTracingEnabled() {
+ return (mPrivateFlags4 & PFLAG4_TRAVERSAL_TRACING_ENABLED)
+ == PFLAG4_TRAVERSAL_TRACING_ENABLED;
+ }
+
+ private void setRelayoutTracingEnabled(boolean enabled) {
+ if (enabled) {
+ if (mTracingStrings == null) {
+ mTracingStrings = new ViewTraversalTracingStrings(this);
+ }
+ mPrivateFlags4 |= PFLAG4_RELAYOUT_TRACING_ENABLED;
+ } else {
+ mPrivateFlags4 &= ~PFLAG4_RELAYOUT_TRACING_ENABLED;
+ }
+ }
+
+ private boolean isRelayoutTracingEnabled() {
+ return (mPrivateFlags4 & PFLAG4_RELAYOUT_TRACING_ENABLED)
+ == PFLAG4_RELAYOUT_TRACING_ENABLED;
+ }
+
/**
* Collects a {@link ViewTranslationRequest} which represents the content to be translated in
* the view.
@@ -31773,7 +34162,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
RemoteCallback remoteCallback = new RemoteCallback(result ->
executor.execute(() -> {
- DisplayHash displayHash = result.getParcelable(EXTRA_DISPLAY_HASH);
+ DisplayHash displayHash = result.getParcelable(EXTRA_DISPLAY_HASH, android.view.displayhash.DisplayHash.class);
int errorCode = result.getInt(EXTRA_DISPLAY_HASH_ERROR_CODE,
DISPLAY_HASH_ERROR_UNKNOWN);
if (displayHash != null) {
@@ -31806,4 +34195,282 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
return null;
}
+
+ /**
+ * Used to calculate the frame rate category of a View.
+ *
+ * @hide
+ */
+ protected int calculateFrameRateCategory() {
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ ViewParent parent = mParent;
+ boolean isInputMethodWindowType =
+ viewRootImpl.mWindowAttributes.type == TYPE_INPUT_METHOD;
+
+ // boost frame rate when the position or the size changed.
+ if (((mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == (
+ PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN) || mLastFrameLeft != mLeft
+ || mLastFrameTop != mTop)
+ && viewRootImpl.shouldCheckFrameRateCategory()
+ && parent instanceof View
+ && ((View) parent).getFrameContentVelocity() <= 0
+ && !isInputMethodWindowType
+ && viewRootImpl.getFrameRateCompatibility() != FRAME_RATE_COMPATIBILITY_AT_LEAST) {
+
+ return FRAME_RATE_CATEGORY_HIGH_HINT | FRAME_RATE_CATEGORY_REASON_BOOST;
+ }
+ int category;
+ switch (viewRootImpl.intermittentUpdateState()) {
+ case ViewRootImpl.INTERMITTENT_STATE_INTERMITTENT -> {
+ if (!sToolkitFrameRateBySizeReadOnlyFlagValue) {
+ category = FRAME_RATE_CATEGORY_NORMAL;
+ } else {
+ // The size based frame rate category can only be LOW or NORMAL. If the size
+ // based frame rate category is LOW, we shouldn't vote for NORMAL for
+ // intermittent.
+ category = Math.min(
+ mSizeBasedFrameRateCategoryAndReason & ~FRAME_RATE_CATEGORY_REASON_MASK,
+ FRAME_RATE_CATEGORY_NORMAL);
+ }
+ category |= FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
+ }
+ case ViewRootImpl.INTERMITTENT_STATE_NOT_INTERMITTENT ->
+ category = mSizeBasedFrameRateCategoryAndReason;
+ default -> category = mLastFrameRateCategory;
+ }
+ return category;
+ }
+
+ /**
+ * Used to vote the preferred frame rate and frame rate category to ViewRootImpl
+ *
+ * @hide
+ */
+ protected void votePreferredFrameRate() {
+ // use toolkitSetFrameRate flag to gate the change
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl == null) {
+ return; // can't vote if not connected
+ }
+ float velocity = mFrameContentVelocity;
+ final float frameRate = mPreferredFrameRate;
+
+ if (viewRootImpl.shouldCheckFrameRate(frameRate > 0f)
+ && (frameRate > 0 || (mAttachInfo.mViewVelocityApi && velocity > 0f))) {
+ float velocityFrameRate = 0f;
+ if (mAttachInfo.mViewVelocityApi && velocity > 0f) {
+ velocityFrameRate = convertVelocityToFrameRate(velocity);
+ }
+ int compatibility;
+ float frameRateToSet;
+ if (frameRate >= velocityFrameRate) {
+ compatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+ frameRateToSet = frameRate;
+ } else {
+ compatibility = FRAME_RATE_COMPATIBILITY_AT_LEAST;
+ frameRateToSet = velocityFrameRate;
+ }
+ viewRootImpl.votePreferredFrameRate(frameRateToSet, compatibility);
+ }
+
+ if (viewRootImpl.shouldCheckFrameRateCategory()) {
+ if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+ int width = mRight - mLeft;
+ int height = mBottom - mTop;
+ float sizePercentage = width * height / mAttachInfo.mDisplayPixelCount;
+ viewRootImpl.recordViewPercentage(sizePercentage);
+ }
+
+ int frameRateCategory;
+ if (Float.isNaN(frameRate)) {
+ frameRateCategory = calculateFrameRateCategory();
+ } else if (frameRate < 0) {
+ switch ((int) frameRate) {
+ case (int) REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE ->
+ frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ case (int) REQUESTED_FRAME_RATE_CATEGORY_LOW ->
+ frameRateCategory = FRAME_RATE_CATEGORY_LOW
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ case (int) REQUESTED_FRAME_RATE_CATEGORY_NORMAL ->
+ frameRateCategory = FRAME_RATE_CATEGORY_NORMAL
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ case (int) REQUESTED_FRAME_RATE_CATEGORY_HIGH ->
+ frameRateCategory = FRAME_RATE_CATEGORY_HIGH
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ default -> {
+ // invalid frame rate, use default
+ int category = sToolkitFrameRateDefaultNormalReadOnlyFlagValue
+ ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
+ frameRateCategory = category
+ | FRAME_RATE_CATEGORY_REASON_INVALID;
+ }
+ }
+ } else {
+ // Category doesn't control it. It is directly controlled by frame rate
+ frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ }
+
+ int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
+ int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
+ viewRootImpl.votePreferredFrameRateCategory(category, reason, this);
+ mLastFrameRateCategory = frameRateCategory;
+ }
+ mLastFrameLeft = mLeft;
+ mLastFrameTop = mTop;
+ }
+
+ private float convertVelocityToFrameRate(float velocityPps) {
+ // Internal testing has shown that this gives a premium experience:
+ // above 300dp/s => 120fps
+ // between 300dp/s and 125fps => 80fps
+ // below 125dp/s => 60fps
+ float density = mAttachInfo.mDensity;
+ float velocityDps = velocityPps / density;
+ float frameRate;
+ if (velocityDps > 300f) {
+ frameRate = MAX_FRAME_RATE; // Use maximum at fast motion
+ } else if (velocityDps > 125f) {
+ frameRate = 80f; // Use medium frame rate when motion is slower
+ } else {
+ frameRate = 60f; // Use minimum frame rate when motion is very slow
+ }
+ return frameRate;
+ }
+
+ /**
+ * Set the current velocity of the View, we only track positive value.
+ * We will use the velocity information to adjust the frame rate when applicable.
+ * For example, we could potentially lower the frame rate when
+ * the velocity of a fling gesture becomes slower.
+ * Note that this is only valid till the next drawn frame.
+ *
+ * @param pixelsPerSecond how many pixels move per second.
+ */
+ @FlaggedApi(FLAG_VIEW_VELOCITY_API)
+ public void setFrameContentVelocity(float pixelsPerSecond) {
+ if (mAttachInfo != null && mAttachInfo.mViewVelocityApi) {
+ mFrameContentVelocity = Math.abs(pixelsPerSecond);
+
+ if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+ Trace.setCounter("Set frame velocity", (long) mFrameContentVelocity);
+ }
+ }
+ }
+
+ /**
+ * Get the current velocity of the View.
+ * The value should always be greater than or equal to 0.
+ * Note that this is only valid till the next drawn frame.
+ *
+ * @return 0 by default, or value passed to {@link #setFrameContentVelocity(float)}.
+ */
+ @FlaggedApi(FLAG_VIEW_VELOCITY_API)
+ public float getFrameContentVelocity() {
+ if (mAttachInfo != null && mAttachInfo.mViewVelocityApi) {
+ return Math.max(mFrameContentVelocity, 0f);
+ }
+ return 0;
+ }
+
+ /**
+ * You can set the preferred frame rate for a View using a positive number
+ * or by specifying the preferred frame rate category using constants, including
+ * REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE, REQUESTED_FRAME_RATE_CATEGORY_LOW,
+ * REQUESTED_FRAME_RATE_CATEGORY_NORMAL, REQUESTED_FRAME_RATE_CATEGORY_HIGH.
+ * Keep in mind that the preferred frame rate affects the frame rate for the next frame,
+ * so use this method carefully. It's important to note that the preference is valid as
+ * long as the View is invalidated. Please also be aware that the requested frame rate
+ * will not propagate to child views when this API is used on a ViewGroup.
+ *
+ * @param frameRate the preferred frame rate of the view.
+ */
+ @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void setRequestedFrameRate(float frameRate) {
+ // Skip setting the frame rate if it's currently in forced override mode.
+ if (sToolkitViewGroupFrameRateApiFlagValue && getForcedOverrideFrameRateFlag()) {
+ return;
+ }
+
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ mPreferredFrameRate = frameRate;
+ }
+
+ if (sToolkitViewGroupFrameRateApiFlagValue) {
+ // If frameRate is Float.NaN, it means it's set to the default value.
+ // We only want to make the flag true, when the value is not Float.nan
+ setSelfRequestedFrameRateFlag(!Float.isNaN(mPreferredFrameRate));
+ }
+ }
+
+ /**
+ * Get the current preferred frame rate of the View.
+ * The value could be negative when preferred frame rate category is set
+ * instead of perferred frame rate.
+ * The frame rate category includes
+ * REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE, REQUESTED_FRAME_RATE_CATEGORY_LOW,
+ * REQUESTED_FRAME_RATE_CATEGORY_NORMAL, and REQUESTED_FRAME_RATE_CATEGORY_HIGH.
+ * Note that the frame rate value is valid as long as the View is invalidated.
+ * Please also be aware that the requested frame rate will not propagate to
+ * child views when this API is used on a ViewGroup.
+ *
+ * @return REQUESTED_FRAME_RATE_CATEGORY_DEFAULT by default,
+ * or value passed to {@link #setRequestedFrameRate(float)}.
+ */
+ @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public float getRequestedFrameRate() {
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ return mPreferredFrameRate;
+ }
+ return 0;
+ }
+
+ void overrideFrameRate(float frameRate, boolean forceOverride) {
+ setForcedOverrideFrameRateFlag(forceOverride);
+ if (forceOverride || !getSelfRequestedFrameRateFlag()) {
+ mPreferredFrameRate = frameRate;
+ }
+ }
+
+ void setForcedOverrideFrameRateFlag(boolean forcedOverride) {
+ if (forcedOverride) {
+ mPrivateFlags4 |= PFLAG4_FORCED_OVERRIDE_FRAME_RATE;
+ } else {
+ mPrivateFlags4 &= ~PFLAG4_FORCED_OVERRIDE_FRAME_RATE;
+ }
+ }
+
+ boolean getForcedOverrideFrameRateFlag() {
+ return (mPrivateFlags4 & PFLAG4_FORCED_OVERRIDE_FRAME_RATE) != 0;
+ }
+
+ void setSelfRequestedFrameRateFlag(boolean forcedOverride) {
+ if (forcedOverride) {
+ mPrivateFlags4 |= PFLAG4_SELF_REQUESTED_FRAME_RATE;
+ } else {
+ mPrivateFlags4 &= ~PFLAG4_SELF_REQUESTED_FRAME_RATE;
+ }
+ }
+
+ boolean getSelfRequestedFrameRateFlag() {
+ return (mPrivateFlags4 & PFLAG4_SELF_REQUESTED_FRAME_RATE) != 0;
+ }
+
+ /**
+ * Called from apps when they want to report jank stats to the system.
+ * @param appJankStats the stats that will be merged with the stats collected by the system.
+ */
+ @FlaggedApi(android.app.jank.Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void reportAppJankStats(@NonNull AppJankStats appJankStats) {
+ getRootView().reportAppJankStats(appJankStats);
+ }
+
+ /**
+ * Called by widgets to get a reference to JankTracker in order to update states.
+ * @hide
+ */
+ public @Nullable JankTracker getJankTracker() {
+ return getRootView().getJankTracker();
+ }
}
diff --git a/app/src/main/assets/tree-sitter-queries/java/blocks.scm b/app/src/main/assets/tree-sitter-queries/java/blocks.scm
index 903b3e2a7..ba9326d86 100644
--- a/app/src/main/assets/tree-sitter-queries/java/blocks.scm
+++ b/app/src/main/assets/tree-sitter-queries/java/blocks.scm
@@ -1,6 +1,6 @@
; Code block patterns for editor
; Capture names for scopes does not matter much in sora-editor implementation, you may use 'abc', 'test.xyz', etc.
-; General, editor considers the captured node's region as code block region.
+; Generally, editor considers the captured node's region as code block region.
; However, capture name with '.marked' suffix is special. The last terminal node's start position in the capture will be the end position of the block
; 'terminal node' refers to a node without children
diff --git a/app/src/main/java/io/github/rosemoe/sora/app/MainActivity.kt b/app/src/main/java/io/github/rosemoe/sora/app/MainActivity.kt
index 77b6dc396..32207c75d 100644
--- a/app/src/main/java/io/github/rosemoe/sora/app/MainActivity.kt
+++ b/app/src/main/java/io/github/rosemoe/sora/app/MainActivity.kt
@@ -28,7 +28,6 @@ import android.graphics.Typeface
import android.net.Uri
import android.os.Build
import android.os.Bundle
-import android.os.PersistableBundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
@@ -40,7 +39,6 @@ import android.widget.PopupMenu
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts.GetContent
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.view.ViewCompat
import androidx.lifecycle.lifecycleScope
import androidx.savedstate.write
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -659,7 +657,16 @@ class MainActivity : AppCompatActivity() {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.write {
- putString("text", binding.editor.text.toString())
+ // For production, you may need to store the text to external storage to avoid data loss
+ val text = binding.editor.text.toString().let {
+ val limit = 128 * 1024
+ if (it.length > limit) {
+ it.substring(0, limit)
+ } else {
+ it
+ }
+ }
+ putString("text", text)
putFloat("font.size", binding.editor.textSizePx)
putInt("position.left", binding.editor.cursor.left)
putInt("position.right", binding.editor.cursor.right)
diff --git a/build-logic/gradle/wrapper/gradle-wrapper.properties b/build-logic/gradle/wrapper/gradle-wrapper.properties
index 5e9bea2a5..4ffe95d9c 100644
--- a/build-logic/gradle/wrapper/gradle-wrapper.properties
+++ b/build-logic/gradle/wrapper/gradle-wrapper.properties
@@ -24,6 +24,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/editor-bom/build.gradle.kts b/editor-bom/build.gradle.kts
index 1f5c5601c..cf6a1fae1 100644
--- a/editor-bom/build.gradle.kts
+++ b/editor-bom/build.gradle.kts
@@ -24,7 +24,7 @@
plugins {
- id("com.vanniktech.maven.publish.base")
+ alias(libs.plugins.publish)
id("java-platform")
}
diff --git a/editor-lsp/build.gradle.kts b/editor-lsp/build.gradle.kts
index 078f5de69..52a9c2a44 100644
--- a/editor-lsp/build.gradle.kts
+++ b/editor-lsp/build.gradle.kts
@@ -22,9 +22,9 @@
* additional information or have any questions
******************************************************************************/
plugins {
- id("com.android.library")
+ alias(libs.plugins.android.library)
+ // alias(libs.plugins.publish)
id("org.jetbrains.kotlin.android")
- //id("com.vanniktech.maven.publish.base")
}
android {
diff --git a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspEditor.kt b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspEditor.kt
index 02525c10c..2281409c4 100644
--- a/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspEditor.kt
+++ b/editor-lsp/src/main/java/io/github/rosemoe/sora/lsp/editor/LspEditor.kt
@@ -226,6 +226,7 @@ class LspEditor(
if (capabilities.inlayHintProvider?.left != false || capabilities.inlayHintProvider?.right != null) {
requestInlayHint(CharPosition(0, 0))
}
+ requestDocumentColor()
status = LspEditorStatus.CONNECTED
}.onFailure {
@@ -365,6 +366,9 @@ class LspEditor(
uiDelegate.showDocumentColors(documentColors)
}
+ fun getAllColorOccurrences(): List
+ * This should be called when the language is detached from editor.
+ *
+ * @param scopeName New language scope name
+ */
+ public void updateLanguage(@NonNull String scopeName) {
var grammar = grammarRegistry.findGrammar(scopeName);
+ if (grammar == null) {
+ throw new IllegalArgumentException(String.format("Language with %s scope name %s not found", grammarRegistry, scopeName));
+ }
var languageConfiguration = grammarRegistry.findLanguageConfiguration(grammar.getScopeName());
createAnalyzerAndNewlineHandler(grammar, languageConfiguration);
}
- public void updateLanguage(GrammarDefinition grammarDefinition) {
+ /**
+ * Update the root language to the given grammar definition.
+ *
+ * This should be called when the language is detached from editor.
+ *
+ * @param grammarDefinition New language grammar definition
+ */
+ public void updateLanguage(@NonNull GrammarDefinition grammarDefinition) {
var grammar = grammarRegistry.loadGrammar(grammarDefinition);
-
var languageConfiguration = grammarRegistry.findLanguageConfiguration(grammar.getScopeName());
-
createAnalyzerAndNewlineHandler(grammar, languageConfiguration);
}
@@ -257,7 +256,6 @@ public int getTabSize() {
return tabSize;
}
-
@Override
public boolean useTab() {
return useTab;
@@ -285,10 +283,19 @@ public NewlineHandler[] getNewlineHandlers() {
return newlineHandlers;
}
+ /**
+ * Whether auto-completion is enabled.
+ * @see #setAutoCompleteEnabled(boolean)
+ */
public boolean isAutoCompleteEnabled() {
return autoCompleteEnabled;
}
+ /**
+ * Set whether auto-completion is enabled.
+ *
+ * Identifiers are available in auto-completion only when the language instance is set to collect identifiers at the time it is created.
+ */
public void setAutoCompleteEnabled(boolean autoCompleteEnabled) {
this.autoCompleteEnabled = autoCompleteEnabled;
}
@@ -307,6 +314,9 @@ public IdentifierAutoComplete getAutoCompleter() {
return autoComplete;
}
+ /**
+ * Set keywords for auto-completion
+ */
public void setCompleterKeywords(String[] keywords) {
autoComplete.setKeywords(keywords, false);
}
diff --git a/language-treesitter/build.gradle.kts b/language-treesitter/build.gradle.kts
index af82d8b45..c35e722ed 100644
--- a/language-treesitter/build.gradle.kts
+++ b/language-treesitter/build.gradle.kts
@@ -23,8 +23,8 @@
******************************************************************************/
plugins {
- id("com.android.library")
- id("com.vanniktech.maven.publish.base")
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.publish)
}
android {
diff --git a/oniguruma-native/build.gradle.kts b/oniguruma-native/build.gradle.kts
index 2aae6ef96..317c7424a 100644
--- a/oniguruma-native/build.gradle.kts
+++ b/oniguruma-native/build.gradle.kts
@@ -23,8 +23,8 @@
******************************************************************************/
plugins {
- id("com.android.library")
- //id("com.vanniktech.maven.publish.base")
+ alias(libs.plugins.android.library)
+ // alias(libs.plugins.publish)
}
android {
@@ -51,7 +51,7 @@ android {
externalNativeBuild {
cmake {
path = project.file("src/main/cpp/CMakeLists.txt")
- version = "3.22.1"
+ version = "4.1.2"
}
}
}
diff --git a/oniguruma-native/src/main/cpp/oniguruma b/oniguruma-native/src/main/cpp/oniguruma
index 1ea354208..b9689f994 160000
--- a/oniguruma-native/src/main/cpp/oniguruma
+++ b/oniguruma-native/src/main/cpp/oniguruma
@@ -1 +1 @@
-Subproject commit 1ea354208c8f197eaf42486af78f55e2425e3762
+Subproject commit b9689f9941b6bfa77f3a324bb340e19a64515db0
+ *
+ * @param event The {@link MotionEvent} that requires a pointer icon to be resolved for one of
+ * pointers.
+ * @param pointerIndex The index of the pointer in {@code event} for which to retrieve the
+ * {@link PointerIcon}. This will be between 0 and {@link MotionEvent#getPointerCount()}.
+ * @return the pointer icon to use for specified pointer, or {@code null} if a pointer icon
+ * is not specified and the default icon should be used.
* @see PointerIcon
+ * @see InputManager#isStylusPointerIconEnabled()
*/
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
if (isDraggingScrollBar() || isOnScrollbarThumb(x, y)) {
- return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW);
+ // Use the default pointer icon.
+ return null;
}
- return mPointerIcon;
+
+ // Note: A drawing tablet will have both SOURCE_MOUSE and SOURCE_STYLUS, but it would use
+ // TOOL_TYPE_STYLUS. For now, treat drawing tablets the same way as a mouse or touchpad.
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ return mMousePointerIcon;
+ }
+
+ return null;
}
/**
- * Set the pointer icon for the current view.
+ * Set the pointer icon to be used for a mouse pointer in the current view.
+ *
* Passing {@code null} will restore the pointer icon to its default value.
+ * Note that setting the pointer icon using this method will only set it for events coming from
+ * a mouse device (i.e. with source {@link InputDevice#SOURCE_MOUSE}). To resolve
+ * the pointer icon for other device types like styluses, override
+ * {@link #onResolvePointerIcon(MotionEvent, int)}.
+ *
* @param pointerIcon A PointerIcon instance which will be shown when the mouse hovers.
+ * @see #onResolvePointerIcon(MotionEvent, int)
+ * @see PointerIcon
*/
public void setPointerIcon(PointerIcon pointerIcon) {
- mPointerIcon = pointerIcon;
- if (mAttachInfo == null || mAttachInfo.mHandlingPointerEvent) {
+ mMousePointerIcon = pointerIcon;
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl == null) {
return;
}
- try {
- mAttachInfo.mSession.updatePointerIcon(mAttachInfo.mWindow);
- } catch (RemoteException e) {
- }
+ viewRootImpl.refreshPointerIcon();
}
/**
- * Gets the pointer icon for the current view.
+ * Gets the mouse pointer icon for the current view.
+ *
+ * @see #setPointerIcon(PointerIcon)
*/
@InspectableProperty
public PointerIcon getPointerIcon() {
- return mPointerIcon;
+ return mMousePointerIcon;
}
/**
@@ -29182,11 +31386,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
- if (sUseBrokenMakeMeasureSpec) {
- return size + mode;
- } else {
- return (size & ~MODE_MASK) | (mode & MODE_MASK);
- }
+ return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
/**
@@ -29197,9 +31397,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@UnsupportedAppUsage
public static int makeSafeMeasureSpec(int size, int mode) {
- if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
- return 0;
- }
return makeMeasureSpec(size, mode);
}
@@ -29499,6 +31696,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return true if the callback consumed the long click, false otherwise.
*/
boolean onLongClick(View v);
+
+ /**
+ * Returns whether the default {@link HapticFeedbackConstants#LONG_PRESS} haptic feedback
+ * is performed when this listener has consumed the long click. This method is called
+ * immediately after {@link #onLongClick} has returned true.
+ *
+ * @param v The view that was clicked and held.
+ * @return true to perform the default {@link HapticFeedbackConstants#LONG_PRESS} haptic
+ * feedback, or false if the handler manages all haptics itself.
+ */
+ default boolean onLongClickUseDefaultHapticFeedback(@NonNull View v) {
+ return true;
+ }
}
/**
@@ -29767,7 +31977,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
interface Callbacks {
void playSoundEffect(int effectId);
- boolean performHapticFeedback(int effectId, boolean always);
+
+ boolean performHapticFeedback(int effectId,
+ @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags);
+
+ void performHapticFeedbackForInputDevice(int effectId,
+ int inputDeviceId, int inputSource,
+ @HapticFeedbackConstants.Flags int flags,
+ @HapticFeedbackConstants.PrivateFlags int privFlags);
}
/**
@@ -30124,6 +32342,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
final ArrayList
+ * @Override
+ * public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
+ * final int toolType = event.getToolType(pointerIndex);
+ * if (!event.isFromSource(InputDevice.SOURCE_MOUSE)
+ * && event.isFromSource(InputDevice.SOURCE_STYLUS)
+ * && (toolType == MotionEvent.TOOL_TYPE_STYLUS
+ * || toolType == MotionEvent.TOOL_TYPE_ERASER)) {
+ * // Show this pointer icon only if this pointer is a stylus.
+ * return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HANDWRITING);
+ * }
+ * // Use the default logic for determining the pointer icon for other non-stylus pointers,
+ * // like for the mouse cursor.
+ * return super.onResolvePointerIcon(event, pointerIndex);
+ * }
+ *