chromium/android_webview/java/src/org/chromium/android_webview/ScrollAccessibilityHelper.java

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.android_webview;

import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;

/**
 * Helper used to post the VIEW_SCROLLED accessibility event.
 *
 * TODO(mkosiba): Investigate whether this is behavior we want to share with the chrome/ layer.
 * TODO(mkosiba): We currently don't handle JS-initiated scrolling for layers other than the root
 * layer.
 */
class ScrollAccessibilityHelper {
    // This is copied straight out of android.view.ViewConfiguration.
    private static final long SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS = 100;

    private class HandlerCallback implements Handler.Callback {
        public static final int MSG_VIEW_SCROLLED = 1;

        private View mEventSender;

        public HandlerCallback(View eventSender) {
            mEventSender = eventSender;
        }

        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_VIEW_SCROLLED:
                    mMsgViewScrolledQueued = false;
                    mEventSender.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
                    break;
                default:
                    throw new IllegalStateException(
                            "AccessibilityInjector: unhandled message: " + msg.what);
            }
            return true;
        }
    }

    private Handler mHandler;
    private boolean mMsgViewScrolledQueued;
    private boolean mIsInAScroll;
    private boolean mEventSentByViewBaseClass;
    private final Runnable mSendRecurringViewScrolledEvents =
            new Runnable() {
                @Override
                public void run() {
                    if (!mEventSentByViewBaseClass) {
                        Message msg = mHandler.obtainMessage(HandlerCallback.MSG_VIEW_SCROLLED);
                        mHandler.sendMessage(msg);
                    } else {
                        mEventSentByViewBaseClass = false;
                    }
                    if (mIsInAScroll) {
                        mHandler.postDelayed(
                                this, SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS);
                    }
                }
            };

    public ScrollAccessibilityHelper(View eventSender) {
        mHandler = new Handler(new HandlerCallback(eventSender));
    }

    /**
     * Post a callback to send a {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
     * This event is sent at most once every
     * {@link android.view.ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
     */
    public void postViewScrolledAccessibilityEventCallback() {
        if (mMsgViewScrolledQueued) return;
        mMsgViewScrolledQueued = true;
        mEventSentByViewBaseClass = false;

        Message msg = mHandler.obtainMessage(HandlerCallback.MSG_VIEW_SCROLLED);
        mHandler.sendMessageDelayed(msg, SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS);
    }

    public void setIsInAScroll(boolean isScrolling) {
        mIsInAScroll = isScrolling;
        if (isScrolling) {
            mHandler.postDelayed(
                    mSendRecurringViewScrolledEvents,
                    SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS);
        }
    }

    public void removePostedViewScrolledAccessibilityEventCallback() {
        mEventSentByViewBaseClass = true;
        if (!mMsgViewScrolledQueued) return;
        mMsgViewScrolledQueued = false;

        mHandler.removeMessages(HandlerCallback.MSG_VIEW_SCROLLED);
    }

    public void removePostedCallbacks() {
        removePostedViewScrolledAccessibilityEventCallback();
        mHandler.removeCallbacks(mSendRecurringViewScrolledEvents);
    }
}