1 /*
2  * Copyright (C) 2020 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 android.view.inputmethod;
18 
19 import android.annotation.IntRange;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.text.TextUtils;
25 
26 /**
27  * Information about the surrounding text around the cursor for use by an input method.
28  *
29  * <p>This contains information about the text and the selection relative to the text. </p>
30  */
31 public final class SurroundingText implements Parcelable {
32     /**
33      * The surrounding text around the cursor.
34      */
35     @NonNull
36     private final CharSequence mText;
37 
38     /**
39      * The text offset of the start of the selection in the surrounding text.
40      *
41      * <p>This needs to be the position relative to the {@link #mText} instead of the real position
42      * in the editor.</p>
43      */
44     @IntRange(from = 0)
45     private final int mSelectionStart;
46 
47     /**
48      * The text offset of the end of the selection in the surrounding text.
49      *
50      * <p>This needs to be the position relative to the {@link #mText} instead of the real position
51      * in the editor.</p>
52      */
53     @IntRange(from = 0)
54     private final int mSelectionEnd;
55 
56     /**
57      * The text offset between the start of the editor's text and the start of the surrounding text.
58      *
59      * <p>-1 indicates the offset information is unknown.</p>
60      */
61     @IntRange(from = -1)
62     private final int mOffset;
63 
64     /**
65      * Constructor.
66      *
67      * @param text The surrounding text.
68      * @param selectionStart The text offset of the start of the selection in the surrounding text.
69      *                       Reversed selection is allowed.
70      * @param selectionEnd The text offset of the end of the selection in the surrounding text.
71      *                     Reversed selection is allowed.
72      * @param offset The text offset between the start of the editor's text and the start of the
73      *               surrounding text. -1 indicates the offset is unknown.
74      */
SurroundingText(@onNull final CharSequence text, @IntRange(from = 0) int selectionStart, @IntRange(from = 0) int selectionEnd, @IntRange(from = -1) int offset)75     public SurroundingText(@NonNull final CharSequence text,
76             @IntRange(from = 0) int selectionStart, @IntRange(from = 0) int selectionEnd,
77             @IntRange(from = -1) int offset) {
78         mText = copyWithParcelableSpans(text);
79         mSelectionStart = selectionStart;
80         mSelectionEnd = selectionEnd;
81         mOffset = offset;
82     }
83 
84     /**
85      * Returns the surrounding text around the cursor.
86      */
87     @NonNull
getText()88     public CharSequence getText() {
89         return mText;
90     }
91 
92     /**
93      * Returns the text offset of the start of the selection in the surrounding text.
94      *
95      * <p>A selection is the current range of the text that is selected by the user, or the current
96      * position of the cursor. A cursor is a selection where the start and end are at the same
97      * offset.<p>
98      */
99     @IntRange(from = 0)
getSelectionStart()100     public int getSelectionStart() {
101         return mSelectionStart;
102     }
103 
104     /**
105      * Returns the text offset of the end of the selection in the surrounding text.
106      *
107      * <p>A selection is the current range of the text that is selected by the user, or the current
108      * position of the cursor. A cursor is a selection where the start and end are at the same
109      * offset.<p>
110      */
111     @IntRange(from = 0)
getSelectionEnd()112     public int getSelectionEnd() {
113         return mSelectionEnd;
114     }
115 
116     /**
117      * Returns text offset between the start of the editor's text and the start of the surrounding
118      * text.
119      *
120      * <p>-1 indicates the offset information is unknown.</p>
121      */
122     @IntRange(from = -1)
getOffset()123     public int getOffset() {
124         return mOffset;
125     }
126 
127     @Override
writeToParcel(@onNull Parcel out, int flags)128     public void writeToParcel(@NonNull Parcel out, int flags) {
129         TextUtils.writeToParcel(mText, out, flags);
130         out.writeInt(mSelectionStart);
131         out.writeInt(mSelectionEnd);
132         out.writeInt(mOffset);
133     }
134 
135     @Override
describeContents()136     public int describeContents() {
137         return 0;
138     }
139 
140     @NonNull
141     public static final Parcelable.Creator<SurroundingText> CREATOR =
142             new Parcelable.Creator<SurroundingText>() {
143                 @NonNull
144                 public SurroundingText createFromParcel(Parcel in) {
145                     final CharSequence text =
146                             TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
147                     final int selectionHead = in.readInt();
148                     final int selectionEnd = in.readInt();
149                     final int offset = in.readInt();
150                     return new SurroundingText(
151                             text == null ? "" : text,  selectionHead, selectionEnd, offset);
152                 }
153 
154                 @NonNull
155                 public SurroundingText[] newArray(int size) {
156                     return new SurroundingText[size];
157                 }
158             };
159 
160     /**
161      * Create a copy of the given {@link CharSequence} object, with completely copy
162      * {@link ParcelableSpan} instances.
163      *
164      * @param source the original {@link CharSequence} to be copied.
165      * @return the copied {@link CharSequence}. {@code null} if {@code source} is {@code null}.
166      */
167     @Nullable
copyWithParcelableSpans(@ullable CharSequence source)168     private static CharSequence copyWithParcelableSpans(@Nullable CharSequence source) {
169         if (source == null) {
170             return null;
171         }
172         Parcel parcel = null;
173         try {
174             parcel = Parcel.obtain();
175             TextUtils.writeToParcel(source, parcel, /* parcelableFlags= */ 0);
176             parcel.setDataPosition(0);
177             return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
178         } finally {
179             if (parcel != null) {
180                 parcel.recycle();
181             }
182         }
183     }
184 
185     /** @hide */
isEqualTo(@ullable SurroundingText that)186     public boolean isEqualTo(@Nullable SurroundingText that) {
187         if (that == null) return false;
188         if (this == that) return true;
189         return mSelectionStart == that.mSelectionStart
190                 && mSelectionEnd == that.mSelectionEnd
191                 && mOffset == that.mOffset
192                 && TextUtils.equals(mText, that.mText);
193     }
194 }
195