111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette/*
211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * Copyright (C) 2013 The Android Open Source Project
311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette *
411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * Licensed under the Apache License, Version 2.0 (the "License");
511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * you may not use this file except in compliance with the License.
611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * You may obtain a copy of the License at
711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette *
811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette *      http://www.apache.org/licenses/LICENSE-2.0
911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette *
1011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * Unless required by applicable law or agreed to in writing, software
1111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * distributed under the License is distributed on an "AS IS" BASIS,
1211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * See the License for the specific language governing permissions and
1411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * limitations under the License.
1511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette */
1611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
1711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverettepackage com.example.android.supportv4.widget;
1811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
1911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.annotation.TargetApi;
2011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.app.Activity;
2111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.content.Context;
2211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.graphics.Canvas;
2311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.graphics.Color;
2411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.graphics.Paint;
2511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.graphics.Paint.Align;
2611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.graphics.Paint.Style;
2711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.graphics.Rect;
2811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.graphics.RectF;
2911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.os.Build;
3011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.os.Bundle;
3111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.support.v4.view.ViewCompat;
3211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
3311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
3411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.support.v4.widget.ExploreByTouchHelper;
3511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.util.AttributeSet;
3611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.view.MotionEvent;
3711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.view.View;
3811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport android.view.accessibility.AccessibilityEvent;
3911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport com.example.android.supportv4.R;
4011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
4111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport java.util.ArrayList;
4211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viveretteimport java.util.List;
4311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
4411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette/**
4511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * This example shows how to use the {@link ExploreByTouchHelper} class in the
4611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * Android support library to add accessibility support to a custom view that
4711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * represents multiple logical items.
4811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <p>
4911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * The {@link ExploreByTouchHelper} class wraps
5011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * {@link AccessibilityNodeProviderCompat} and simplifies exposing information
5111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * about a custom view's logical structure to accessibility services.
5211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <p>
5311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * The custom view in this example is responsible for:
5411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <ul>
5511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <li>Creating a helper class that extends {@link ExploreByTouchHelper}
5611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <li>Setting the helper as the accessibility delegate using
5711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * {@link ViewCompat#setAccessibilityDelegate}
5811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <li>Dispatching hover events to the helper in {@link View#dispatchHoverEvent}
5911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * </ul>
6011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <p>
6111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * The helper class implementation in this example is responsible for:
6211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <ul>
6311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <li>Mapping hover event coordinates to logical items
6411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <li>Exposing information about logical items to accessibility services
6511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <li>Handling accessibility actions
6611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette * <ul>
6711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette */
6811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverettepublic class ExploreByTouchHelperActivity extends Activity {
6911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette    @Override
7011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette    protected void onCreate(Bundle savedInstanceState) {
7111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        super.onCreate(savedInstanceState);
7211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
7311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        setContentView(R.layout.explore_by_touch_helper);
7411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
7511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        final CustomView customView = (CustomView) findViewById(R.id.custom_view);
7611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
7711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        // Adds an item at the top-left quarter of the custom view.
7811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        customView.addItem(getString(R.string.sample_item_a), 0, 0, 0.5f, 0.5f);
7911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
8011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        // Adds an item at the bottom-right quarter of the custom view.
8111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        customView.addItem(getString(R.string.sample_item_b), 0.5f, 0.5f, 1, 1);
8211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette    }
8311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
8411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette    /**
8511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette     * Simple custom view that draws rectangular items to the screen. Each item
8611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette     * has a checked state that may be toggled by tapping on the item.
8711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette     */
8811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette    public static class CustomView extends View {
8911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        private static final int NO_ITEM = -1;
9011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
9111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        private final Paint mPaint = new Paint();
9211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        private final Rect mTempBounds = new Rect();
9311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        private final List<CustomItem> mItems = new ArrayList<CustomItem>();
9411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        private CustomViewTouchHelper mTouchHelper;
9511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
9611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        public CustomView(Context context, AttributeSet attrs) {
9711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            super(context, attrs);
9811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
9911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            // Set up accessibility helper class.
10011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            mTouchHelper = new CustomViewTouchHelper(this);
10111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
10211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        }
10311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
10411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
10511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        @Override
10611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        public boolean dispatchHoverEvent(MotionEvent event) {
10711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            // Always attempt to dispatch hover events to accessibility first.
10811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            if (mTouchHelper.dispatchHoverEvent(event)) {
10911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                return true;
11011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            }
11111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
11211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            return super.dispatchHoverEvent(event);
11311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        }
11411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
11511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        @Override
11611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        public boolean onTouchEvent(MotionEvent event) {
11711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            switch (event.getAction()) {
11811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                case MotionEvent.ACTION_DOWN:
11911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                    return true;
12011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                case MotionEvent.ACTION_UP:
12111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                    final int itemIndex = getItemIndexUnder(event.getX(), event.getY());
12211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                    if (itemIndex >= 0) {
12311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                        onItemClicked(itemIndex);
12411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                    }
12511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                    return true;
12611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            }
12711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
12811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            return super.onTouchEvent(event);
12911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        }
13011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
13111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        /**
13211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette         * Adds an item to the custom view. The item is positioned relative to
13311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette         * the custom view bounds and its descriptions is drawn at its center.
13411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette         *
13511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette         * @param description The item's description.
13611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette         * @param top Top coordinate as a fraction of the parent height, range
13711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette         *            is [0,1].
13811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette         * @param left Left coordinate as a fraction of the parent width, range
13911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette         *            is [0,1].
14011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette         * @param bottom Bottom coordinate as a fraction of the parent height,
14111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette         *            range is [0,1].
14211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette         * @param right Right coordinate as a fraction of the parent width,
14311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette         *            range is [0,1].
14411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette         */
14511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        public void addItem(String description, float top, float left, float bottom, float right) {
14611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            final CustomItem item = new CustomItem();
14711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            item.bounds = new RectF(top, left, bottom, right);
14811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            item.description = description;
14911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            item.checked = false;
15011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            mItems.add(item);
15111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        }
15211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
15311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        @Override
15411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        protected void onDraw(Canvas canvas) {
15511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            super.onDraw(canvas);
15611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
15711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            final Paint paint = mPaint;
15811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            final Rect bounds = mTempBounds;
15911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            final int height = getHeight();
16011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            final int width = getWidth();
16111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
16211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            for (CustomItem item : mItems) {
16311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                paint.setColor(item.checked ? Color.RED : Color.BLUE);
16411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                paint.setStyle(Style.FILL);
16511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                scaleRectF(item.bounds, bounds, width, height);
16611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                canvas.drawRect(bounds, paint);
16711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                paint.setColor(Color.WHITE);
16811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                paint.setTextAlign(Align.CENTER);
16911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                canvas.drawText(item.description, bounds.centerX(), bounds.centerY(), paint);
17011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            }
17111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        }
17211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
17311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        protected boolean onItemClicked(int index) {
17411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            final CustomItem item = getItem(index);
17511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            if (item == null) {
17611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                return false;
17711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            }
17811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
17911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            item.checked = !item.checked;
18011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            invalidate();
18111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
18211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            // Since the item's checked state is exposed to accessibility
18311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            // services through its AccessibilityNodeInfo, we need to invalidate
18411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            // the item's virtual view. At some point in the future, the
18511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            // framework will obtain an updated version of the virtual view.
18611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            mTouchHelper.invalidateVirtualView(index);
18711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
18811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            // We also need to let the framework know what type of event
18911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            // happened. Accessibility services may use this event to provide
19011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            // appropriate feedback to the user.
19111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            mTouchHelper.sendEventForVirtualView(index, AccessibilityEvent.TYPE_VIEW_CLICKED);
19211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
19311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            return true;
19411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        }
19511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
19611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        protected int getItemIndexUnder(float x, float y) {
19711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            final float scaledX = (x / getWidth());
19811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            final float scaledY = (y / getHeight());
19911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            final int n = mItems.size();
20011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
20111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            for (int i = 0; i < n; i++) {
20211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                final CustomItem item = mItems.get(i);
20311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                if (item.bounds.contains(scaledX, scaledY)) {
20411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                    return i;
20511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                }
20611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            }
20711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
20811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            return NO_ITEM;
20911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        }
21011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
21111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        protected CustomItem getItem(int index) {
21211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            if ((index < 0) || (index >= mItems.size())) {
21311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                return null;
21411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            }
21511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
21611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            return mItems.get(index);
21711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        }
21811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
21911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        protected static void scaleRectF(RectF in, Rect out, int width, int height) {
22011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            out.top = (int) (in.top * height);
22111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            out.bottom = (int) (in.bottom * height);
22211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            out.left = (int) (in.left * width);
22311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            out.right = (int) (in.right * width);
22411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        }
22511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
22611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        private class CustomViewTouchHelper extends ExploreByTouchHelper {
22711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            private final Rect mTempRect = new Rect();
22811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
22911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            public CustomViewTouchHelper(View forView) {
23011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                super(forView);
23111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            }
23211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
23311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            @Override
23411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            protected int getVirtualViewAt(float x, float y) {
23511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // We also perform hit detection in onTouchEvent(), and we can
23611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // reuse that logic here. This will ensure consistency whether
23711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // accessibility is on or off.
23811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                final int index = getItemIndexUnder(x, y);
23911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                if (index == NO_ITEM) {
24011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                    return ExploreByTouchHelper.INVALID_ID;
24111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                }
24211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
24311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                return index;
24411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            }
24511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
24611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            @Override
24711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
24811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // Since every item should be visible, and since we're mapping
24911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // directly from item index to virtual view id, we can just add
25011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // every available index in the item list.
25111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                final int n = mItems.size();
25211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                for (int i = 0; i < n; i++) {
25311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                    virtualViewIds.add(i);
25411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                }
25511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            }
25611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
25711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            @Override
25811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            protected void onPopulateEventForVirtualView(
25911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                    int virtualViewId, AccessibilityEvent event) {
26011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                final CustomItem item = getItem(virtualViewId);
26111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                if (item == null) {
26211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                    throw new IllegalArgumentException("Invalid virtual view id");
26311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                }
26411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
26511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // The event must be populated with text, either using
26611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // getText().add() or setContentDescription(). Since the item's
26711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // description is displayed visually, we'll add it to the event
26811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // text. If it was only used for accessibility, we would use
26911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // setContentDescription().
27011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                event.getText().add(item.description);
27111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            }
27211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
27311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            @Override
27411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            protected void onPopulateNodeForVirtualView(
27511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                    int virtualViewId, AccessibilityNodeInfoCompat node) {
27611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                final CustomItem item = getItem(virtualViewId);
27711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                if (item == null) {
27811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                    throw new IllegalArgumentException("Invalid virtual view id");
27911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                }
28011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
28111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // Node and event text and content descriptions are usually
28211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // identical, so we'll use the exact same string as before.
28311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                node.setText(item.description);
28411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
28511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // Reported bounds should be consistent with those used to draw
28611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // the item in onDraw(). They should also be consistent with the
28711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // hit detection performed in getVirtualViewAt() and
28811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // onTouchEvent().
28911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                final Rect bounds = mTempRect;
29011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                final int height = getHeight();
29111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                final int width = getWidth();
29211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                scaleRectF(item.bounds, bounds, width, height);
29311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                node.setBoundsInParent(bounds);
29411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
29511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // Since the user can tap an item, add the CLICK action. We'll
29611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // need to handle this later in onPerformActionForVirtualView.
29711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
29811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
29911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                // This item has a checked state.
30011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                node.setCheckable(true);
30111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                node.setChecked(item.checked);
30211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            }
30311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
30411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            @Override
30511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            protected boolean onPerformActionForVirtualView(
30611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                    int virtualViewId, int action, Bundle arguments) {
30711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                switch (action) {
30811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                    case AccessibilityNodeInfoCompat.ACTION_CLICK:
30911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                        // Click handling should be consistent with
31011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                        // onTouchEvent(). This ensures that the view works the
31111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                        // same whether accessibility is turned on or off.
31211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                        return onItemClicked(virtualViewId);
31311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                }
31411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
31511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette                return false;
31611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            }
31711d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
31811d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        }
31911d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette
32011d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        public static class CustomItem {
32111d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            private String description;
32211d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            private RectF bounds;
32311d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette            private boolean checked;
32411d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette        }
32511d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette    }
32611d03f96a3f29508c703b3df60a634eeaba5d37dAlan Viverette}
327