Java Code Examples for androidx.core.view.ViewCompat#getImportantForAccessibility()

The following examples show how to use androidx.core.view.ViewCompat#getImportantForAccessibility() . You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example 1
Source File: TextSpec.java    From litho with Apache License 2.0 6 votes vote down vote up
@OnPopulateAccessibilityNode
static void onPopulateAccessibilityNode(
    View host,
    AccessibilityNodeInfoCompat node,
    @Prop(resType = ResType.STRING) CharSequence text,
    @Prop(optional = true, resType = ResType.BOOL) boolean isSingleLine) {
  if (ViewCompat.getImportantForAccessibility(host)
      == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
    ViewCompat.setImportantForAccessibility(host, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
  }
  CharSequence contentDescription = node.getContentDescription();
  node.setText(contentDescription != null ? contentDescription : text);
  node.setContentDescription(contentDescription != null ? contentDescription : text);

  node.addAction(AccessibilityNodeInfoCompat.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
  node.addAction(AccessibilityNodeInfoCompat.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
  node.setMovementGranularities(
      AccessibilityNodeInfoCompat.MOVEMENT_GRANULARITY_CHARACTER
          | AccessibilityNodeInfoCompat.MOVEMENT_GRANULARITY_WORD
          | AccessibilityNodeInfoCompat.MOVEMENT_GRANULARITY_PARAGRAPH);

  if (!isSingleLine) {
    node.setMultiLine(true);
  }
}
 
Example 2
Source File: ComponentHost.java    From litho with Apache License 2.0 5 votes vote down vote up
/**
 * Host views implement their own content description handling instead of just delegating to the
 * underlying view framework for performance reasons as the framework sets/resets content
 * description very frequently on host views and the underlying accessibility notifications might
 * cause performance issues. This is safe to do because the framework owns the accessibility state
 * and knows how to update it efficiently.
 */
@Override
public void setContentDescription(CharSequence contentDescription) {
  mContentDescription = contentDescription;

  if (!TextUtils.isEmpty(contentDescription)
      && ViewCompat.getImportantForAccessibility(this)
          == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
    ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
  }

  maybeInvalidateAccessibilityState();
}
 
Example 3
Source File: ComponentHost.java    From litho with Apache License 2.0 5 votes vote down vote up
protected void refreshAccessibilityDelegatesIfNeeded(boolean isAccessibilityEnabled) {
  if (isAccessibilityEnabled == mIsComponentAccessibilityDelegateSet) {
    return;
  }

  if (isAccessibilityEnabled && mComponentAccessibilityDelegate == null) {
    mComponentAccessibilityDelegate =
        new ComponentAccessibilityDelegate(
            this, this.isFocusable(), ViewCompat.getImportantForAccessibility(this));
  }

  ViewCompat.setAccessibilityDelegate(
      this, isAccessibilityEnabled ? mComponentAccessibilityDelegate : null);
  mIsComponentAccessibilityDelegateSet = isAccessibilityEnabled;

  if (!isAccessibilityEnabled) {
    return;
  }

  for (int i = 0, size = getChildCount(); i < size; i++) {
    final View child = getChildAt(i);
    if (child instanceof ComponentHost) {
      ((ComponentHost) child).refreshAccessibilityDelegatesIfNeeded(true);
    } else {
      final NodeInfo nodeInfo = (NodeInfo) child.getTag(R.id.component_node_info);
      if (nodeInfo != null) {
        ViewCompat.setAccessibilityDelegate(
            child,
            new ComponentAccessibilityDelegate(
                child,
                nodeInfo,
                child.isFocusable(),
                ViewCompat.getImportantForAccessibility(child)));
      }
    }
  }
}
 
Example 4
Source File: VerticalViewPager.java    From DKVideoPlayer with Apache License 2.0 5 votes vote down vote up
void initViewPager() {
    setWillNotDraw(false);
    setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
    setFocusable(true);
    final Context context = getContext();
    mScroller = new Scroller(context, sInterpolator);
    final ViewConfiguration configuration = ViewConfiguration.get(context);
    final float density = context.getResources().getDisplayMetrics().density;

    mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
    mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
    mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    mTopEdge = new EdgeEffectCompat(context);
    mBottomEdge = new EdgeEffectCompat(context);

    mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
    mCloseEnough = (int) (CLOSE_ENOUGH * density);
    mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);

    ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());

    if (ViewCompat.getImportantForAccessibility(this)
            == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
        ViewCompat.setImportantForAccessibility(this,
                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
    }
}
 
Example 5
Source File: SwipeDismissBehavior.java    From material-components-android with Apache License 2.0 5 votes vote down vote up
@Override
public boolean onLayoutChild(
    @NonNull CoordinatorLayout parent, @NonNull V child, int layoutDirection) {
  boolean handled = super.onLayoutChild(parent, child, layoutDirection);
  if (ViewCompat.getImportantForAccessibility(child)
      == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
    ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
    updateAccessibilityActions(child);
  }
  return handled;
}
 
Example 6
Source File: AccessibilityUtil.java    From screenshot-tests-for-android with Apache License 2.0 5 votes vote down vote up
/**
 * Returns whether the supplied {@link View} and {@link AccessibilityNodeInfoCompat} would produce
 * spoken feedback if it were accessibility focused. NOTE: not all speaking nodes are focusable.
 *
 * @param view The {@link View} to evaluate
 * @param node The {@link AccessibilityNodeInfoCompat} to evaluate
 * @return {@code true} if it meets the criterion for producing spoken feedback
 */
public static boolean isSpeakingNode(
    @Nullable AccessibilityNodeInfoCompat node, @Nullable View view) {
  if (node == null || view == null) {
    return false;
  }

  final int important = ViewCompat.getImportantForAccessibility(view);
  if (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
      || (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO && node.getChildCount() <= 0)) {
    return false;
  }

  return node.isCheckable() || hasText(node) || hasNonActionableSpeakingDescendants(node, view);
}
 
Example 7
Source File: SliderPager.java    From Android-Image-Slider with Apache License 2.0 4 votes vote down vote up
void initSliderPager() {
    setWillNotDraw(false);
    setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
    setFocusable(true);
    final Context context = getContext();
    mScroller = new OwnScroller(context, DEFAULT_SCROLL_DURATION, sInterpolator);
    final ViewConfiguration configuration = ViewConfiguration.get(context);
    final float density = context.getResources().getDisplayMetrics().density;

    mTouchSlop = configuration.getScaledPagingTouchSlop();
    mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
    mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    mLeftEdge = new EdgeEffect(context);
    mRightEdge = new EdgeEffect(context);

    mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
    mCloseEnough = (int) (CLOSE_ENOUGH * density);
    mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);

    ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());

    if (ViewCompat.getImportantForAccessibility(this)
            == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
        ViewCompat.setImportantForAccessibility(this,
                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
    }

    ViewCompat.setOnApplyWindowInsetsListener(this,
            new androidx.core.view.OnApplyWindowInsetsListener() {
                private final Rect mTempRect = new Rect();

                @Override
                public WindowInsetsCompat onApplyWindowInsets(final View v,
                                                              final WindowInsetsCompat originalInsets) {
                    // First let the SliderPager itself try and consume them...
                    final WindowInsetsCompat applied =
                            ViewCompat.onApplyWindowInsets(v, originalInsets);
                    if (applied.isConsumed()) {
                        // If the SliderPager consumed all insets, return now
                        return applied;
                    }

                    // Now we'll manually dispatch the insets to our children. Since SliderPager
                    // children are always full-height, we do not want to use the standard
                    // ViewGroup dispatchApplyWindowInsets since if child 0 consumes them,
                    // the rest of the children will not receive any insets. To workaround this
                    // we manually dispatch the applied insets, not allowing children to
                    // consume them from each other. We do however keep track of any insets
                    // which are consumed, returning the union of our children's consumption
                    final Rect res = mTempRect;
                    res.left = applied.getSystemWindowInsetLeft();
                    res.top = applied.getSystemWindowInsetTop();
                    res.right = applied.getSystemWindowInsetRight();
                    res.bottom = applied.getSystemWindowInsetBottom();

                    for (int i = 0, count = getChildCount(); i < count; i++) {
                        final WindowInsetsCompat childInsets = ViewCompat
                                .dispatchApplyWindowInsets(getChildAt(i), applied);
                        // Now keep track of any consumed by tracking each dimension's min
                        // value
                        res.left = Math.min(childInsets.getSystemWindowInsetLeft(),
                                res.left);
                        res.top = Math.min(childInsets.getSystemWindowInsetTop(),
                                res.top);
                        res.right = Math.min(childInsets.getSystemWindowInsetRight(),
                                res.right);
                        res.bottom = Math.min(childInsets.getSystemWindowInsetBottom(),
                                res.bottom);
                    }

                    // Now return a new WindowInsets, using the consumed window insets
                    return applied.replaceSystemWindowInsets(
                            res.left, res.top, res.right, res.bottom);
                }
            });
}
 
Example 8
Source File: RadialTimePickerView.java    From DateTimePicker with Apache License 2.0 4 votes vote down vote up
public RadialTimePickerView(
        Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)  {
    super(context, attrs);

    applyAttributes(attrs, defStyleAttr, defStyleRes);

    // Pull disabled alpha from theme.
    final TypedValue outValue = new TypedValue();
    context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
    mDisabledAlpha = outValue.getFloat();

    mTypeface = Typeface.create("sans-serif", Typeface.NORMAL);

    mPaint[HOURS] = new Paint();
    mPaint[HOURS].setAntiAlias(true);
    mPaint[HOURS].setTextAlign(Paint.Align.CENTER);

    mPaint[MINUTES] = new Paint();
    mPaint[MINUTES].setAntiAlias(true);
    mPaint[MINUTES].setTextAlign(Paint.Align.CENTER);

    mPaintCenter.setAntiAlias(true);

    mPaintSelector[SELECTOR_CIRCLE] = new Paint();
    mPaintSelector[SELECTOR_CIRCLE].setAntiAlias(true);

    mPaintSelector[SELECTOR_DOT] = new Paint();
    mPaintSelector[SELECTOR_DOT].setAntiAlias(true);

    mPaintSelector[SELECTOR_LINE] = new Paint();
    mPaintSelector[SELECTOR_LINE].setAntiAlias(true);
    mPaintSelector[SELECTOR_LINE].setStrokeWidth(2);

    mPaintBackground.setAntiAlias(true);

    final Resources res = getResources();
    mSelectorRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_radius);
    mSelectorStroke = res.getDimensionPixelSize(R.dimen.timepicker_selector_stroke);
    mSelectorDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_dot_radius);
    mCenterDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_center_dot_radius);

    mTextSize[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal);
    mTextSize[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal);
    mTextSize[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_inner);

    mTextInset[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal);
    mTextInset[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal);
    mTextInset[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_inner);

    mShowHours = true;
    mHoursToMinutes = HOURS;
    mIs24HourMode = false;
    mAmOrPm = AM;

    // Set up accessibility components.
    mTouchHelper = new RadialPickerTouchHelper();
    ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
    //setAccessibilityDelegate(mTouchHelper);

    if(ViewCompat.getImportantForAccessibility(this) == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO){
        ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
    }
    /*if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
    }*/

    initHoursAndMinutesText();
    initData();

    // Initial values
    final Calendar calendar = Calendar.getInstance(Locale.getDefault());
    final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
    final int currentMinute = calendar.get(Calendar.MINUTE);

    setCurrentHourInternal(currentHour, false, false);
    setCurrentMinuteInternal(currentMinute, false);

    setHapticFeedbackEnabled(true);
}
 
