com.android.screenspeak.formatter.CheckableClickedFormatter.java Source code

Java tutorial

Introduction

Here is the source code for com.android.screenspeak.formatter.CheckableClickedFormatter.java

Source

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.android.screenspeak.formatter;

import android.os.Build;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
import android.text.TextUtils;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Switch;
import android.widget.ToggleButton;

import com.android.screenspeak.R;
import com.google.android.marvin.screenspeak.ScreenSpeakService;
import com.android.screenspeak.Utterance;
import com.android.utils.AccessibilityEventUtils;
import com.android.utils.AccessibilityNodeInfoUtils;

/**
 * Filters and formats click events from checkable items.
 */
public class CheckableClickedFormatter
        implements EventSpeechRule.AccessibilityEventFilter, EventSpeechRule.AccessibilityEventFormatter {

    private long mClickedTime = -1;
    private AccessibilityNodeInfoCompat mClickedNode;

    // accept and format are not called from the same class instance
    private static AccessibilityNodeInfoCompat sCachedCheckableNode;

    private boolean findClickedCheckableNode(AccessibilityNodeInfoCompat source) {
        if (source == null)
            return false;

        if (source.equals(mClickedNode)) {
            boolean ret = findCheckableNode(source);
            if (mClickedNode != null) {
                mClickedNode.recycle();
                mClickedNode = null;
            }

            mClickedTime = -1;
            return ret;
        }

        int children = source.getChildCount();
        for (int i = 0; i < children; i++) {
            AccessibilityNodeInfoCompat node = source.getChild(i);
            if (findClickedCheckableNode(node)) {
                if (!sCachedCheckableNode.equals(node)) {
                    node.recycle();
                }
                return true;
            }

            if (node != null) {
                node.recycle();
            }
        }

        return false;
    }

    private boolean findCheckableNode(AccessibilityNodeInfoCompat source) {
        if (source.isCheckable()) {
            sCachedCheckableNode = source;
            return true;
        }

        int children = source.getChildCount();
        for (int i = 0; i < children; i++) {
            AccessibilityNodeInfoCompat node = source.getChild(i);
            if (findCheckableNode(node)) {
                if (!sCachedCheckableNode.equals(node)) {
                    node.recycle();
                }
                return true;
            }

            if (node != null) {
                node.recycle();
            }
        }

        return false;
    }

    @Override
    public boolean accept(AccessibilityEvent event, ScreenSpeakService context) {
        int type = event.getEventType();

        if (type == AccessibilityEvent.TYPE_VIEW_CLICKED) {
            mClickedNode = null;
            mClickedTime = -1;

            if (event.isChecked()) {
                return true;
            }

            AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
            AccessibilityNodeInfoCompat source = record.getSource();
            if (source != null) {
                if (source.isCheckable()) {
                    return true;
                }

                // it is bug in settings application that does not include clicked state on node
                // so we need to restore it later from TYPE_WINDOW_CONTENT_CHANGED event
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    mClickedNode = source;
                    mClickedTime = System.currentTimeMillis();
                }
            }

            return false;
        }

        if (type == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
                return false;
            if (mClickedTime == -1 || mClickedNode == null)
                return false;

            long now = System.currentTimeMillis();
            if ((mClickedTime + 1000) < now) {
                mClickedTime = -1;
                if (mClickedNode != null) {
                    mClickedNode.recycle();
                    mClickedNode = null;
                }
                return false;
            }

            AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
            AccessibilityNodeInfoCompat source = record.getSource();
            return findClickedCheckableNode(source);
        }

        return false;
    }

    @Override
    public boolean format(AccessibilityEvent event, ScreenSpeakService context, Utterance utterance) {
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
            if (sCachedCheckableNode == null)
                return false;

            utterance.addAuditory(R.raw.tick);
            utterance.addHaptic(R.array.view_clicked_pattern);
            utterance.addSpoken(context.getString(
                    sCachedCheckableNode.isChecked() ? R.string.value_checked : R.string.value_not_checked));
            sCachedCheckableNode.recycle();
            sCachedCheckableNode = null;
            return true;
        }

        AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
        AccessibilityNodeInfoCompat source = record.getSource();

        utterance.addAuditory(R.raw.tick);
        utterance.addHaptic(R.array.view_clicked_pattern);

        CharSequence eventText = AccessibilityEventUtils.getEventTextOrDescription(event);
        if (!TextUtils.isEmpty(eventText)) {
            utterance.addSpoken(eventText);
        }

        // Switch and ToggleButton state is sent along with the event, so only
        // append checked / not checked state for other types of controls.
        if (AccessibilityNodeInfoUtils.nodeMatchesClassByType(source, ToggleButton.class)
                || AccessibilityNodeInfoUtils.nodeMatchesClassByType(source, Switch.class)) {
            return true;
        }

        if (source == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            if (event.isChecked()) {
                utterance.addSpoken(context.getString(R.string.value_checked));
            } else {
                utterance.addSpoken(context.getString(R.string.value_not_checked));
            }

            return true;
        }

        if (source.isCheckable()) {
            if (source.isChecked()) {
                utterance.addSpoken(context.getString(R.string.value_checked));
            } else {
                utterance.addSpoken(context.getString(R.string.value_not_checked));
            }
        }

        return true;
    }
}