diff options
Diffstat (limited to 'android/view/View.java')
-rw-r--r-- | android/view/View.java | 782 |
1 files changed, 603 insertions, 179 deletions
diff --git a/android/view/View.java b/android/view/View.java index 3d6a6fee..97e11b15 100644 --- a/android/view/View.java +++ b/android/view/View.java @@ -75,6 +75,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; +import android.text.InputType; import android.text.TextUtils; import android.util.AttributeSet; import android.util.FloatProperty; @@ -726,6 +727,8 @@ import java.util.function.Predicate; * @attr ref android.R.styleable#View_nextFocusRight * @attr ref android.R.styleable#View_nextFocusUp * @attr ref android.R.styleable#View_onClick + * @attr ref android.R.styleable#View_outlineSpotShadowColor + * @attr ref android.R.styleable#View_outlineAmbientShadowColor * @attr ref android.R.styleable#View_padding * @attr ref android.R.styleable#View_paddingHorizontal * @attr ref android.R.styleable#View_paddingVertical @@ -904,6 +907,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static boolean sThrowOnInvalidFloatProperties; + /** + * Prior to P, {@code #startDragAndDrop} accepts a builder which produces an empty drag shadow. + * Currently zero size SurfaceControl cannot be created thus we create a dummy 1x1 surface + * instead. + */ + private static boolean sAcceptZeroSizeDragShadow; + /** @hide */ @IntDef({NOT_FOCUSABLE, FOCUSABLE, FOCUSABLE_AUTO}) @Retention(RetentionPolicy.SOURCE) @@ -2943,6 +2953,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG3_NO_REVEAL_ON_FOCUS * 1 PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT * 1 PFLAG3_SCREEN_READER_FOCUSABLE + * 1 PFLAG3_AGGREGATED_VISIBLE + * 1 PFLAG3_AUTOFILLID_EXPLICITLY_SET + * 1 available * |-------|-------|-------|-------| */ @@ -3233,6 +3246,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static final int PFLAG3_AGGREGATED_VISIBLE = 0x20000000; + /** + * Used to indicate that {@link #mAutofillId} was explicitly set through + * {@link #setAutofillId(AutofillId)}. + */ + private static final int PFLAG3_AUTOFILLID_EXPLICITLY_SET = 0x40000000; + /* End of masks for mPrivateFlags3 */ /** @@ -3975,6 +3994,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Current clip bounds. to which all drawing of this view are constrained. */ + @ViewDebug.ExportedProperty(category = "drawing") Rect mClipBounds = null; private boolean mLastIsOpaque; @@ -4300,7 +4320,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, OnCapturedPointerListener mOnCapturedPointerListener; - private ArrayList<OnKeyFallbackListener> mKeyFallbackListeners; + private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners; } ListenerInfo mListenerInfo; @@ -4442,6 +4462,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private CheckForLongPress mPendingCheckForLongPress; private CheckForTap mPendingCheckForTap = null; private PerformClick mPerformClick; + private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent; private UnsetPressedState mUnsetPressedState; @@ -4794,6 +4815,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Canvas.sCompatibilityRestore = targetSdkVersion < Build.VERSION_CODES.M; Canvas.sCompatibilitySetBitmap = targetSdkVersion < Build.VERSION_CODES.O; + Canvas.setCompatibilityVersion(targetSdkVersion); // In M and newer, our widgets can pass a "hint" value in the size // for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers @@ -4836,6 +4858,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sAlwaysAssignFocus = targetSdkVersion < Build.VERSION_CODES.P; + sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P; + sCompatibilityDone = true; } } @@ -5445,6 +5469,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setAccessibilityPaneTitle(a.getString(attr)); } break; + case R.styleable.View_outlineSpotShadowColor: + setOutlineSpotShadowColor(a.getColor(attr, Color.BLACK)); + break; + case R.styleable.View_outlineAmbientShadowColor: + setOutlineAmbientShadowColor(a.getColor(attr, Color.BLACK)); + break; } } @@ -6516,7 +6546,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } finally { // Set it to already called so it's not called twice when called by // performClickInternal() - mPrivateFlags |= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK; + mPrivateFlags &= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK; } } } @@ -7201,7 +7231,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (gainFocus) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } else { - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -7251,7 +7281,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // becomes true where it should issue notifyViewEntered(). afm.notifyViewEntered(this); } - } else if (!isFocused()) { + } else if (!enter && !isFocused()) { afm.notifyViewExited(this); } } @@ -7259,19 +7289,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * If this view is a visually distinct portion of a window, for example the content view of - * a fragment that is replaced, it is considered a pane for accessibility purposes. In order - * for accessibility services to understand the views role, and to announce its title as - * appropriate, such views should have pane titles. + * Visually distinct portion of a window with window-like semantics are considered panes for + * accessibility purposes. One example is the content view of a fragment that is replaced. + * In order for accessibility services to understand a pane's window-like behavior, panes + * should have descriptive titles. Views with pane titles produce {@link AccessibilityEvent}s + * when they appear, disappear, or change title. * - * @param accessibilityPaneTitle The pane's title. + * @param accessibilityPaneTitle The pane's title. Setting to {@code null} indicates that this + * View is not a pane. * * {@see AccessibilityNodeInfo#setPaneTitle(CharSequence)} */ - public void setAccessibilityPaneTitle(CharSequence accessibilityPaneTitle) { + public void setAccessibilityPaneTitle(@Nullable CharSequence accessibilityPaneTitle) { if (!TextUtils.equals(accessibilityPaneTitle, mAccessibilityPaneTitle)) { mAccessibilityPaneTitle = accessibilityPaneTitle; - notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_TITLE); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_TITLE); } } @@ -7282,10 +7315,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * {@see #setAccessibilityPaneTitle}. */ - public CharSequence getAccessibilityPaneTitle() { + @Nullable public CharSequence getAccessibilityPaneTitle() { return mAccessibilityPaneTitle; } + private boolean isAccessibilityPane() { + return mAccessibilityPaneTitle != null; + } + /** * Sends an accessibility event of the given type. If accessibility is * not enabled this method has no effect. The default implementation calls @@ -7849,6 +7886,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, structure.setAutofillHints(getAutofillHints()); structure.setAutofillValue(getAutofillValue()); } + structure.setImportantForAutofill(getImportantForAutofill()); } int ignoredParentLeft = 0; @@ -7930,12 +7968,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * optimal implementation providing this data. */ public void onProvideVirtualStructure(ViewStructure structure) { - AccessibilityNodeProvider provider = getAccessibilityNodeProvider(); + onProvideVirtualStructureCompat(structure, false); + } + + /** + * Fallback implementation to populate a ViewStructure from accessibility state. + * + * @param structure The structure to populate. + * @param forAutofill Whether the structure is needed for autofill. + */ + private void onProvideVirtualStructureCompat(ViewStructure structure, boolean forAutofill) { + final AccessibilityNodeProvider provider = getAccessibilityNodeProvider(); if (provider != null) { - AccessibilityNodeInfo info = createAccessibilityNodeInfo(); + if (android.view.autofill.Helper.sVerbose && forAutofill) { + Log.v(VIEW_LOG_TAG, "onProvideVirtualStructureCompat() for " + this); + } + + final AccessibilityNodeInfo info = createAccessibilityNodeInfo(); structure.setChildCount(1); - ViewStructure root = structure.newChild(0); - populateVirtualStructure(root, provider, info); + final ViewStructure root = structure.newChild(0); + populateVirtualStructure(root, provider, info, forAutofill); info.recycle(); } } @@ -7964,12 +8016,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <li>Call {@link android.view.autofill.AutofillManager#notifyViewEntered(View, int, Rect)} * and/or {@link android.view.autofill.AutofillManager#notifyViewExited(View, int)} * when the focused virtual child changed. + * <li>Override {@link #isVisibleToUserForAutofill(int)} to allow the platform to query + * whether a given virtual view is visible to the user in order to support triggering + * save when all views of interest go away. * <li>Call * {@link android.view.autofill.AutofillManager#notifyValueChanged(View, int, AutofillValue)} * when the value of a virtual child changed. * <li>Call {@link * android.view.autofill.AutofillManager#notifyViewVisibilityChanged(View, int, boolean)} * when the visibility of a virtual child changed. + * <li>Call + * {@link android.view.autofill.AutofillManager#notifyViewClicked(View, int)} when a virtual + * child is clicked. * <li>Call {@link AutofillManager#commit()} when the autofill context of the view structure * changed and the current context should be committed (for example, when the user tapped * a {@code SUBMIT} button in an HTML page). @@ -7999,6 +8057,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS */ public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) { + if (mContext.isAutofillCompatibilityEnabled()) { + onProvideVirtualStructureCompat(structure, true); + } } /** @@ -8076,10 +8137,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @attr ref android.R.styleable#Theme_autofilledHighlight */ public void autofill(@NonNull @SuppressWarnings("unused") SparseArray<AutofillValue> values) { + if (!mContext.isAutofillCompatibilityEnabled()) { + return; + } + final AccessibilityNodeProvider provider = getAccessibilityNodeProvider(); + if (provider == null) { + return; + } + final int valueCount = values.size(); + for (int i = 0; i < valueCount; i++) { + final AutofillValue value = values.valueAt(i); + if (value.isText()) { + final int virtualId = values.keyAt(i); + final CharSequence text = value.getTextValue(); + final Bundle arguments = new Bundle(); + arguments.putCharSequence( + AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text); + provider.performAction(virtualId, AccessibilityNodeInfo.ACTION_SET_TEXT, arguments); + } + } } /** - * Gets the unique identifier of this view in the screen, for autofill purposes. + * Gets the unique, logical identifier of this view in the activity, for autofill purposes. + * + * <p>The autofill id is created on demand, unless it is explicitly set by + * {@link #setAutofillId(AutofillId)}. + * + * <p>See {@link #setAutofillId(AutofillId)} for more info. * * @return The View's autofill id. */ @@ -8093,6 +8178,73 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Sets the unique, logical identifier of this view in the activity, for autofill purposes. + * + * <p>The autofill id is created on demand, and this method should only be called when a view is + * reused after {@link #dispatchProvideAutofillStructure(ViewStructure, int)} is called, as + * that method creates a snapshot of the view that is passed along to the autofill service. + * + * <p>This method is typically used when view subtrees are recycled to represent different + * content* —in this case, the autofill id can be saved before the view content is swapped + * out, and restored later when it's swapped back in. For example: + * + * <pre> + * EditText reusableView = ...; + * ViewGroup parentView = ...; + * AutofillManager afm = ...; + * + * // Swap out the view and change its contents + * AutofillId oldId = reusableView.getAutofillId(); + * CharSequence oldText = reusableView.getText(); + * parentView.removeView(reusableView); + * AutofillId newId = afm.getNextAutofillId(); + * reusableView.setText("New I am"); + * reusableView.setAutofillId(newId); + * parentView.addView(reusableView); + * + * // Later, swap the old content back in + * parentView.removeView(reusableView); + * reusableView.setAutofillId(oldId); + * reusableView.setText(oldText); + * parentView.addView(reusableView); + * </pre> + * + * @param id an autofill ID that is unique in the {@link android.app.Activity} hosting the view, + * or {@code null} to reset it. Usually it's an id previously allocated to another view (and + * obtained through {@link #getAutofillId()}), or a new value obtained through + * {@link AutofillManager#getNextAutofillId()}. + * + * @throws IllegalStateException if the view is already {@link #isAttachedToWindow() attached to + * a window}. + * + * @throws IllegalArgumentException if the id is an autofill id associated with a virtual view. + */ + public void setAutofillId(@Nullable AutofillId id) { + // TODO(b/37566627): add unit / CTS test for all possible combinations below + if (android.view.autofill.Helper.sVerbose) { + Log.v(VIEW_LOG_TAG, "setAutofill(): from " + mAutofillId + " to " + id); + } + if (isAttachedToWindow()) { + throw new IllegalStateException("Cannot set autofill id when view is attached"); + } + if (id != null && id.isVirtual()) { + throw new IllegalStateException("Cannot set autofill id assigned to virtual views"); + } + if (id == null && (mPrivateFlags3 & PFLAG3_AUTOFILLID_EXPLICITLY_SET) == 0) { + // Ignore reset because it was never explicitly set before. + return; + } + mAutofillId = id; + if (id != null) { + mAutofillViewId = id.getViewId(); + mPrivateFlags3 |= PFLAG3_AUTOFILLID_EXPLICITLY_SET; + } else { + mAutofillViewId = NO_ID; + mPrivateFlags3 &= ~PFLAG3_AUTOFILLID_EXPLICITLY_SET; + } + } + + /** * Describes the autofill type of this view, so an * {@link android.service.autofill.AutofillService} can create the proper {@link AutofillValue} * when autofilling the view. @@ -8309,6 +8461,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + // If the app developer explicitly set hints for it, it's important. + if (getAutofillHints() != null) { + return true; + } + // Otherwise, assume it's not important... return false; } @@ -8329,9 +8486,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } private void populateVirtualStructure(ViewStructure structure, - AccessibilityNodeProvider provider, AccessibilityNodeInfo info) { + AccessibilityNodeProvider provider, AccessibilityNodeInfo info, + boolean forAutofill) { structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()), - null, null, null); + null, null, info.getViewIdResourceName()); Rect rect = structure.getTempRect(); info.getBoundsInParent(rect); structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height()); @@ -8364,21 +8522,54 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (info.isContextClickable()) { structure.setContextClickable(true); } + if (forAutofill) { + structure.setAutofillId(new AutofillId(getAutofillId(), + AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()))); + } CharSequence cname = info.getClassName(); structure.setClassName(cname != null ? cname.toString() : null); structure.setContentDescription(info.getContentDescription()); - if ((info.getText() != null || info.getError() != null)) { - structure.setText(info.getText(), info.getTextSelectionStart(), - info.getTextSelectionEnd()); + if (forAutofill) { + final int maxTextLength = info.getMaxTextLength(); + if (maxTextLength != -1) { + structure.setMaxTextLength(maxTextLength); + } + structure.setHint(info.getHintText()); + } + CharSequence text = info.getText(); + boolean hasText = text != null || info.getError() != null; + if (hasText) { + structure.setText(text, info.getTextSelectionStart(), info.getTextSelectionEnd()); + } + if (forAutofill) { + if (info.isEditable()) { + structure.setDataIsSensitive(true); + if (hasText) { + structure.setAutofillType(AUTOFILL_TYPE_TEXT); + structure.setAutofillValue(AutofillValue.forText(text)); + } + int inputType = info.getInputType(); + if (inputType == 0 && info.isPassword()) { + inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD; + } + structure.setInputType(inputType); + } else { + structure.setDataIsSensitive(false); + } } final int NCHILDREN = info.getChildCount(); if (NCHILDREN > 0) { structure.setChildCount(NCHILDREN); for (int i=0; i<NCHILDREN; i++) { + if (AccessibilityNodeInfo.getVirtualDescendantId(info.getChildNodeIds().get(i)) + == AccessibilityNodeProvider.HOST_VIEW_ID) { + Log.e(VIEW_LOG_TAG, "Virtual view pointing to its host. Ignoring"); + continue; + } AccessibilityNodeInfo cinfo = provider.createAccessibilityNodeInfo( AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i))); ViewStructure child = structure.newChild(i); - populateVirtualStructure(child, provider, cinfo); + populateVirtualStructure(child, provider, cinfo, forAutofill); cinfo.recycle(); } } @@ -8707,6 +8898,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Computes whether this virtual autofill view is visible to the user. + * + * <p><b>Note: </b>By default it returns {@code true}, but views providing a virtual hierarchy + * view must override it. + * + * @return Whether the view is visible on the screen. + */ + public boolean isVisibleToUserForAutofill(int virtualId) { + if (mContext.isAutofillCompatibilityEnabled()) { + final AccessibilityNodeProvider provider = getAccessibilityNodeProvider(); + if (provider != null) { + final AccessibilityNodeInfo node = provider.createAccessibilityNodeInfo(virtualId); + if (node != null) { + return node.isVisibleToUser(); + } + // if node is null, assume it's not visible anymore + } else { + Log.w(VIEW_LOG_TAG, "isVisibleToUserForAutofill(" + virtualId + "): no provider"); + } + return false; + } + return true; + } + + /** * Computes whether this view is visible to the user. Such a view is * attached, visible, all its predecessors are visible, it is not clipped * entirely by its predecessors, and has an alpha greater than zero. @@ -8715,7 +8931,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ - protected boolean isVisibleToUser() { + public boolean isVisibleToUser() { return isVisibleToUser(null); } @@ -8924,9 +9140,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final boolean nonEmptyDesc = contentDescription != null && contentDescription.length() > 0; if (nonEmptyDesc && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } else { - notifyAccessibilityStateChanged( + notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION); } } @@ -8959,7 +9175,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } mAccessibilityTraversalBeforeId = beforeId; - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } /** @@ -9002,7 +9219,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } mAccessibilityTraversalAfterId = afterId; - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } /** @@ -9044,7 +9262,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, && mID == View.NO_ID) { mID = generateViewId(); } - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } /** @@ -10544,7 +10763,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (pflags3 != mPrivateFlags3) { mPrivateFlags3 = pflags3; - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -11374,7 +11594,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK; mPrivateFlags2 |= (mode << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT) & PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK; - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -11431,9 +11652,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT) & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK; if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility()) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } else { - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } } @@ -11519,7 +11741,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return mode == IMPORTANT_FOR_ACCESSIBILITY_YES || isActionableForAccessibility() || hasListenersForAccessibility() || getAccessibilityNodeProvider() != null || getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE - || (mAccessibilityPaneTitle != null); + || isAccessibilityPane(); } /** @@ -11609,8 +11831,51 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ - public void notifyAccessibilityStateChanged(int changeType) { - notifyAccessibilityStateChanged(this, changeType); + public void notifyViewAccessibilityStateChangedIfNeeded(int changeType) { + if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) { + return; + } + + // Changes to views with a pane title count as window state changes, as the pane title + // marks them as significant parts of the UI. + if ((changeType != AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) + && isAccessibilityPane()) { + // If the pane isn't visible, content changed events are sufficient unless we're + // reporting that the view just disappeared + if ((getVisibility() == VISIBLE) + || (changeType == AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED)) { + final AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + event.setContentChangeTypes(changeType); + event.setSource(this); + onPopulateAccessibilityEvent(event); + if (mParent != null) { + try { + mParent.requestSendAccessibilityEvent(this, event); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } + return; + } + } + + // If this is a live region, we should send a subtree change event + // from this view immediately. Otherwise, we can let it propagate up. + if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) { + final AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + event.setContentChangeTypes(changeType); + sendAccessibilityEventUnchecked(event); + } else if (mParent != null) { + try { + mParent.notifySubtreeAccessibilityStateChanged(this, this, changeType); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } } /** @@ -11624,42 +11889,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ - public void notifyAccessibilitySubtreeChanged() { - if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) { - mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; - notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); - } - } - - void notifyAccessibilityStateChanged(View source, int changeType) { + public void notifySubtreeAccessibilityStateChangedIfNeeded() { if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) { return; } - // Changes to views with a pane title count as window state changes, as the pane title - // marks them as significant parts of the UI. - if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) { - final AccessibilityEvent event = AccessibilityEvent.obtain(); - event.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - event.setContentChangeTypes(changeType); - onPopulateAccessibilityEvent(event); + + if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) { + mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; if (mParent != null) { try { - mParent.requestSendAccessibilityEvent(this, event); + mParent.notifySubtreeAccessibilityStateChanged( + this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); } catch (AbstractMethodError e) { - Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() - + " does not fully implement ViewParent", e); + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); } } } - - if (mParent != null) { - try { - mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType); - } catch (AbstractMethodError e) { - Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() - + " does not fully implement ViewParent", e); - } - } } /** @@ -11679,10 +11925,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Reset the flag indicating the accessibility state of the subtree rooted * at this view changed. - * - * @hide */ - public void resetSubtreeAccessibilityStateChanged() { + void resetSubtreeAccessibilityStateChanged() { mPrivateFlags2 &= ~PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED; } @@ -11843,7 +12087,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || getAccessibilitySelectionEnd() != end) && (start == end)) { setAccessibilitySelection(start, end); - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); return true; } } break; @@ -12643,7 +12888,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) { if (isVisible != oldVisible) { - notifyAccessibilityStateChanged(isVisible + notifyViewAccessibilityStateChangedIfNeeded(isVisible ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED); } @@ -13672,11 +13917,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mUnbufferedDispatchRequested = true; } + private boolean hasSize() { + return (mBottom > mTop) && (mRight > mLeft); + } + private boolean canTakeFocus() { return ((mViewFlags & VISIBILITY_MASK) == VISIBLE) && ((mViewFlags & FOCUSABLE) == FOCUSABLE) && ((mViewFlags & ENABLED_MASK) == ENABLED) - && (sCanFocusZeroSized || !isLayoutValid() || (mBottom > mTop) && (mRight > mLeft)); + && (sCanFocusZeroSized || !isLayoutValid() || hasSize()); } /** @@ -13737,7 +13986,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || focusableChangedByAuto == 0 || viewRootImpl == null || viewRootImpl.mThread == Thread.currentThread()) { - shouldNotifyFocusableAvailable = true; + shouldNotifyFocusableAvailable = canTakeFocus(); } } } @@ -13756,11 +14005,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, needGlobalAttributesUpdate(true); - // a view becoming visible is worth notifying the parent - // about in case nothing has focus. even if this specific view - // isn't focusable, it may contain something that is, so let - // the root view try to give this focus if nothing else does. - shouldNotifyFocusableAvailable = true; + // a view becoming visible is worth notifying the parent about in case nothing has + // focus. Even if this specific view isn't focusable, it may contain something that + // is, so let the root view try to give this focus if nothing else does. + shouldNotifyFocusableAvailable = hasSize(); } } @@ -13769,16 +14017,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // a view becoming enabled should notify the parent as long as the view is also // visible and the parent wasn't already notified by becoming visible during this // setFlags invocation. - shouldNotifyFocusableAvailable = true; + shouldNotifyFocusableAvailable = canTakeFocus(); } else { - if (hasFocus()) clearFocus(); + if (isFocused()) clearFocus(); } } - if (shouldNotifyFocusableAvailable) { - if (mParent != null && canTakeFocus()) { - mParent.focusableViewAvailable(this); - } + if (shouldNotifyFocusableAvailable && mParent != null) { + mParent.focusableViewAvailable(this); } /* Check if the GONE bit has changed */ @@ -13860,7 +14106,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ((!(mParent instanceof ViewGroup)) || ((ViewGroup) mParent).isShown())) { dispatchVisibilityAggregated(newVisibility == VISIBLE); } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -13902,16 +14148,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (accessibilityEnabled) { + // If we're an accessibility pane and the visibility changed, we already have sent + // a state change, so we really don't need to report other changes. + if (isAccessibilityPane()) { + changed &= ~VISIBILITY_MASK; + } if ((changed & FOCUSABLE) != 0 || (changed & VISIBILITY_MASK) != 0 || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0 || (changed & CONTEXT_CLICKABLE) != 0) { if (oldIncludeForAccessibility != includeForAccessibility()) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } else { - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } else if ((changed & ENABLED_MASK) != 0) { - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } } @@ -13945,13 +14198,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param oldt Previous vertical scroll origin. */ protected void onScrollChanged(int l, int t, int oldl, int oldt) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); - ViewRootImpl root = getViewRootImpl(); - if (root != null) { - root.getAccessibilityState() - .getSendViewScrolledAccessibilityEvent() - .post(this, /* dx */ l - oldl, /* dy */ t - oldt); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt); } mBackgroundSizeChanged = true; @@ -14094,7 +14344,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Return the width of the your view. + * Return the width of your view. * * @return The width of your view, in pixels. */ @@ -14347,7 +14597,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14391,7 +14641,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14435,7 +14685,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14472,7 +14722,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14509,7 +14759,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -14597,6 +14847,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Returns whether or not a pivot has been set by a call to {@link #setPivotX(float)} or + * {@link #setPivotY(float)}. If no pivot has been set then the pivot will be the center + * of the view. + * + * @return True if a pivot has been set, false if the default pivot is being used + */ + public boolean isPivotSet() { + return mRenderNode.isPivotExplicitlySet(); + } + + /** + * Clears any pivot previously set by a call to {@link #setPivotX(float)} or + * {@link #setPivotY(float)}. After calling this {@link #isPivotSet()} will be false + * and the pivot used for rotation will return to default of being centered on the view. + */ + public void resetPivot() { + if (mRenderNode.resetPivot()) { + invalidateViewProperty(false, false); + } + } + + /** * The opacity of the view. This is a value from 0 to 1, where 0 means the view is * completely transparent and 1 means the view is completely opaque. * @@ -14656,10 +14928,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * ImageView with only the foreground image. The default implementation returns true; subclasses * should override if they have cases which can be optimized.</p> * - * <p>The current implementation of the saveLayer and saveLayerAlpha methods in {@link Canvas} - * necessitates that a View return true if it uses the methods internally without passing the - * {@link Canvas#CLIP_TO_LAYER_SAVE_FLAG}.</p> - * * <p><strong>Note:</strong> The return value of this method is ignored if {@link * #forceHasOverlappingRendering(boolean)} has been called on this view.</p> * @@ -14712,7 +14980,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mTransformationInfo.mAlpha != alpha) { // Report visibility changes, which can affect children, to accessibility if ((alpha == 0) ^ (mTransformationInfo.mAlpha == 0)) { - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } mTransformationInfo.mAlpha = alpha; if (onSetAlpha((int) (alpha * 255))) { @@ -15214,7 +15482,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -15248,7 +15516,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -15418,7 +15686,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void invalidateOutline() { rebuildOutline(); - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); invalidateViewProperty(false, false); } @@ -15459,14 +15727,61 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * @hide + * Sets the color of the spot shadow that is drawn when the view has a positive Z or + * elevation value. + * <p> + * By default the shadow color is black. Generally, this color will be opaque so the intensity + * of the shadow is consistent between different views with different colors. + * <p> + * The opacity of the final spot shadow is a function of the shadow caster height, the + * alpha channel of the outlineSpotShadowColor (typically opaque), and the + * {@link android.R.attr#spotShadowAlpha} theme attribute. + * + * @attr ref android.R.styleable#View_outlineSpotShadowColor + * @param color The color this View will cast for its elevation spot shadow. + */ + public void setOutlineSpotShadowColor(@ColorInt int color) { + if (mRenderNode.setSpotShadowColor(color)) { + invalidateViewProperty(true, true); + } + } + + /** + * @return The shadow color set by {@link #setOutlineSpotShadowColor(int)}, or black if nothing + * was set + */ + public @ColorInt int getOutlineSpotShadowColor() { + return mRenderNode.getSpotShadowColor(); + } + + /** + * Sets the color of the ambient shadow that is drawn when the view has a positive Z or + * elevation value. + * <p> + * By default the shadow color is black. Generally, this color will be opaque so the intensity + * of the shadow is consistent between different views with different colors. + * <p> + * The opacity of the final ambient shadow is a function of the shadow caster height, the + * alpha channel of the outlineAmbientShadowColor (typically opaque), and the + * {@link android.R.attr#ambientShadowAlpha} theme attribute. + * + * @attr ref android.R.styleable#View_outlineAmbientShadowColor + * @param color The color this View will cast for its elevation shadow. */ - public void setShadowColor(@ColorInt int color) { - if (mRenderNode.setShadowColor(color)) { + public void setOutlineAmbientShadowColor(@ColorInt int color) { + if (mRenderNode.setAmbientShadowColor(color)) { invalidateViewProperty(true, true); } } + /** + * @return The shadow color set by {@link #setOutlineAmbientShadowColor(int)}, or black if + * nothing was set + */ + public @ColorInt int getOutlineAmbientShadowColor() { + return mRenderNode.getAmbientShadowColor(); + } + /** @hide */ public void setRevealClip(boolean shouldClip, float x, float y, float radius) { @@ -15613,7 +15928,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidateParentIfNeeded(); } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -15661,7 +15976,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidateParentIfNeeded(); } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -16539,6 +16854,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Post a callback to send a {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. + * This event is sent at most once every + * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}. + */ + private void postSendViewScrolledAccessibilityEventCallback(int dx, int dy) { + if (mSendViewScrolledAccessibilityEvent == null) { + mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent(); + } + mSendViewScrolledAccessibilityEvent.post(dx, dy); + } + + /** * Called by a parent to request that a child update its values for mScrollX * and mScrollY if necessary. This will typically be done if the child is * animating a scroll using a {@link android.widget.Scroller Scroller} @@ -17793,13 +18120,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, removeUnsetPressCallback(); removeLongPressCallback(); removePerformClickCallback(); - if (mAttachInfo != null - && mAttachInfo.mViewRootImpl.mAccessibilityState != null - && mAttachInfo.mViewRootImpl.mAccessibilityState.isScrollEventSenderInitialized()) { - mAttachInfo.mViewRootImpl.mAccessibilityState - .getSendViewScrolledAccessibilityEvent() - .cancelIfPendingFor(this); - } + cancel(mSendViewScrolledAccessibilityEvent); stopNestedScroll(); // Anything that started animating right before detach should already @@ -17847,19 +18168,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * currently attached to. */ public WindowId getWindowId() { - if (mAttachInfo == null) { + AttachInfo ai = mAttachInfo; + if (ai == null) { return null; } - if (mAttachInfo.mWindowId == null) { + if (ai.mWindowId == null) { try { - mAttachInfo.mIWindowId = mAttachInfo.mSession.getWindowId( - mAttachInfo.mWindowToken); - mAttachInfo.mWindowId = new WindowId( - mAttachInfo.mIWindowId); + ai.mIWindowId = ai.mSession.getWindowId(ai.mWindowToken); + if (ai.mIWindowId != null) { + ai.mWindowId = new WindowId(ai.mIWindowId); + } } catch (RemoteException e) { } } - return mAttachInfo.mWindowId; + return ai.mWindowId; } /** @@ -18255,7 +18577,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Hence prevent the same autofill view id from being restored multiple times. ((BaseSavedState) state).mSavedData &= ~BaseSavedState.AUTOFILL_ID; - mAutofillViewId = baseState.mAutofillViewId; + if ((mPrivateFlags3 & PFLAG3_AUTOFILLID_EXPLICITLY_SET) != 0) { + // Ignore when view already set it through setAutofillId(); + if (android.view.autofill.Helper.sDebug) { + Log.d(VIEW_LOG_TAG, "onRestoreInstanceState(): not setting autofillId to " + + baseState.mAutofillViewId + " because view explicitly set it to " + + mAutofillId); + } + } else { + mAutofillViewId = baseState.mAutofillViewId; + mAutofillId = null; // will be set on demand by getAutofillId() + } } } } @@ -19892,22 +20224,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int solidColor = getSolidColor(); if (solidColor == 0) { - final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; - if (drawTop) { - canvas.saveLayer(left, top, right, top + length, null, flags); + canvas.saveUnclippedLayer(left, top, right, top + length); } if (drawBottom) { - canvas.saveLayer(left, bottom - length, right, bottom, null, flags); + canvas.saveUnclippedLayer(left, bottom - length, right, bottom); } if (drawLeft) { - canvas.saveLayer(left, top, left + length, bottom, null, flags); + canvas.saveUnclippedLayer(left, top, left + length, bottom); } if (drawRight) { - canvas.saveLayer(right - length, top, right, bottom, null, flags); + canvas.saveUnclippedLayer(right - length, top, right, bottom); } } else { scrollabilityCache.setFadeColor(solidColor); @@ -20427,7 +20757,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mForegroundInfo.mBoundsChanged = true; } - notifyAccessibilitySubtreeChanged(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } return changed; } @@ -21871,7 +22201,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (selected) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); } else { - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } } @@ -23458,8 +23789,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * constructor variant is only useful when the {@link #onProvideShadowMetrics(Point, Point)} * and {@link #onDrawShadow(Canvas)} methods are also overridden in order * to supply the drag shadow's dimensions and appearance without - * reference to any View object. If they are not overridden, then the result is an - * invisible drag shadow. + * reference to any View object. */ public DragShadowBuilder() { mView = new WeakReference<View>(null); @@ -23605,9 +23935,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Point shadowTouchPoint = new Point(); shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint); - if ((shadowSize.x <= 0) || (shadowSize.y <= 0) + if ((shadowSize.x < 0) || (shadowSize.y < 0) || (shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) { - throw new IllegalStateException("Drag shadow dimensions must be positive"); + throw new IllegalStateException("Drag shadow dimensions must not be negative"); + } + + // Create 1x1 surface when zero surface size is specified because SurfaceControl.Builder + // does not accept zero size surface. + if (shadowSize.x == 0 || shadowSize.y == 0) { + if (!sAcceptZeroSizeDragShadow) { + throw new IllegalStateException("Drag shadow dimensions must be positive"); + } + shadowSize.x = 1; + shadowSize.y = 1; } if (ViewDebug.DEBUG_DRAG) { @@ -25540,26 +25880,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Interface definition for a callback to be invoked when a hardware key event is - * dispatched to this view during the fallback phase. This means no view in the hierarchy - * has handled this event. + * Interface definition for a callback to be invoked when a hardware key event hasn't + * been handled by the view hierarchy. */ - public interface OnKeyFallbackListener { + public interface OnUnhandledKeyEventListener { /** - * Called when a hardware key is dispatched to a view in the fallback phase. This allows - * listeners to respond to events after the view hierarchy has had a chance to respond. - * <p>Key presses in software keyboards will generally NOT trigger this method, - * although some may elect to do so in some situations. Do not assume a - * software input method has to be key-based; even if it is, it may use key presses - * in a different way than you expect, so there is no way to reliably catch soft - * input key presses. + * Called when a hardware key is dispatched to a view after being unhandled during normal + * {@link KeyEvent} dispatch. * * @param v The view the key has been dispatched to. - * @param event The KeyEvent object containing full information about - * the event. - * @return True if the listener has consumed the event, false otherwise. + * @param event The KeyEvent object containing information about the event. + * @return {@code true} if the listener has consumed the event, {@code false} otherwise. */ - boolean onKeyFallback(View v, KeyEvent event); + boolean onUnhandledKeyEvent(View v, KeyEvent event); } /** @@ -26458,6 +26791,53 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Resuable callback for sending + * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. + */ + private class SendViewScrolledAccessibilityEvent implements Runnable { + public volatile boolean mIsPending; + public int mDeltaX; + public int mDeltaY; + + public void post(int dx, int dy) { + mDeltaX += dx; + mDeltaY += dy; + if (!mIsPending) { + mIsPending = true; + postDelayed(this, ViewConfiguration.getSendRecurringAccessibilityEventsInterval()); + } + } + + @Override + public void run() { + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_VIEW_SCROLLED); + event.setScrollDeltaX(mDeltaX); + event.setScrollDeltaY(mDeltaY); + sendAccessibilityEventUnchecked(event); + } + reset(); + } + + private void reset() { + mIsPending = false; + mDeltaX = 0; + mDeltaY = 0; + } + } + + /** + * Remove the pending callback for sending a + * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event. + */ + private void cancel(@Nullable SendViewScrolledAccessibilityEvent callback) { + if (callback == null || !callback.mIsPending) return; + removeCallbacks(callback); + callback.reset(); + } + + /** * <p> * This class represents a delegate that can be registered in a {@link View} * to enhance accessibility support via composition rather via inheritance. @@ -26903,6 +27283,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, stream.addProperty("drawing:scaleY", getScaleY()); stream.addProperty("drawing:pivotX", getPivotX()); stream.addProperty("drawing:pivotY", getPivotY()); + stream.addProperty("drawing:clipBounds", + mClipBounds == null ? null : mClipBounds.toString()); stream.addProperty("drawing:opaque", isOpaque()); stream.addProperty("drawing:alpha", getAlpha()); stream.addProperty("drawing:transitionAlpha", getTransitionAlpha()); @@ -26914,6 +27296,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, stream.addProperty("drawing:willNotCacheDrawing", willNotCacheDrawing()); stream.addProperty("drawing:drawingCacheEnabled", isDrawingCacheEnabled()); stream.addProperty("drawing:overlappingRendering", hasOverlappingRendering()); + stream.addProperty("drawing:outlineAmbientShadowColor", getOutlineAmbientShadowColor()); + stream.addProperty("drawing:outlineSpotShadowColor", getOutlineSpotShadowColor()); // focus stream.addProperty("focus:hasFocus", hasFocus()); @@ -27074,7 +27458,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mTooltipInfo.mTooltipPopup.show(this, x, y, fromTouch, mTooltipInfo.mTooltipText); mAttachInfo.mTooltipHost = this; // The available accessibility actions have changed - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded(CONTENT_CHANGE_TYPE_UNDEFINED); return true; } @@ -27094,7 +27478,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mTooltipHost = null; } // The available accessibility actions have changed - notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED); + notifyViewAccessibilityStateChangedIfNeeded(CONTENT_CHANGE_TYPE_UNDEFINED); } private boolean showLongClickTooltip(int x, int y) { @@ -27199,20 +27583,44 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * @return {@code true} if the default focus highlight is enabled, {@code false} otherwies. + * @hide + */ + @TestApi + public static boolean isDefaultFocusHighlightEnabled() { + return sUseDefaultFocusHighlight; + } + + /** + * Dispatch a previously unhandled {@link KeyEvent} to this view. Unlike normal key dispatch, + * this dispatches to ALL child views until it is consumed. The dispatch order is z-order + * (visually on-top views first). + * + * @param evt the previously unhandled {@link KeyEvent}. + * @return the {@link View} which consumed the event or {@code null} if not consumed. + */ + View dispatchUnhandledKeyEvent(KeyEvent evt) { + if (onUnhandledKeyEvent(evt)) { + return this; + } + return null; + } + + /** * Allows this view to handle {@link KeyEvent}s which weren't handled by normal dispatch. This * occurs after the normal view hierarchy dispatch, but before the window callback. By default, * this will dispatch into all the listeners registered via - * {@link #addKeyFallbackListener(OnKeyFallbackListener)} in last-in-first-out order (most - * recently added will receive events first). + * {@link #addOnUnhandledKeyEventListener(OnUnhandledKeyEventListener)} in last-in-first-out + * order (most recently added will receive events first). * - * @param event A not-previously-handled event. + * @param event An unhandled event. * @return {@code true} if the event was handled, {@code false} otherwise. - * @see #addKeyFallbackListener + * @see #addOnUnhandledKeyEventListener */ - public boolean onKeyFallback(@NonNull KeyEvent event) { - if (mListenerInfo != null && mListenerInfo.mKeyFallbackListeners != null) { - for (int i = mListenerInfo.mKeyFallbackListeners.size() - 1; i >= 0; --i) { - if (mListenerInfo.mKeyFallbackListeners.get(i).onKeyFallback(this, event)) { + boolean onUnhandledKeyEvent(@NonNull KeyEvent event) { + if (mListenerInfo != null && mListenerInfo.mUnhandledKeyListeners != null) { + for (int i = mListenerInfo.mUnhandledKeyListeners.size() - 1; i >= 0; --i) { + if (mListenerInfo.mUnhandledKeyListeners.get(i).onUnhandledKeyEvent(this, event)) { return true; } } @@ -27220,31 +27628,47 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return false; } + boolean hasUnhandledKeyListener() { + return (mListenerInfo != null && mListenerInfo.mUnhandledKeyListeners != null + && !mListenerInfo.mUnhandledKeyListeners.isEmpty()); + } + /** - * Adds a listener which will receive unhandled {@link KeyEvent}s. - * @param listener the receiver of fallback {@link KeyEvent}s. - * @see #onKeyFallback(KeyEvent) + * Adds a listener which will receive unhandled {@link KeyEvent}s. This must be called on the + * UI thread. + * + * @param listener a receiver of unhandled {@link KeyEvent}s. + * @see #removeOnUnhandledKeyEventListener */ - public void addKeyFallbackListener(OnKeyFallbackListener listener) { - ArrayList<OnKeyFallbackListener> fallbacks = getListenerInfo().mKeyFallbackListeners; - if (fallbacks == null) { - fallbacks = new ArrayList<>(); - getListenerInfo().mKeyFallbackListeners = fallbacks; + public void addOnUnhandledKeyEventListener(OnUnhandledKeyEventListener listener) { + ArrayList<OnUnhandledKeyEventListener> listeners = getListenerInfo().mUnhandledKeyListeners; + if (listeners == null) { + listeners = new ArrayList<>(); + getListenerInfo().mUnhandledKeyListeners = listeners; + } + listeners.add(listener); + if (listeners.size() == 1 && mParent instanceof ViewGroup) { + ((ViewGroup) mParent).incrementChildUnhandledKeyListeners(); } - fallbacks.add(listener); } /** - * Removes a listener which will receive unhandled {@link KeyEvent}s. - * @param listener the receiver of fallback {@link KeyEvent}s. - * @see #onKeyFallback(KeyEvent) + * Removes a listener which will receive unhandled {@link KeyEvent}s. This must be called on the + * UI thread. + * + * @param listener a receiver of unhandled {@link KeyEvent}s. + * @see #addOnUnhandledKeyEventListener */ - public void removeKeyFallbackListener(OnKeyFallbackListener listener) { + public void removeOnUnhandledKeyEventListener(OnUnhandledKeyEventListener listener) { if (mListenerInfo != null) { - if (mListenerInfo.mKeyFallbackListeners != null) { - mListenerInfo.mKeyFallbackListeners.remove(listener); - if (mListenerInfo.mKeyFallbackListeners.isEmpty()) { - mListenerInfo.mKeyFallbackListeners = null; + if (mListenerInfo.mUnhandledKeyListeners != null + && !mListenerInfo.mUnhandledKeyListeners.isEmpty()) { + mListenerInfo.mUnhandledKeyListeners.remove(listener); + if (mListenerInfo.mUnhandledKeyListeners.isEmpty()) { + mListenerInfo.mUnhandledKeyListeners = null; + if (mParent instanceof ViewGroup) { + ((ViewGroup) mParent).decrementChildUnhandledKeyListeners(); + } } } } |