1 // Copyright 2017 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.omnibox; 6 7 import android.text.TextUtils; 8 9 import androidx.annotation.VisibleForTesting; 10 11 import java.util.Locale; 12 13 /** 14 * A state to keep track of EditText and autocomplete. 15 */ 16 class AutocompleteState { 17 private String mUserText; 18 private String mAutocompleteText; 19 private int mSelStart; 20 private int mSelEnd; 21 AutocompleteState(AutocompleteState a)22 public AutocompleteState(AutocompleteState a) { 23 copyFrom(a); 24 } 25 AutocompleteState(String userText, String autocompleteText, int selStart, int selEnd)26 public AutocompleteState(String userText, String autocompleteText, int selStart, int selEnd) { 27 set(userText, autocompleteText, selStart, selEnd); 28 } 29 set(String userText, String autocompleteText, int selStart, int selEnd)30 public void set(String userText, String autocompleteText, int selStart, int selEnd) { 31 mUserText = userText; 32 mAutocompleteText = autocompleteText; 33 mSelStart = selStart; 34 mSelEnd = selEnd; 35 } 36 copyFrom(AutocompleteState a)37 public void copyFrom(AutocompleteState a) { 38 set(a.mUserText, a.mAutocompleteText, a.mSelStart, a.mSelEnd); 39 } 40 getUserText()41 public String getUserText() { 42 return mUserText; 43 } 44 getAutocompleteText()45 public String getAutocompleteText() { 46 return mAutocompleteText; 47 } 48 hasAutocompleteText()49 public boolean hasAutocompleteText() { 50 return !TextUtils.isEmpty(mAutocompleteText); 51 } 52 53 /** @return The whole text including autocomplete text. */ getText()54 public String getText() { 55 return mUserText + mAutocompleteText; 56 } 57 getSelStart()58 public int getSelStart() { 59 return mSelStart; 60 } 61 getSelEnd()62 public int getSelEnd() { 63 return mSelEnd; 64 } 65 setSelection(int selStart, int selEnd)66 public void setSelection(int selStart, int selEnd) { 67 mSelStart = selStart; 68 mSelEnd = selEnd; 69 } 70 setUserText(String userText)71 public void setUserText(String userText) { 72 mUserText = userText; 73 } 74 setAutocompleteText(String autocompleteText)75 public void setAutocompleteText(String autocompleteText) { 76 mAutocompleteText = autocompleteText; 77 } 78 clearAutocompleteText()79 public void clearAutocompleteText() { 80 mAutocompleteText = ""; 81 } 82 isCursorAtEndOfUserText()83 public boolean isCursorAtEndOfUserText() { 84 return mSelStart == mUserText.length() && mSelEnd == mUserText.length(); 85 } 86 isWholeUserTextSelected()87 public boolean isWholeUserTextSelected() { 88 return mSelStart == 0 && mSelEnd == mUserText.length(); 89 } 90 91 /** 92 * @param prevState The previous state to compare the current state with. 93 * @return Whether the current state is backward-deleted from prevState. 94 */ isBackwardDeletedFrom(AutocompleteState prevState)95 public boolean isBackwardDeletedFrom(AutocompleteState prevState) { 96 return isCursorAtEndOfUserText() && prevState.isCursorAtEndOfUserText() 97 && isPrefix(mUserText, prevState.mUserText); 98 } 99 100 /** 101 * @param prevState The previous state to compare the current state with. 102 * @return Whether the current state is forward-typed from prevState. 103 */ isForwardTypedFrom(AutocompleteState prevState)104 public boolean isForwardTypedFrom(AutocompleteState prevState) { 105 return isCursorAtEndOfUserText() && prevState.isCursorAtEndOfUserText() 106 && isPrefix(prevState.mUserText, mUserText); 107 } 108 109 /** 110 * @param prevState The previous state to compare the current state with. 111 * @return The differential string that has been backward deleted. 112 */ getBackwardDeletedTextFrom(AutocompleteState prevState)113 public String getBackwardDeletedTextFrom(AutocompleteState prevState) { 114 if (!isBackwardDeletedFrom(prevState)) return null; 115 return prevState.mUserText.substring(mUserText.length()); 116 } 117 118 @VisibleForTesting isPrefix(String a, String b)119 public static boolean isPrefix(String a, String b) { 120 return b.startsWith(a) && b.length() > a.length(); 121 } 122 123 /** 124 * When the user manually types the next character that was already suggested in the previous 125 * autocomplete, then the suggestion is still valid if we simply remove one character from the 126 * beginning of it. For example, if prev = "a[bc]" and current text is "ab", this method 127 * constructs "ab[c]". 128 * @param prevState The previous state. 129 * @return Whether the shifting was successful. 130 */ reuseAutocompleteTextIfPrefixExtension(AutocompleteState prevState)131 public boolean reuseAutocompleteTextIfPrefixExtension(AutocompleteState prevState) { 132 // Shift when user text has grown or remains the same, but still prefix of prevState's whole 133 // text. 134 int diff = mUserText.length() - prevState.mUserText.length(); 135 if (diff < 0) return false; 136 if (!isPrefix(mUserText, prevState.getText())) return false; 137 mAutocompleteText = prevState.mAutocompleteText.substring(diff); 138 return true; 139 } 140 commitAutocompleteText()141 public void commitAutocompleteText() { 142 mUserText += mAutocompleteText; 143 mAutocompleteText = ""; 144 } 145 146 @Override equals(Object o)147 public boolean equals(Object o) { 148 if (!(o instanceof AutocompleteState)) return false; 149 if (o == this) return true; 150 AutocompleteState a = (AutocompleteState) o; 151 return mUserText.equals(a.mUserText) && mAutocompleteText.equals(a.mAutocompleteText) 152 && mSelStart == a.mSelStart && mSelEnd == a.mSelEnd; 153 } 154 155 @Override hashCode()156 public int hashCode() { 157 return mUserText.hashCode() * 2 + mAutocompleteText.hashCode() * 3 + mSelStart * 5 158 + mSelEnd * 7; 159 } 160 161 @Override toString()162 public String toString() { 163 return String.format(Locale.US, "AutocompleteState {[%s][%s] [%d-%d]}", mUserText, 164 mAutocompleteText, mSelStart, mSelEnd); 165 } 166 }