com.facebook.litho.ComponentAccessibilityDelegate.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.litho.ComponentAccessibilityDelegate.java

Source

/**
 * Copyright (c) 2017-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.litho;

import java.util.List;

import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
import android.support.v4.widget.ExploreByTouchHelper;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;

/**
 * Class that is used to set up accessibility for {@link ComponentHost}s.
 * Virtual nodes are only exposed if the component implements support for
 * extra accessibility nodes.
 */
class ComponentAccessibilityDelegate extends ExploreByTouchHelper {
    private static final String TAG = "ComponentAccessibility";

    private final View mView;
    private NodeInfo mNodeInfo;
    private final AccessibilityDelegateCompat mSuperDelegate;
    private static Rect sDefaultBounds;

    ComponentAccessibilityDelegate(View view, NodeInfo nodeInfo) {
        super(view);
        mView = view;
        mNodeInfo = nodeInfo;
        mSuperDelegate = new SuperDelegate();
    }

    ComponentAccessibilityDelegate(View view) {
        this(view, null);
    }

    /**
     * {@link ComponentHost} contains the logic for setting the {@link NodeInfo} containing the
     * {@link EventHandler}s for its delegate instance whenever it is set/unset
     *
     * @see ComponentHost#setTag(int, Object)
     */
    void setNodeInfo(NodeInfo nodeInfo) {
        mNodeInfo = nodeInfo;
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat node) {
        final MountItem mountItem = getAccessibleMountItem(mView);

        if (mNodeInfo != null && mNodeInfo.getOnInitializeAccessibilityNodeInfoHandler() != null) {
            EventDispatcherUtils.dispatchOnInitializeAccessibilityNodeInfoEvent(
                    mNodeInfo.getOnInitializeAccessibilityNodeInfoHandler(), host, node, mSuperDelegate);
        } else if (mountItem != null) {
            super.onInitializeAccessibilityNodeInfo(host, node);
            // Coalesce the accessible mount item's information with the
            // the root host view's as they are meant to behave as a single
            // node in the accessibility framework.
            final Component<?> component = mountItem.getComponent();
            component.getLifecycle().onPopulateAccessibilityNode(node, component);
        } else {
            super.onInitializeAccessibilityNodeInfo(host, node);
        }
    }

    @Override
    protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
        final MountItem mountItem = getAccessibleMountItem(mView);
        if (mountItem == null) {
            return;
        }

        final Component<?> component = mountItem.getComponent();

        final int extraAccessibilityNodesCount = component.getLifecycle()
                .getExtraAccessibilityNodesCount(component);