Example 9
Source File: CustomViewPager.java    From AsteroidOSSync with GNU General Public License v3.0 4 votes vote down vote up
void initViewPager() {
    setWillNotDraw(false);
    setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
    setFocusable(true);
    final Context context = getContext();
    mScroller = new Scroller(context, sInterpolator);
    final ViewConfiguration configuration = ViewConfiguration.get(context);
    final float density = context.getResources().getDisplayMetrics().density;

    mTouchSlop = configuration.getScaledPagingTouchSlop();
    mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
    mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    mLeftEdge = new EdgeEffectCompat(context);
    mRightEdge = new EdgeEffectCompat(context);

    mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
    mCloseEnough = (int) (CLOSE_ENOUGH * density);
    mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);

    ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());

    if (ViewCompat.getImportantForAccessibility(this)
            == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
        ViewCompat.setImportantForAccessibility(this,
                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
    }

    ViewCompat.setOnApplyWindowInsetsListener(this,
            new androidx.core.view.OnApplyWindowInsetsListener() {
                private final Rect mTempRect = new Rect();

                @Override
                public WindowInsetsCompat onApplyWindowInsets(final View v,
                                                              final WindowInsetsCompat originalInsets) {
                    // First let the ViewPager itself try and consume them...
                    final WindowInsetsCompat applied =
                            ViewCompat.onApplyWindowInsets(v, originalInsets);
                    if (applied.isConsumed()) {
                        // If the ViewPager consumed all insets, return now
                        return applied;
                    }

                    // Now we'll manually dispatch the insets to our children. Since ViewPager
                    // children are always full-height, we do not want to use the standard
                    // ViewGroup dispatchApplyWindowInsets since if child 0 consumes them,
                    // the rest of the children will not receive any insets. To workaround this
                    // we manually dispatch the applied insets, not allowing children to
                    // consume them from each other. We do however keep track of any insets
                    // which are consumed, returning the union of our children's consumption
                    final Rect res = mTempRect;
                    res.left = applied.getSystemWindowInsetLeft();
                    res.top = applied.getSystemWindowInsetTop();
                    res.right = applied.getSystemWindowInsetRight();
                    res.bottom = applied.getSystemWindowInsetBottom();

                    for (int i = 0, count = getChildCount(); i < count; i++) {
                        final WindowInsetsCompat childInsets = ViewCompat
                                .dispatchApplyWindowInsets(getChildAt(i), applied);
                        // Now keep track of any consumed by tracking each dimension's min
                        // value
                        res.left = Math.min(childInsets.getSystemWindowInsetLeft(),
                                res.left);
                        res.top = Math.min(childInsets.getSystemWindowInsetTop(),
                                res.top);
                        res.right = Math.min(childInsets.getSystemWindowInsetRight(),
                                res.right);
                        res.bottom = Math.min(childInsets.getSystemWindowInsetBottom(),
                                res.bottom);
                    }

                    // Now return a new WindowInsets, using the consumed window insets
                    return applied.replaceSystemWindowInsets(
                            res.left, res.top, res.right, res.bottom);
                }
            });
}
 
