Home | History | Annotate | Download | only in latin
      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 
     17 package com.android.inputmethod.latin;
     18 
     19 import com.android.inputmethod.latin.utils.TextRange;
     20 
     21 import android.inputmethodservice.InputMethodService;
     22 import android.os.Parcel;
     23 import android.test.AndroidTestCase;
     24 import android.test.MoreAsserts;
     25 import android.test.suitebuilder.annotation.SmallTest;
     26 import android.text.SpannableString;
     27 import android.text.Spanned;
     28 import android.text.TextUtils;
     29 import android.text.style.SuggestionSpan;
     30 import android.view.inputmethod.ExtractedText;
     31 import android.view.inputmethod.ExtractedTextRequest;
     32 import android.view.inputmethod.InputConnection;
     33 import android.view.inputmethod.InputConnectionWrapper;
     34 
     35 import java.util.Locale;
     36 
     37 @SmallTest
     38 public class RichInputConnectionAndTextRangeTests extends AndroidTestCase {
     39 
     40     // The following is meant to be a reasonable default for
     41     // the "word_separators" resource.
     42     private static final String sSeparators = ".,:;!?-";
     43 
     44     @Override
     45     protected void setUp() throws Exception {
     46         super.setUp();
     47     }
     48 
     49     private class MockConnection extends InputConnectionWrapper {
     50         final CharSequence mTextBefore;
     51         final CharSequence mTextAfter;
     52         final ExtractedText mExtractedText;
     53 
     54         public MockConnection(final CharSequence text, final int cursorPosition) {
     55             super(null, false);
     56             // Interaction of spans with Parcels is completely non-trivial, but in the actual case
     57             // the CharSequences do go through Parcels because they go through IPC. There
     58             // are some significant differences between the behavior of Spanned objects that
     59             // have and that have not gone through parceling, so it's much easier to simulate
     60             // the environment with Parcels than try to emulate things by hand.
     61             final Parcel p = Parcel.obtain();
     62             TextUtils.writeToParcel(text.subSequence(0, cursorPosition), p, 0 /* flags */);
     63             TextUtils.writeToParcel(text.subSequence(cursorPosition, text.length()), p,
     64                     0 /* flags */);
     65             final byte[] marshalled = p.marshall();
     66             p.unmarshall(marshalled, 0, marshalled.length);
     67             p.setDataPosition(0);
     68             mTextBefore = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p);
     69             mTextAfter = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p);
     70             mExtractedText = null;
     71             p.recycle();
     72         }
     73 
     74         public MockConnection(String textBefore, String textAfter, ExtractedText extractedText) {
     75             super(null, false);
     76             mTextBefore = textBefore;
     77             mTextAfter = textAfter;
     78             mExtractedText = extractedText;
     79         }
     80 
     81         /* (non-Javadoc)
     82          * @see android.view.inputmethod.InputConnectionWrapper#getTextBeforeCursor(int, int)
     83          */
     84         @Override
     85         public CharSequence getTextBeforeCursor(int n, int flags) {
     86             return mTextBefore;
     87         }
     88 
     89         /* (non-Javadoc)
     90          * @see android.view.inputmethod.InputConnectionWrapper#getTextAfterCursor(int, int)
     91          */
     92         @Override
     93         public CharSequence getTextAfterCursor(int n, int flags) {
     94             return mTextAfter;
     95         }
     96 
     97         /* (non-Javadoc)
     98          * @see android.view.inputmethod.InputConnectionWrapper#getExtractedText(
     99          *         ExtractedTextRequest, int)
    100          */
    101         @Override
    102         public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
    103             return mExtractedText;
    104         }
    105 
    106         @Override
    107         public boolean beginBatchEdit() {
    108             return true;
    109         }
    110 
    111         @Override
    112         public boolean endBatchEdit() {
    113             return true;
    114         }
    115 
    116         @Override
    117         public boolean finishComposingText() {
    118             return true;
    119         }
    120     }
    121 
    122     private class MockInputMethodService extends InputMethodService {
    123         InputConnection mInputConnection;
    124         public void setInputConnection(final InputConnection inputConnection) {
    125             mInputConnection = inputConnection;
    126         }
    127         @Override
    128         public InputConnection getCurrentInputConnection() {
    129             return mInputConnection;
    130         }
    131     }
    132 
    133     /************************** Tests ************************/
    134 
    135     /**
    136      * Test for getting previous word (for bigram suggestions)
    137      */
    138     public void testGetPreviousWord() {
    139         // If one of the following cases breaks, the bigram suggestions won't work.
    140         assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSeparators, 2), "abc");
    141         assertNull(RichInputConnection.getNthPreviousWord("abc", sSeparators, 2));
    142         assertNull(RichInputConnection.getNthPreviousWord("abc. def", sSeparators, 2));
    143 
    144         // The following tests reflect the current behavior of the function
    145         // RichInputConnection#getNthPreviousWord.
    146         // TODO: However at this time, the code does never go
    147         // into such a path, so it should be safe to change the behavior of
    148         // this function if needed - especially since it does not seem very
    149         // logical. These tests are just there to catch any unintentional
    150         // changes in the behavior of the RichInputConnection#getPreviousWord method.
    151         assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSeparators, 2), "abc");
    152         assertEquals(RichInputConnection.getNthPreviousWord("abc def.", sSeparators, 2), "abc");
    153         assertEquals(RichInputConnection.getNthPreviousWord("abc def .", sSeparators, 2), "def");
    154         assertNull(RichInputConnection.getNthPreviousWord("abc ", sSeparators, 2));
    155 
    156         assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSeparators, 1), "def");
    157         assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSeparators, 1), "def");
    158         assertNull(RichInputConnection.getNthPreviousWord("abc def.", sSeparators, 1));
    159         assertNull(RichInputConnection.getNthPreviousWord("abc def .", sSeparators, 1));
    160     }
    161 
    162     /**
    163      * Test logic in getting the word range at the cursor.
    164      */
    165     public void testGetWordRangeAtCursor() {
    166         ExtractedText et = new ExtractedText();
    167         final MockInputMethodService mockInputMethodService = new MockInputMethodService();
    168         final RichInputConnection ic = new RichInputConnection(mockInputMethodService);
    169         mockInputMethodService.setInputConnection(new MockConnection("word wo", "rd", et));
    170         et.startOffset = 0;
    171         et.selectionStart = 7;
    172         TextRange r;
    173 
    174         ic.beginBatchEdit();
    175         // basic case
    176         r = ic.getWordRangeAtCursor(" ", 0);
    177         assertTrue(TextUtils.equals("word", r.mWord));
    178 
    179         // more than one word
    180         r = ic.getWordRangeAtCursor(" ", 1);
    181         assertTrue(TextUtils.equals("word word", r.mWord));
    182         ic.endBatchEdit();
    183 
    184         // tab character instead of space
    185         mockInputMethodService.setInputConnection(new MockConnection("one\tword\two", "rd", et));
    186         ic.beginBatchEdit();
    187         r = ic.getWordRangeAtCursor("\t", 1);
    188         ic.endBatchEdit();
    189         assertTrue(TextUtils.equals("word\tword", r.mWord));
    190 
    191         // only one word doesn't go too far
    192         mockInputMethodService.setInputConnection(new MockConnection("one\tword\two", "rd", et));
    193         ic.beginBatchEdit();
    194         r = ic.getWordRangeAtCursor("\t", 1);
    195         ic.endBatchEdit();
    196         assertTrue(TextUtils.equals("word\tword", r.mWord));
    197 
    198         // tab or space
    199         mockInputMethodService.setInputConnection(new MockConnection("one word\two", "rd", et));
    200         ic.beginBatchEdit();
    201         r = ic.getWordRangeAtCursor(" \t", 1);
    202         ic.endBatchEdit();
    203         assertTrue(TextUtils.equals("word\tword", r.mWord));
    204 
    205         // tab or space multiword
    206         mockInputMethodService.setInputConnection(new MockConnection("one word\two", "rd", et));
    207         ic.beginBatchEdit();
    208         r = ic.getWordRangeAtCursor(" \t", 2);
    209         ic.endBatchEdit();
    210         assertTrue(TextUtils.equals("one word\tword", r.mWord));
    211 
    212         // splitting on supplementary character
    213         final String supplementaryChar = "\uD840\uDC8A";
    214         mockInputMethodService.setInputConnection(
    215                 new MockConnection("one word" + supplementaryChar + "wo", "rd", et));
    216         ic.beginBatchEdit();
    217         r = ic.getWordRangeAtCursor(supplementaryChar, 0);
    218         ic.endBatchEdit();
    219         assertTrue(TextUtils.equals("word", r.mWord));
    220     }
    221 
    222     /**
    223      * Test logic in getting the word range at the cursor.
    224      */
    225     public void testGetSuggestionSpansAtWord() {
    226         helpTestGetSuggestionSpansAtWord(10);
    227         helpTestGetSuggestionSpansAtWord(12);
    228         helpTestGetSuggestionSpansAtWord(15);
    229         helpTestGetSuggestionSpansAtWord(16);
    230     }
    231 
    232     private void helpTestGetSuggestionSpansAtWord(final int cursorPos) {
    233         final MockInputMethodService mockInputMethodService = new MockInputMethodService();
    234         final RichInputConnection ic = new RichInputConnection(mockInputMethodService);
    235 
    236         final String[] SUGGESTIONS1 = { "swing", "strong" };
    237         final String[] SUGGESTIONS2 = { "storing", "strung" };
    238 
    239         // Test the usual case.
    240         SpannableString text = new SpannableString("This is a string for test");
    241         text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
    242                 10 /* start */, 16 /* end */, 0 /* flags */);
    243         mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
    244         TextRange r;
    245         SuggestionSpan[] suggestions;
    246 
    247         r = ic.getWordRangeAtCursor(" ", 0);
    248         suggestions = r.getSuggestionSpansAtWord();
    249         assertEquals(suggestions.length, 1);
    250         MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
    251 
    252         // Test the case with 2 suggestion spans in the same place.
    253         text = new SpannableString("This is a string for test");
    254         text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
    255                 10 /* start */, 16 /* end */, 0 /* flags */);
    256         text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
    257                 10 /* start */, 16 /* end */, 0 /* flags */);
    258         mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
    259         r = ic.getWordRangeAtCursor(" ", 0);
    260         suggestions = r.getSuggestionSpansAtWord();
    261         assertEquals(suggestions.length, 2);
    262         MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
    263         MoreAsserts.assertEquals(suggestions[1].getSuggestions(), SUGGESTIONS2);
    264 
    265         // Test a case with overlapping spans, 2nd extending past the start of the word
    266         text = new SpannableString("This is a string for test");
    267         text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
    268                 10 /* start */, 16 /* end */, 0 /* flags */);
    269         text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
    270                 5 /* start */, 16 /* end */, 0 /* flags */);
    271         mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
    272         r = ic.getWordRangeAtCursor(" ", 0);
    273         suggestions = r.getSuggestionSpansAtWord();
    274         assertEquals(suggestions.length, 1);
    275         MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
    276 
    277         // Test a case with overlapping spans, 2nd extending past the end of the word
    278         text = new SpannableString("This is a string for test");
    279         text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
    280                 10 /* start */, 16 /* end */, 0 /* flags */);
    281         text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
    282                 10 /* start */, 20 /* end */, 0 /* flags */);
    283         mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
    284         r = ic.getWordRangeAtCursor(" ", 0);
    285         suggestions = r.getSuggestionSpansAtWord();
    286         assertEquals(suggestions.length, 1);
    287         MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
    288 
    289         // Test a case with overlapping spans, 2nd extending past both ends of the word
    290         text = new SpannableString("This is a string for test");
    291         text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
    292                 10 /* start */, 16 /* end */, 0 /* flags */);
    293         text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
    294                 5 /* start */, 20 /* end */, 0 /* flags */);
    295         mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
    296         r = ic.getWordRangeAtCursor(" ", 0);
    297         suggestions = r.getSuggestionSpansAtWord();
    298         assertEquals(suggestions.length, 1);
    299         MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
    300 
    301         // Test a case with overlapping spans, none right on the word
    302         text = new SpannableString("This is a string for test");
    303         text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
    304                 5 /* start */, 16 /* end */, 0 /* flags */);
    305         text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
    306                 5 /* start */, 20 /* end */, 0 /* flags */);
    307         mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
    308         r = ic.getWordRangeAtCursor(" ", 0);
    309         suggestions = r.getSuggestionSpansAtWord();
    310         assertEquals(suggestions.length, 0);
    311     }
    312 }
    313