        // Expose extra accessibility nodes declared by the component to the
        // accessibility framework. The actual nodes will be populated in
        // {@link #onPopulateNodeForVirtualView}.
        for (int i = 0; i < extraAccessibilityNodesCount; i++) {
            virtualViewIds.add(i);
        }
    }

    @Override
    protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfoCompat node) {
        final MountItem mountItem = getAccessibleMountItem(mView);
        if (mountItem == null) {
            Log.e(TAG, "No accessible mount item found for view: " + mView);

            // ExploreByTouchHelper insists that we set something.
            node.setContentDescription("");
            node.setBoundsInParent(getDefaultBounds());
            return;
        }

        final Drawable drawable = (Drawable) mountItem.getContent();
        final Rect bounds = drawable.getBounds();

        final Component<?> component = mountItem.getComponent();
        final ComponentLifecycle lifecycle = component.getLifecycle();

        node.setClassName(lifecycle.getClass().getName());

        if (virtualViewId >= lifecycle.getExtraAccessibilityNodesCount(component)) {
            Log.e(TAG, "Received unrecognized virtual view id: " + virtualViewId);

            // ExploreByTouchHelper insists that we set something.
            node.setContentDescription("");
            node.setBoundsInParent(getDefaultBounds());
            return;
        }

        lifecycle.onPopulateExtraAccessibilityNode(node, virtualViewId, bounds.left, bounds.top, component);
    }

    /**
     * Finds extra accessibility nodes under the given event coordinates.
     * Returns {@link #INVALID_ID} otherwise.
     */
    @Override
    protected int getVirtualViewAt(float x, float y) {
        final MountItem mountItem = getAccessibleMountItem(mView);
        if (mountItem == null) {
            return INVALID_ID;
        }

        final Component<?> component = mountItem.getComponent();
        final ComponentLifecycle lifecycle = component.getLifecycle();

        if (lifecycle.getExtraAccessibilityNodesCount(component) == 0) {
            return INVALID_ID;
        }

        final Drawable drawable = (Drawable) mountItem.getContent();
        final Rect bounds = drawable.getBounds();

        // Try to find an extra accessibility node that intersects with
        // the given coordinates.
        final int virtualViewId = lifecycle.getExtraAccessibilityNodeAt((int) x - bounds.left, (int) y - bounds.top,
                component);

        return (virtualViewId >= 0 ? virtualViewId : INVALID_ID);
    }

    @Override
    protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
        // TODO (T10543861): ExploreByTouchHelper enforces subclasses to set a content description
        // or text on new events but components don't provide APIs to do so yet.
        event.setContentDescription("");
    }

    @Override
    protected boolean onPerformActionForVirtualView(int virtualViewId, int action, Bundle arguments) {
        return false;
    }

    /**
     * Returns a {AccessibilityNodeProviderCompat} if the host contains a component
     * that implements custom accessibility logic. Returns {@code NULL} otherwise.
     * Components with accessibility content are automatically wrapped in hosts by
     * {@link LayoutState}.
     */
    @Override
    public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) {
        final MountItem mountItem = getAccessibleMountItem(mView);
        if (mountItem != null && mountItem.getComponent().getLifecycle().implementsExtraAccessibilityNodes()) {
            return super.getAccessibilityNodeProvider(host);
        }

        return null;
    }

    private static MountItem getAccessibleMountItem(View view) {
        if (!(view instanceof ComponentHost)) {
            return null;
        }

        return ((ComponentHost) view).getAccessibleMountItem();
    }

    @Override
    public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
        if (mNodeInfo != null && mNodeInfo.getOnInitializeAccessibilityEventHandler() != null) {
            EventDispatcherUtils.dispatchOnInitializeAccessibilityEvent(
                    mNodeInfo.getOnInitializeAccessibilityEventHandler(), host, event, mSuperDelegate);
        } else {
            super.onInitializeAccessibilityEvent(host, event);
        }
    }

    @Override
    public void sendAccessibilityEvent(View host, int eventType) {
        if (mNodeInfo != null && mNodeInfo.getSendAccessibilityEventHandler() != null) {
            EventDispatcherUtils.dispatchSendAccessibilityEvent(mNodeInfo.getSendAccessibilityEventHandler(), host,
                    eventType, mSuperDelegate);
        } else {
            super.sendAccessibilityEvent(host, eventType);
        }
    }

    @Override
    public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
        if (mNodeInfo != null && mNodeInfo.getSendAccessibilityEventUncheckedHandler() != null) {
            EventDispatcherUtils.dispatchSendAccessibilityEventUnchecked(
                    mNodeInfo.getSendAccessibilityEventUncheckedHandler(), host, event, mSuperDelegate);
        } else {
            super.sendAccessibilityEventUnchecked(host, event);
        }
    }

    @Override
    public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
        if (mNodeInfo != null && mNodeInfo.getDispatchPopulateAccessibilityEventHandler() != null) {
            return EventDispatcherUtils.dispatchDispatchPopulateAccessibilityEvent(
                    mNodeInfo.getDispatchPopulateAccessibilityEventHandler(), host, event, mSuperDelegate);
        }

        return super.dispatchPopulateAccessibilityEvent(host, event);
    }

    @Override
    public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
        if (mNodeInfo != null && mNodeInfo.getOnPopulateAccessibilityEventHandler() != null) {
            EventDispatcherUtils.dispatchOnPopulateAccessibilityEvent(
                    mNodeInfo.getOnPopulateAccessibilityEventHandler(), host, event, mSuperDelegate);
        } else {
            super.onPopulateAccessibilityEvent(host, event);
        }
    }

    @Override
    public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event) {
        if (mNodeInfo != null && mNodeInfo.getOnRequestSendAccessibilityEventHandler() != null) {
            return EventDispatcherUtils.dispatchOnRequestSendAccessibilityEvent(
                    mNodeInfo.getOnRequestSendAccessibilityEventHandler(), host, child, event, mSuperDelegate);
        }

        return super.onRequestSendAccessibilityEvent(host, child, event);
    }

    @Override
    public boolean performAccessibilityAction(View host, int action, Bundle args) {
        if (mNodeInfo != null && mNodeInfo.getPerformAccessibilityActionHandler() != null) {
            return EventDispatcherUtils.dispatchPerformAccessibilityActionEvent(
                    mNodeInfo.getPerformAccessibilityActionHandler(), host, action, args, mSuperDelegate);
        }

        return super.performAccessibilityAction(host, action, args);
    }

    private static Rect getDefaultBounds() {
        synchronized (sDefaultBounds) {
            if (sDefaultBounds == null) {
                sDefaultBounds = new Rect(0, 0, 1, 1);
            }
        }

        return sDefaultBounds;
    }

    private class SuperDelegate extends AccessibilityDelegateCompat {

        @Override
        public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
            return ComponentAccessibilityDelegate.super.dispatchPopulateAccessibilityEvent(host, event);
        }

        @Override
        public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
            ComponentAccessibilityDelegate.super.onInitializeAccessibilityEvent(host, event);
        }

        @Override
        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat node) {
            ComponentAccessibilityDelegate.super.onInitializeAccessibilityNodeInfo(host, node);
        }

        @Override
        public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
            ComponentAccessibilityDelegate.super.onPopulateAccessibilityEvent(host, event);
        }

        @Override
        public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event) {
            return ComponentAccessibilityDelegate.super.onRequestSendAccessibilityEvent(host, child, event);
        }

        @Override
        public boolean performAccessibilityAction(View host, int action, Bundle args) {
            return ComponentAccessibilityDelegate.super.performAccessibilityAction(host, action, args);
        }

        @Override
        public void sendAccessibilityEvent(View host, int eventType) {
            ComponentAccessibilityDelegate.super.sendAccessibilityEvent(host, eventType);
        }

        @Override
        public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
            ComponentAccessibilityDelegate.super.sendAccessibilityEventUnchecked(host, event);
        }
    }
}