Example 10
Source File: BottomSheetBehavior.java    From material-components-android with Apache License 2.0 4 votes vote down vote up
@Override
public boolean onLayoutChild(
    @NonNull CoordinatorLayout parent, @NonNull V child, int layoutDirection) {
  if (ViewCompat.getFitsSystemWindows(parent) && !ViewCompat.getFitsSystemWindows(child)) {
    child.setFitsSystemWindows(true);
  }

  if (viewRef == null) {
    // First layout with this behavior.
    peekHeightMin =
        parent.getResources().getDimensionPixelSize(R.dimen.design_bottom_sheet_peek_height_min);
    setSystemGestureInsets(parent);
    viewRef = new WeakReference<>(child);
    // Only set MaterialShapeDrawable as background if shapeTheming is enabled, otherwise will
    // default to android:background declared in styles or layout.
    if (shapeThemingEnabled && materialShapeDrawable != null) {
      ViewCompat.setBackground(child, materialShapeDrawable);
    }
    // Set elevation on MaterialShapeDrawable
    if (materialShapeDrawable != null) {
      // Use elevation attr if set on bottomsheet; otherwise, use elevation of child view.
      materialShapeDrawable.setElevation(
          elevation == -1 ? ViewCompat.getElevation(child) : elevation);
      // Update the material shape based on initial state.
      isShapeExpanded = state == STATE_EXPANDED;
      materialShapeDrawable.setInterpolation(isShapeExpanded ? 0f : 1f);
    }
    updateAccessibilityActions();
    if (ViewCompat.getImportantForAccessibility(child)
        == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
      ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
    }
  }
  if (viewDragHelper == null) {
    viewDragHelper = ViewDragHelper.create(parent, dragCallback);
  }

  int savedTop = child.getTop();
  // First let the parent lay it out
  parent.onLayoutChild(child, layoutDirection);
  // Offset the bottom sheet
  parentWidth = parent.getWidth();
  parentHeight = parent.getHeight();
  fitToContentsOffset = Math.max(0, parentHeight - child.getHeight());
  calculateHalfExpandedOffset();
  calculateCollapsedOffset();

  if (state == STATE_EXPANDED) {
    ViewCompat.offsetTopAndBottom(child, getExpandedOffset());
  } else if (state == STATE_HALF_EXPANDED) {
    ViewCompat.offsetTopAndBottom(child, halfExpandedOffset);
  } else if (hideable && state == STATE_HIDDEN) {
    ViewCompat.offsetTopAndBottom(child, parentHeight);
  } else if (state == STATE_COLLAPSED) {
    ViewCompat.offsetTopAndBottom(child, collapsedOffset);
  } else if (state == STATE_DRAGGING || state == STATE_SETTLING) {
    ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop());
  }

  nestedScrollingChildRef = new WeakReference<>(findScrollingChild(child));
  return true;
}
 
