1ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka/*
2ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * Copyright (C) 2016 The Android Open Source Project
3ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *
4ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * Licensed under the Apache License, Version 2.0 (the "License");
5ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * you may not use this file except in compliance with the License.
6ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * You may obtain a copy of the License at
7ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *
8ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *      http://www.apache.org/licenses/LICENSE-2.0
9ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *
10ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * Unless required by applicable law or agreed to in writing, software
11ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * distributed under the License is distributed on an "AS IS" BASIS,
12ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * See the License for the specific language governing permissions and
14ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * limitations under the License.
15ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka */
16ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
17ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakapackage android.text.method;
18ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
19bbe51f8290af05b4071c7ed44fcbaa98e2f85e13Andrei Stingaceanuimport static org.mockito.Matchers.any;
20bbe51f8290af05b4071c7ed44fcbaa98e2f85e13Andrei Stingaceanuimport static org.mockito.Mockito.doNothing;
21bbe51f8290af05b4071c7ed44fcbaa98e2f85e13Andrei Stingaceanuimport static org.mockito.Mockito.mock;
22bbe51f8290af05b4071c7ed44fcbaa98e2f85e13Andrei Stingaceanuimport static org.mockito.Mockito.when;
23bbe51f8290af05b4071c7ed44fcbaa98e2f85e13Andrei Stingaceanu
24ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakaimport android.graphics.Canvas;
25ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakaimport android.graphics.Paint;
26ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakaimport android.text.Editable;
27ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakaimport android.text.Spannable;
28ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakaimport android.text.SpannableString;
29ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakaimport android.text.style.ReplacementSpan;
30ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
31ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakaimport junit.framework.Assert;
32ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
33ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka/**
34ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * Represents an editor state.
35ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *
36ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * The editor state can be specified by following string format.
37ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * - Components are separated by space(U+0020).
38ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * - Single-quoted string for printable ASCII characters, e.g. 'a', '123'.
39ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * - U+XXXX form can be used for a Unicode code point.
40ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * - Components inside '[' and ']' are in selection.
41ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * - Components inside '(' and ')' are in ReplacementSpan.
42ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * - '|' is for specifying cursor position.
43ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *
44ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * Selection and cursor can not be specified at the same time.
45ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *
46ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * Example:
47ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *   - "'Hello,' | U+0020 'world!'" means "Hello, world!" is displayed and the cursor position
48ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *     is 6.
49ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *   - "'abc' [ 'def' ] 'ghi'" means "abcdefghi" is displayed and "def" is selected.
50ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *   - "U+1F441 | ( U+1F441 U+1F441 )" means three U+1F441 characters are displayed and
51ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *     ReplacementSpan is set from offset 2 to 6.
52ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka */
53ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakapublic class EditorState {
54ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    private static final String REPLACEMENT_SPAN_START = "(";
55ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    private static final String REPLACEMENT_SPAN_END = ")";
56ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    private static final String SELECTION_START = "[";
57ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    private static final String SELECTION_END = "]";
58ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    private static final String CURSOR = "|";
59ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
60ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    public Editable mText;
61ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    public int mSelectionStart = -1;
62ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    public int mSelectionEnd = -1;
63ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
64ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    public EditorState() {
65ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    }
66ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
67ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    // Returns true if the code point is ASCII and graph.
68ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    private boolean isGraphicAscii(int codePoint) {
69ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        return 0x20 < codePoint && codePoint < 0x7F;
70ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    }
71ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
72ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    // Setup editor state with string. Please see class description for string format.
73ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    public void setByString(String string) {
74ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        final StringBuilder sb = new StringBuilder();
75ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        int replacementSpanStart = -1;
76ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        int replacementSpanEnd = -1;
77ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        mSelectionStart = -1;
78ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        mSelectionEnd = -1;
79ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
80ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        final String[] tokens = string.split(" +");
81ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        for (String token : tokens) {
82ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            if (token.startsWith("'") && token.endsWith("'")) {
83ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                for (int i = 1; i < token.length() - 1; ++i) {
84ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    final char ch = token.charAt(1);
85ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    if (!isGraphicAscii(ch)) {
86ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                        throw new IllegalArgumentException(
87ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                                "Only printable characters can be in single quote. " +
88ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                                "Use U+" + Integer.toHexString(ch).toUpperCase() + " instead");
89ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    }
90ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                }
91ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                sb.append(token.substring(1, token.length() - 1));
92ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            } else if (token.startsWith("U+")) {
93ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                final int codePoint = Integer.parseInt(token.substring(2), 16);
94ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                if (codePoint < 0 || 0x10FFFF < codePoint) {
95ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    throw new IllegalArgumentException("Invalid code point is specified:" + token);
96ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                }
97ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                sb.append(Character.toChars(codePoint));
98ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            } else if (token.equals(CURSOR)) {
99ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                if (mSelectionStart != -1 || mSelectionEnd != -1) {
100ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    throw new IllegalArgumentException(
101ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                            "Two or more cursor/selection positions are specified.");
102ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                }
103ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                mSelectionStart = mSelectionEnd = sb.length();
104ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            } else if (token.equals(SELECTION_START)) {
105ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                if (mSelectionStart != -1) {
106ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    throw new IllegalArgumentException(
107ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                            "Two or more cursor/selection positions are specified.");
108ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                }
109ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                mSelectionStart = sb.length();
110ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            } else if (token.equals(SELECTION_END)) {
111ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                if (mSelectionEnd != -1) {
112ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    throw new IllegalArgumentException(
113ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                            "Two or more cursor/selection positions are specified.");
114ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                }
115ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                mSelectionEnd = sb.length();
116ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            } else if (token.equals(REPLACEMENT_SPAN_START)) {
117ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                if (replacementSpanStart != -1) {
118ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    throw new IllegalArgumentException(
119ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                            "Only one replacement span is supported");
120ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                }
121ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                replacementSpanStart = sb.length();
122ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            } else if (token.equals(REPLACEMENT_SPAN_END)) {
123ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                if (replacementSpanEnd != -1) {
124ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    throw new IllegalArgumentException(
125ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                            "Only one replacement span is supported");
126ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                }
127ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                replacementSpanEnd = sb.length();
128ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            } else {
129ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                throw new IllegalArgumentException("Unknown or invalid token: " + token);
130ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            }
131ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        }
132ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
133ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        if (mSelectionStart == -1 || mSelectionEnd == -1) {
134ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka              if (mSelectionEnd != -1) {
135ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                  throw new IllegalArgumentException(
136ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                          "Selection start position doesn't exist.");
137ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka              } else if (mSelectionStart != -1) {
138ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                  throw new IllegalArgumentException(
139ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                          "Selection end position doesn't exist.");
140ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka              } else {
141ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                  throw new IllegalArgumentException(
142ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                          "At least cursor position or selection range must be specified.");
143ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka              }
144ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        } else if (mSelectionStart > mSelectionEnd) {
145ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka              throw new IllegalArgumentException(
146ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                      "Selection start position appears after end position.");
147ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        }
148ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
149ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        final Spannable spannable = new SpannableString(sb.toString());
150ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
151ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        if (replacementSpanStart != -1 || replacementSpanEnd != -1) {
152ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            if (replacementSpanStart == -1) {
153ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                throw new IllegalArgumentException(
154ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                        "ReplacementSpan start position doesn't exist.");
155ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            }
156ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            if (replacementSpanEnd == -1) {
157ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                throw new IllegalArgumentException(
158ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                        "ReplacementSpan end position doesn't exist.");
159ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            }
160ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            if (replacementSpanStart > replacementSpanEnd) {
161ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                throw new IllegalArgumentException(
162ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                        "ReplacementSpan start position appears after end position.");
163ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            }
164bbe51f8290af05b4071c7ed44fcbaa98e2f85e13Andrei Stingaceanu
165bbe51f8290af05b4071c7ed44fcbaa98e2f85e13Andrei Stingaceanu            ReplacementSpan mockReplacementSpan = mock(ReplacementSpan.class);
166bbe51f8290af05b4071c7ed44fcbaa98e2f85e13Andrei Stingaceanu            when(mockReplacementSpan.getSize(any(), any(), any(), any(), any()))
167bbe51f8290af05b4071c7ed44fcbaa98e2f85e13Andrei Stingaceanu                .thenReturn(0);
168bbe51f8290af05b4071c7ed44fcbaa98e2f85e13Andrei Stingaceanu            doNothing().when(mockReplacementSpan)
169bbe51f8290af05b4071c7ed44fcbaa98e2f85e13Andrei Stingaceanu                .draw(any(), any(), any(), any(), any(), any(), any(), any(), any());
170bbe51f8290af05b4071c7ed44fcbaa98e2f85e13Andrei Stingaceanu
171bbe51f8290af05b4071c7ed44fcbaa98e2f85e13Andrei Stingaceanu            spannable.setSpan(mockReplacementSpan, replacementSpanStart, replacementSpanEnd,
172ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
173ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        }
174ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        mText = Editable.Factory.getInstance().newEditable(spannable);
175ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    }
176ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
177ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    public void assertEquals(String string) {
178ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        EditorState expected = new EditorState();
179ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        expected.setByString(string);
180ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
181ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        Assert.assertEquals(expected.mText.toString(), mText.toString());
182ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        Assert.assertEquals(expected.mSelectionStart, mSelectionStart);
183ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        Assert.assertEquals(expected.mSelectionEnd, mSelectionEnd);
184ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    }
185ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka}
186ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
187