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 }