Example 11
Source File: BottomSheetBehavior.java    From Mysplash with GNU Lesser General Public License v3.0 4 votes vote down vote up
@Override
public boolean onLayoutChild(
        @NonNull CoordinatorLayout parent, @NonNull V child, int layoutDirection) {
    if (ViewCompat.getFitsSystemWindows(parent) && !ViewCompat.getFitsSystemWindows(child)) {
        child.setFitsSystemWindows(true);
    }

    if (viewRef == null) {
        // First layout with this behavior.
        peekHeightMin =
                parent.getResources().getDimensionPixelSize(R.dimen.design_bottom_sheet_peek_height_min);
        viewRef = new WeakReference<>(child);
        // Only set MaterialShapeDrawable as background if shapeTheming is enabled, otherwise will
        // default to android:background declared in styles or layout.
        if (shapeThemingEnabled && materialShapeDrawable != null) {
            ViewCompat.setBackground(child, materialShapeDrawable);
        }
        // Set elevation on MaterialShapeDrawable
        if (materialShapeDrawable != null) {
            // Use elevation attr if set on bottomsheet; otherwise, use elevation of child view.
            materialShapeDrawable.setElevation(
                    elevation == -1 ? ViewCompat.getElevation(child) : elevation);
            // Update the material shape based on initial state.
            isShapeExpanded = state == STATE_EXPANDED;
            materialShapeDrawable.setInterpolation(isShapeExpanded ? 0f : 1f);
        }
        updateAccessibilityActions();
        if (ViewCompat.getImportantForAccessibility(child)
                == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
            ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
        }
    }
    if (viewDragHelper == null) {
        viewDragHelper = ViewDragHelper.create(parent, dragCallback);
    }

    int savedTop = child.getTop();
    // First let the parent lay it out
    parent.onLayoutChild(child, layoutDirection);
    // Offset the bottom sheet
    parentWidth = parent.getWidth();
    parentHeight = parent.getHeight();
    fitToContentsOffset = Math.max(0, parentHeight - child.getHeight());
    calculateHalfExpandedOffset();
    calculateCollapsedOffset();

    if (state == STATE_EXPANDED) {
        ViewCompat.offsetTopAndBottom(child, getExpandedOffset());
    } else if (state == STATE_HALF_EXPANDED) {
        ViewCompat.offsetTopAndBottom(child, halfExpandedOffset);
    } else if (hideable && state == STATE_HIDDEN) {
        ViewCompat.offsetTopAndBottom(child, parentHeight);
    } else if (state == STATE_COLLAPSED) {
        ViewCompat.offsetTopAndBottom(child, collapsedOffset);
    } else if (state == STATE_DRAGGING || state == STATE_SETTLING) {
        ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop());
    }

    nestedScrollingChildRef = new WeakReference<>(findScrollingChild(child));
    return true;
}
 
