1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.graphics.Rect;
20import android.text.Layout;
21import android.text.Spannable;
22import android.view.AccessibilityIterators.AbstractTextSegmentIterator;
23
24/**
25 * This class contains the implementation of text segment iterators
26 * for accessibility support.
27 */
28final class AccessibilityIterators {
29
30    static class LineTextSegmentIterator extends AbstractTextSegmentIterator {
31        private static LineTextSegmentIterator sLineInstance;
32
33        protected static final int DIRECTION_START = -1;
34        protected static final int DIRECTION_END = 1;
35
36        protected Layout mLayout;
37
38        public static LineTextSegmentIterator getInstance() {
39            if (sLineInstance == null) {
40                sLineInstance = new LineTextSegmentIterator();
41            }
42            return sLineInstance;
43        }
44
45        public void initialize(Spannable text, Layout layout) {
46            mText = text.toString();
47            mLayout = layout;
48        }
49
50        @Override
51        public int[] following(int offset) {
52            final int textLegth = mText.length();
53            if (textLegth <= 0) {
54                return null;
55            }
56            if (offset >= mText.length()) {
57                return null;
58            }
59            int nextLine;
60            if (offset < 0) {
61                nextLine = mLayout.getLineForOffset(0);
62            } else {
63                final int currentLine = mLayout.getLineForOffset(offset);
64                if (getLineEdgeIndex(currentLine, DIRECTION_START) == offset) {
65                    nextLine = currentLine;
66                } else {
67                    nextLine = currentLine + 1;
68                }
69            }
70            if (nextLine >= mLayout.getLineCount()) {
71                return null;
72            }
73            final int start = getLineEdgeIndex(nextLine, DIRECTION_START);
74            final int end = getLineEdgeIndex(nextLine, DIRECTION_END) + 1;
75            return getRange(start, end);
76        }
77
78        @Override
79        public int[] preceding(int offset) {
80            final int textLegth = mText.length();
81            if (textLegth <= 0) {
82                return null;
83            }
84            if (offset <= 0) {
85                return null;
86            }
87            int previousLine;
88            if (offset > mText.length()) {
89                previousLine = mLayout.getLineForOffset(mText.length());
90            } else {
91                final int currentLine = mLayout.getLineForOffset(offset);
92                if (getLineEdgeIndex(currentLine, DIRECTION_END) + 1 == offset) {
93                    previousLine = currentLine;
94                } else {
95                    previousLine = currentLine - 1;
96                }
97            }
98            if (previousLine < 0) {
99                return null;
100            }
101            final int start = getLineEdgeIndex(previousLine, DIRECTION_START);
102            final int end = getLineEdgeIndex(previousLine, DIRECTION_END) + 1;
103            return getRange(start, end);
104        }
105
106        protected int getLineEdgeIndex(int lineNumber, int direction) {
107            final int paragraphDirection = mLayout.getParagraphDirection(lineNumber);
108            if (direction * paragraphDirection < 0) {
109                return mLayout.getLineStart(lineNumber);
110            } else {
111                return mLayout.getLineEnd(lineNumber) - 1;
112            }
113        }
114    }
115
116    static class PageTextSegmentIterator extends LineTextSegmentIterator {
117        private static PageTextSegmentIterator sPageInstance;
118
119        private TextView mView;
120
121        private final Rect mTempRect = new Rect();
122
123        public static PageTextSegmentIterator getInstance() {
124            if (sPageInstance == null) {
125                sPageInstance = new PageTextSegmentIterator();
126            }
127            return sPageInstance;
128        }
129
130        public void initialize(TextView view) {
131            super.initialize((Spannable) view.getIterableTextForAccessibility(), view.getLayout());
132            mView = view;
133        }
134
135        @Override
136        public int[] following(int offset) {
137            final int textLength = mText.length();
138            if (textLength <= 0) {
139                return null;
140            }
141            if (offset >= mText.length()) {
142                return null;
143            }
144            if (!mView.getGlobalVisibleRect(mTempRect)) {
145                return null;
146            }
147
148            final int start = Math.max(0, offset);
149
150            final int currentLine = mLayout.getLineForOffset(start);
151            final int currentLineTop = mLayout.getLineTop(currentLine);
152            final int pageHeight = mTempRect.height() - mView.getTotalPaddingTop()
153                    - mView.getTotalPaddingBottom();
154            final int nextPageStartY = currentLineTop + pageHeight;
155            final int lastLineTop = mLayout.getLineTop(mLayout.getLineCount() - 1);
156            final int currentPageEndLine = (nextPageStartY < lastLineTop)
157                    ? mLayout.getLineForVertical(nextPageStartY) - 1 : mLayout.getLineCount() - 1;
158
159            final int end = getLineEdgeIndex(currentPageEndLine, DIRECTION_END) + 1;
160
161            return getRange(start, end);
162        }
163
164        @Override
165        public int[] preceding(int offset) {
166            final int textLength = mText.length();
167            if (textLength <= 0) {
168                return null;
169            }
170            if (offset <= 0) {
171                return null;
172            }
173            if (!mView.getGlobalVisibleRect(mTempRect)) {
174                return null;
175            }
176
177            final int end = Math.min(mText.length(), offset);
178
179            final int currentLine = mLayout.getLineForOffset(end);
180            final int currentLineTop = mLayout.getLineTop(currentLine);
181            final int pageHeight = mTempRect.height() - mView.getTotalPaddingTop()
182                    - mView.getTotalPaddingBottom();
183            final int previousPageEndY = currentLineTop - pageHeight;
184            int currentPageStartLine = (previousPageEndY > 0) ?
185                     mLayout.getLineForVertical(previousPageEndY) : 0;
186            // If we're at the end of text, we're at the end of the current line rather than the
187            // start of the next line, so we should move up one fewer lines than we would otherwise.
188            if (end == mText.length() && (currentPageStartLine < currentLine)) {
189                currentPageStartLine += 1;
190            }
191
192            final int start = getLineEdgeIndex(currentPageStartLine, DIRECTION_START);
193
194            return getRange(start, end);
195        }
196    }
197}
198