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
19ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakaimport android.graphics.Canvas;
20ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakaimport android.graphics.Paint;
21ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakaimport android.text.Editable;
22ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakaimport android.text.Spannable;
23ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakaimport android.text.SpannableString;
24ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakaimport android.text.style.ReplacementSpan;
25ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
26ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakaimport junit.framework.Assert;
27ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
28ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka/**
29ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * Represents an editor state.
30ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *
31ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * The editor state can be specified by following string format.
32ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * - Components are separated by space(U+0020).
33ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * - Single-quoted string for printable ASCII characters, e.g. 'a', '123'.
34ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * - U+XXXX form can be used for a Unicode code point.
35ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * - Components inside '[' and ']' are in selection.
36ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * - Components inside '(' and ')' are in ReplacementSpan.
37ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * - '|' is for specifying cursor position.
38ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *
39ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * Selection and cursor can not be specified at the same time.
40ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *
41ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka * Example:
42ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *   - "'Hello,' | U+0020 'world!'" means "Hello, world!" is displayed and the cursor position
43ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *     is 6.
44ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *   - "'abc' [ 'def' ] 'ghi'" means "abcdefghi" is displayed and "def" is selected.
45ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *   - "U+1F441 | ( U+1F441 U+1F441 )" means three U+1F441 characters are displayed and
46ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka *     ReplacementSpan is set from offset 2 to 6.
47ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka */
48ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonakapublic class EditorState {
49ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    private static final String REPLACEMENT_SPAN_START = "(";
50ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    private static final String REPLACEMENT_SPAN_END = ")";
51ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    private static final String SELECTION_START = "[";
52ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    private static final String SELECTION_END = "]";
53ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    private static final String CURSOR = "|";
54ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
55ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    public Editable mText;
56ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    public int mSelectionStart = -1;
57ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    public int mSelectionEnd = -1;
58ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
59ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    public EditorState() {
60ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    }
61ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
62ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    /**
63ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka     * A mocked {@link android.text.style.ReplacementSpan} for testing purpose.
64ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka     */
65ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    private static class MockReplacementSpan extends ReplacementSpan {
66ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        public int getSize(Paint paint, CharSequence text, int start, int end,
67ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                Paint.FontMetricsInt fm) {
68ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            return 0;
69ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        }
70ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top,
71ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                int y, int bottom, Paint paint) {
72ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        }
73ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    }
74ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
75ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    // Returns true if the code point is ASCII and graph.
76ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    private boolean isGraphicAscii(int codePoint) {
77ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        return 0x20 < codePoint && codePoint < 0x7F;
78ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    }
79ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
80ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    // Setup editor state with string. Please see class description for string format.
81ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    public void setByString(String string) {
82ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        final StringBuilder sb = new StringBuilder();
83ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        int replacementSpanStart = -1;
84ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        int replacementSpanEnd = -1;
85ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        mSelectionStart = -1;
86ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        mSelectionEnd = -1;
87ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
88ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        final String[] tokens = string.split(" +");
89ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        for (String token : tokens) {
90ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            if (token.startsWith("'") && token.endsWith("'")) {
91ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                for (int i = 1; i < token.length() - 1; ++i) {
92ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    final char ch = token.charAt(1);
93ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    if (!isGraphicAscii(ch)) {
94ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                        throw new IllegalArgumentException(
95ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                                "Only printable characters can be in single quote. " +
96ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                                "Use U+" + Integer.toHexString(ch).toUpperCase() + " instead");
97ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    }
98ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                }
99ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                sb.append(token.substring(1, token.length() - 1));
100ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            } else if (token.startsWith("U+")) {
101ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                final int codePoint = Integer.parseInt(token.substring(2), 16);
102ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                if (codePoint < 0 || 0x10FFFF < codePoint) {
103ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    throw new IllegalArgumentException("Invalid code point is specified:" + token);
104ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                }
105ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                sb.append(Character.toChars(codePoint));
106ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            } else if (token.equals(CURSOR)) {
107ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                if (mSelectionStart != -1 || mSelectionEnd != -1) {
108ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    throw new IllegalArgumentException(
109ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                            "Two or more cursor/selection positions are specified.");
110ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                }
111ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                mSelectionStart = mSelectionEnd = sb.length();
112ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            } else if (token.equals(SELECTION_START)) {
113ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                if (mSelectionStart != -1) {
114ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    throw new IllegalArgumentException(
115ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                            "Two or more cursor/selection positions are specified.");
116ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                }
117ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                mSelectionStart = sb.length();
118ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            } else if (token.equals(SELECTION_END)) {
119ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                if (mSelectionEnd != -1) {
120ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    throw new IllegalArgumentException(
121ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                            "Two or more cursor/selection positions are specified.");
122ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                }
123ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                mSelectionEnd = sb.length();
124ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            } else if (token.equals(REPLACEMENT_SPAN_START)) {
125ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                if (replacementSpanStart != -1) {
126ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    throw new IllegalArgumentException(
127ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                            "Only one replacement span is supported");
128ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                }
129ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                replacementSpanStart = sb.length();
130ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            } else if (token.equals(REPLACEMENT_SPAN_END)) {
131ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                if (replacementSpanEnd != -1) {
132ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    throw new IllegalArgumentException(
133ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                            "Only one replacement span is supported");
134ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                }
135ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                replacementSpanEnd = sb.length();
136ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            } else {
137ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                throw new IllegalArgumentException("Unknown or invalid token: " + token);
138ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            }
139ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        }
140ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
141ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        if (mSelectionStart == -1 || mSelectionEnd == -1) {
142ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka              if (mSelectionEnd != -1) {
143ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                  throw new IllegalArgumentException(
144ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                          "Selection start position doesn't exist.");
145ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka              } else if (mSelectionStart != -1) {
146ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                  throw new IllegalArgumentException(
147ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                          "Selection end position doesn't exist.");
148ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka              } else {
149ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                  throw new IllegalArgumentException(
150ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                          "At least cursor position or selection range must be specified.");
151ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka              }
152ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        } else if (mSelectionStart > mSelectionEnd) {
153ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka              throw new IllegalArgumentException(
154ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                      "Selection start position appears after end position.");
155ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        }
156ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
157ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        final Spannable spannable = new SpannableString(sb.toString());
158ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
159ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        if (replacementSpanStart != -1 || replacementSpanEnd != -1) {
160ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            if (replacementSpanStart == -1) {
161ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                throw new IllegalArgumentException(
162ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                        "ReplacementSpan start position doesn't exist.");
163ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            }
164ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            if (replacementSpanEnd == -1) {
165ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                throw new IllegalArgumentException(
166ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                        "ReplacementSpan end position doesn't exist.");
167ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            }
168ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            if (replacementSpanStart > replacementSpanEnd) {
169ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                throw new IllegalArgumentException(
170ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                        "ReplacementSpan start position appears after end position.");
171ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            }
172ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka            spannable.setSpan(new MockReplacementSpan(), replacementSpanStart, replacementSpanEnd,
173ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
174ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        }
175ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        mText = Editable.Factory.getInstance().newEditable(spannable);
176ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    }
177ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
178ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    public void assertEquals(String string) {
179ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        EditorState expected = new EditorState();
180ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        expected.setByString(string);
181ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
182ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        Assert.assertEquals(expected.mText.toString(), mText.toString());
183ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        Assert.assertEquals(expected.mSelectionStart, mSelectionStart);
184ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka        Assert.assertEquals(expected.mSelectionEnd, mSelectionEnd);
185ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka    }
186ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka}
187ff3bfd5a79a95dcfccd1b8ad6c08c715ce6fa0e3Seigo Nonaka
188