Example 12
Source File: AccessibilityUtil.java    From screenshot-tests-for-android with Apache License 2.0 4 votes vote down vote up
/**
 * Returns whether a given {@link View} will be focusable by Google's TalkBack screen reader.
 *
 * @param view The {@link View} to evaluate.
 * @return {@code boolean} if the view will be ignored by TalkBack.
 */
public static boolean isTalkbackFocusable(View view) {
  if (view == null) {
    return false;
  }

  final int important = ViewCompat.getImportantForAccessibility(view);
  if (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO
      || important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
    return false;
  }

  // Go all the way up the tree to make sure no parent has hidden its descendants
  ViewParent parent = view.getParent();
  while (parent instanceof View) {
    if (ViewCompat.getImportantForAccessibility((View) parent)
        == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
      return false;
    }
    parent = parent.getParent();
  }

  // Trying to evaluate the focusability of certain element types (mainly list views) can cause
  // problems when trying to determine the offset of the elements Rect relative to its parent in
  // ViewGroup.offsetRectBetweenParentAndChild. If this happens, simply return false, as this view
  // will not be focusable.
  AccessibilityNodeInfoCompat node;
  try {
    node = createNodeInfoFromView(view);
  } catch (IllegalArgumentException e) {
    return false;
  }

  if (node == null) {
    return false;
  }

  // Non-leaf nodes identical in size to their Window should not be focusable.
  if (areBoundsIdenticalToWindow(node, view) && node.getChildCount() > 0) {
    return false;
  }

  try {
    if (!node.isVisibleToUser()) {
      return false;
    }

    if (isAccessibilityFocusable(node, view)) {
      if (!hasVisibleChildren(view)) {
        // Leaves that are accessibility focusable are never ignored, even if they don't have a
        // speakable description
        return true;
      } else if (isSpeakingNode(node, view)) {
        // Node is focusable and has something to speak
        return true;
      }

      // Node is focusable and has nothing to speak
      return false;
    }

    // if view is not accessibility focusable, it needs to have text and no focusable ancestors.
    if (!hasText(node)) {
      return false;
    }

    if (!hasFocusableAncestor(node, view)) {
      return true;
    }

    return false;
  } finally {
    node.recycle();
  }
}