Newer
Older
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
import static org.futo.inputmethod.latin.common.Constants.ImeOption.NO_FLOATING_GESTURE_PREVIEW;
import static org.futo.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE;
import static org.futo.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE_COMPAT;
import android.text.InputType;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
import org.futo.inputmethod.latin.common.StringUtils;
import org.futo.inputmethod.latin.utils.InputTypeUtils;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Class to hold attributes of the input field.
*/
public final class InputAttributes {
private final String TAG = InputAttributes.class.getSimpleName();
final public String mTargetApplicationPackageName;
final public boolean mInputTypeNoAutoCorrect;
final public boolean mIsPasswordField;
final public boolean mIsNumericalPasswordField;
final public boolean mIsEmailField;
final public boolean mShouldShowSuggestions;
final public boolean mApplicationSpecifiedCompletionOn;
final public boolean mShouldInsertSpacesAutomatically;
Aleksandras Kostarevas
committed
final public boolean mIsUriField;
Aleksandras Kostarevas
committed
final public boolean mIsCodeField;
Aleksandras Kostarevas
committed
/**
* Whether the floating gesture preview should be disabled. If true, this should override the
* corresponding keyboard settings preference, always suppressing the floating preview text.
* {@link org.futo.inputmethod.latin.settings.SettingsValues#mGestureFloatingPreviewTextEnabled}
*/
final public boolean mDisableGestureFloatingPreviewText;
final public boolean mIsGeneralTextInput;
final public boolean mNoLearning;
final private int mInputType;
final private EditorInfo mEditorInfo;
final private String mPackageNameForPrivateImeOptions;
public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode,
final String packageNameForPrivateImeOptions) {
mEditorInfo = editorInfo;
mPackageNameForPrivateImeOptions = packageNameForPrivateImeOptions;
mTargetApplicationPackageName = null != editorInfo ? editorInfo.packageName : null;
final int inputType = null != editorInfo ? editorInfo.inputType : 0;
final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
mInputType = inputType;
mIsPasswordField = InputTypeUtils.isPasswordInputType(inputType)
|| InputTypeUtils.isVisiblePasswordInputType(inputType);
mIsNumericalPasswordField = mIsPasswordField && (inputClass == InputType.TYPE_CLASS_NUMBER);
Aleksandras Kostarevas
committed
mIsUriField = InputTypeUtils.isUriType(inputType);
Aleksandras Kostarevas
committed
mIsCodeField = isCodeField(editorInfo);
if (inputClass != InputType.TYPE_CLASS_TEXT) {
// If we are not looking at a TYPE_CLASS_TEXT field, the following strange
// cases may arise, so we do a couple validity checks for them. If it's a
// TYPE_CLASS_TEXT field, these special cases cannot happen, by construction
// of the flags.
if (null == editorInfo) {
Log.w(TAG, "No editor info for this field. Bug?");
} else if (InputType.TYPE_NULL == inputType) {
// TODO: We should honor TYPE_NULL specification.
Log.i(TAG, "InputType.TYPE_NULL is specified");
Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x"
+ " imeOptions=0x%08x", inputType, editorInfo.imeOptions));
mShouldShowSuggestions = false;
mInputTypeNoAutoCorrect = false;
mApplicationSpecifiedCompletionOn = false;
mShouldInsertSpacesAutomatically = false;
mDisableGestureFloatingPreviewText = false;
mIsGeneralTextInput = false;
mIsEmailField = false;
return;
}
// inputClass == InputType.TYPE_CLASS_TEXT
final int variation = inputType & InputType.TYPE_MASK_VARIATION;
final boolean flagNoSuggestions =
0 != (inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
final boolean flagMultiLine =
0 != (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE);
final boolean flagAutoCorrect =
0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
final boolean flagAutoComplete =
0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
final boolean forceNoSuggestionsByPrivateFlag = editorInfo.privateImeOptions != null
&& editorInfo.privateImeOptions.contains("org.futo.inputmethod.latin.NoSuggestions=1");
// TODO: Have a helper method in InputTypeUtils
// Make sure that passwords are not displayed in {@link SuggestionStripView}.
final boolean shouldSuppressSuggestions = mIsPasswordField
|| forceNoSuggestionsByPrivateFlag;
mShouldShowSuggestions = !shouldSuppressSuggestions;
Aleksandras Kostarevas
committed
mShouldInsertSpacesAutomatically = InputTypeUtils.isAutoSpaceFriendlyType(inputType)
&& !mIsCodeField;
mDisableGestureFloatingPreviewText = InputAttributes.inPrivateImeOptions(
mPackageNameForPrivateImeOptions, NO_FLOATING_GESTURE_PREVIEW, editorInfo);
mIsEmailField = InputTypeUtils.isEmailVariation(variation);
// TODO: This may need adjustment
mInputTypeNoAutoCorrect = shouldSuppressSuggestions
|| (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT && !flagAutoCorrect)
|| InputTypeUtils.isEmailVariation(variation)
|| InputType.TYPE_TEXT_VARIATION_URI == variation
|| InputType.TYPE_TEXT_VARIATION_FILTER == variation
Aleksandras Kostarevas
committed
|| flagNoSuggestions
|| mIsCodeField;
// If it's a browser edit field and auto correct is not ON explicitly, then
// disable auto correction, but keep suggestions on.
// If NO_SUGGESTIONS is set, don't do prediction.
// If it's not multiline and the autoCorrect flag is not set, then don't correct
//mInputTypeNoAutoCorrect =
// (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT && !flagAutoCorrect)
// || flagNoSuggestions
// || (!flagAutoCorrect && !flagMultiLine);
mApplicationSpecifiedCompletionOn = flagAutoComplete && isFullscreenMode;
// If we come here, inputClass is always TYPE_CLASS_TEXT
mIsGeneralTextInput = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS != variation
&& InputType.TYPE_TEXT_VARIATION_PASSWORD != variation
&& InputType.TYPE_TEXT_VARIATION_PHONETIC != variation
&& InputType.TYPE_TEXT_VARIATION_URI != variation
&& InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD != variation
&& InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != variation
&& InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD != variation;
boolean noLearning = false;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
noLearning = (mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING) != 0;
}
Aleksandras Kostarevas
committed
mNoLearning = noLearning || mIsPasswordField || mIsCodeField;
public boolean isTypeNull() {
return InputType.TYPE_NULL == mInputType;
}
public boolean isSameInputType(final EditorInfo editorInfo) {
return editorInfo.inputType == mInputType;
}
private boolean hasNoMicrophoneKeyOption() {
@SuppressWarnings("deprecation")
final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
null, NO_MICROPHONE_COMPAT, mEditorInfo);
final boolean noMicrophone = InputAttributes.inPrivateImeOptions(
mPackageNameForPrivateImeOptions, NO_MICROPHONE, mEditorInfo);
return noMicrophone || deprecatedNoMicrophone;
}
private void dumpFlags(final int inputType) {
final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
final String inputClassString = toInputClassString(inputClass);
final String variationString = toVariationString(
inputClass, inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
final String flagsString = toFlagsString(inputType & InputType.TYPE_MASK_FLAGS);
Log.i(TAG, "Input class: " + inputClassString);
Log.i(TAG, "Variation: " + variationString);
Log.i(TAG, "Flags: " + flagsString);
}
private static String toInputClassString(final int inputClass) {
switch (inputClass) {
case InputType.TYPE_CLASS_TEXT:
return "TYPE_CLASS_TEXT";
case InputType.TYPE_CLASS_PHONE:
return "TYPE_CLASS_PHONE";
case InputType.TYPE_CLASS_NUMBER:
return "TYPE_CLASS_NUMBER";
case InputType.TYPE_CLASS_DATETIME:
return "TYPE_CLASS_DATETIME";
default:
return String.format("unknownInputClass<0x%08x>", inputClass);
}
}
private static String toVariationString(final int inputClass, final int variation) {
switch (inputClass) {
case InputType.TYPE_CLASS_TEXT:
return toTextVariationString(variation);
case InputType.TYPE_CLASS_NUMBER:
return toNumberVariationString(variation);
case InputType.TYPE_CLASS_DATETIME:
return toDatetimeVariationString(variation);
default:
return "";
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
}
private static String toTextVariationString(final int variation) {
switch (variation) {
case InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS:
return " TYPE_TEXT_VARIATION_EMAIL_ADDRESS";
case InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT:
return "TYPE_TEXT_VARIATION_EMAIL_SUBJECT";
case InputType.TYPE_TEXT_VARIATION_FILTER:
return "TYPE_TEXT_VARIATION_FILTER";
case InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE:
return "TYPE_TEXT_VARIATION_LONG_MESSAGE";
case InputType.TYPE_TEXT_VARIATION_NORMAL:
return "TYPE_TEXT_VARIATION_NORMAL";
case InputType.TYPE_TEXT_VARIATION_PASSWORD:
return "TYPE_TEXT_VARIATION_PASSWORD";
case InputType.TYPE_TEXT_VARIATION_PERSON_NAME:
return "TYPE_TEXT_VARIATION_PERSON_NAME";
case InputType.TYPE_TEXT_VARIATION_PHONETIC:
return "TYPE_TEXT_VARIATION_PHONETIC";
case InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS:
return "TYPE_TEXT_VARIATION_POSTAL_ADDRESS";
case InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE:
return "TYPE_TEXT_VARIATION_SHORT_MESSAGE";
case InputType.TYPE_TEXT_VARIATION_URI:
return "TYPE_TEXT_VARIATION_URI";
case InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD:
return "TYPE_TEXT_VARIATION_VISIBLE_PASSWORD";
case InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT:
return "TYPE_TEXT_VARIATION_WEB_EDIT_TEXT";
case InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS:
return "TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS";
case InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD:
return "TYPE_TEXT_VARIATION_WEB_PASSWORD";
default:
return String.format("unknownVariation<0x%08x>", variation);
}
}
private static String toNumberVariationString(final int variation) {
switch (variation) {
case InputType.TYPE_NUMBER_VARIATION_NORMAL:
return "TYPE_NUMBER_VARIATION_NORMAL";
case InputType.TYPE_NUMBER_VARIATION_PASSWORD:
return "TYPE_NUMBER_VARIATION_PASSWORD";
default:
return String.format("unknownVariation<0x%08x>", variation);
}
}
private static String toDatetimeVariationString(final int variation) {
switch (variation) {
case InputType.TYPE_DATETIME_VARIATION_NORMAL:
return "TYPE_DATETIME_VARIATION_NORMAL";
case InputType.TYPE_DATETIME_VARIATION_DATE:
return "TYPE_DATETIME_VARIATION_DATE";
case InputType.TYPE_DATETIME_VARIATION_TIME:
return "TYPE_DATETIME_VARIATION_TIME";
default:
return String.format("unknownVariation<0x%08x>", variation);
}
}
private static String toFlagsString(final int flags) {
final ArrayList<String> flagsArray = new ArrayList<>();
if (0 != (flags & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS))
flagsArray.add("TYPE_TEXT_FLAG_NO_SUGGESTIONS");
if (0 != (flags & InputType.TYPE_TEXT_FLAG_MULTI_LINE))
flagsArray.add("TYPE_TEXT_FLAG_MULTI_LINE");
if (0 != (flags & InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE))
flagsArray.add("TYPE_TEXT_FLAG_IME_MULTI_LINE");
if (0 != (flags & InputType.TYPE_TEXT_FLAG_CAP_WORDS))
flagsArray.add("TYPE_TEXT_FLAG_CAP_WORDS");
if (0 != (flags & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES))
flagsArray.add("TYPE_TEXT_FLAG_CAP_SENTENCES");
if (0 != (flags & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS))
flagsArray.add("TYPE_TEXT_FLAG_CAP_CHARACTERS");
if (0 != (flags & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT))
flagsArray.add("TYPE_TEXT_FLAG_AUTO_CORRECT");
if (0 != (flags & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE))
flagsArray.add("TYPE_TEXT_FLAG_AUTO_COMPLETE");
return flagsArray.isEmpty() ? "" : Arrays.toString(flagsArray.toArray());
// Pretty print
@Override
public String toString() {
return String.format(
"%s: inputType=0x%08x%s%s%s%s%s targetApp=%s\n", getClass().getSimpleName(),
mInputType,
(mInputTypeNoAutoCorrect ? " noAutoCorrect" : ""),
(mIsPasswordField ? " password" : ""),
(mShouldShowSuggestions ? " shouldShowSuggestions" : ""),
(mApplicationSpecifiedCompletionOn ? " appSpecified" : ""),
(mShouldInsertSpacesAutomatically ? " insertSpaces" : ""),
mTargetApplicationPackageName);
public static boolean inPrivateImeOptions(final String packageName, final String key,
final EditorInfo editorInfo) {
if (editorInfo == null) return false;
final String findingKey = (packageName != null) ? packageName + "." + key : key;
return StringUtils.containsInCommaSplittableText(findingKey, editorInfo.privateImeOptions);
Aleksandras Kostarevas
committed
private static boolean isCodeField(final EditorInfo editorInfo) {
if(editorInfo == null || editorInfo.packageName == null) return false;
Aleksandras Kostarevas
committed
boolean noAutocorrect = (editorInfo.inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0;
if(editorInfo.packageName.startsWith("com.replit")) return noAutocorrect;
if(editorInfo.packageName.startsWith("com.termux")) return editorInfo.inputType == 0;
Aleksandras Kostarevas
committed
return false;
}