script-astra/Android/Sdk/sources/android-35/com/android/internal/inputmethod/EditableInputConnection.java

347 lines
13 KiB
Java
Raw Normal View History

2025-01-20 15:15:20 +00:00
/*
* Copyright (C) 2007 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.
*/
package com.android.internal.inputmethod;
import static android.view.inputmethod.InputConnectionProto.CURSOR_CAPS_MODE;
import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT_END;
import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT_START;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.text.Editable;
import android.text.Selection;
import android.text.method.KeyListener;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.DeleteGesture;
import android.view.inputmethod.DeleteRangeGesture;
import android.view.inputmethod.DumpableInputConnection;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.HandwritingGesture;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InsertGesture;
import android.view.inputmethod.InsertModeGesture;
import android.view.inputmethod.JoinOrSplitGesture;
import android.view.inputmethod.PreviewableHandwritingGesture;
import android.view.inputmethod.RemoveSpaceGesture;
import android.view.inputmethod.SelectGesture;
import android.view.inputmethod.SelectRangeGesture;
import android.view.inputmethod.TextBoundsInfo;
import android.view.inputmethod.TextBoundsInfoResult;
import android.widget.TextView;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
/**
* Base class for an editable InputConnection instance. This is created by {@link TextView} or
* {@link android.widget.EditText}.
*/
public final class EditableInputConnection extends BaseInputConnection
implements DumpableInputConnection {
private static final boolean DEBUG = false;
private static final String TAG = "EditableInputConnection";
private final TextView mTextView;
// Keeps track of nested begin/end batch edit to ensure this connection always has a
// balanced impact on its associated TextView.
// A negative value means that this connection has been finished by the InputMethodManager.
private int mBatchEditNesting;
public EditableInputConnection(TextView textview) {
super(textview, true);
mTextView = textview;
}
@Override
public Editable getEditable() {
TextView tv = mTextView;
if (tv != null) {
return tv.getEditableText();
}
return null;
}
@Override
public boolean beginBatchEdit() {
synchronized (this) {
if (mBatchEditNesting >= 0) {
mTextView.beginBatchEdit();
mBatchEditNesting++;
return true;
}
}
return false;
}
@Override
public boolean endBatchEdit() {
synchronized (this) {
if (mBatchEditNesting > 0) {
// When the connection is reset by the InputMethodManager and reportFinish
// is called, some endBatchEdit calls may still be asynchronously received from the
// IME. Do not take these into account, thus ensuring that this IC's final
// contribution to mTextView's nested batch edit count is zero.
mTextView.endBatchEdit();
mBatchEditNesting--;
return mBatchEditNesting > 0;
}
}
return false;
}
@Override
public void endComposingRegionEditInternal() {
// The ContentCapture service is interested in Composing-state changes.
mTextView.notifyContentCaptureTextChanged();
}
@Override
public void closeConnection() {
super.closeConnection();
synchronized (this) {
while (mBatchEditNesting > 0) {
endBatchEdit();
}
// Will prevent any further calls to begin or endBatchEdit
mBatchEditNesting = -1;
}
}
@Override
public boolean clearMetaKeyStates(int states) {
final Editable content = getEditable();
if (content == null) return false;
KeyListener kl = mTextView.getKeyListener();
if (kl != null) {
try {
kl.clearMetaKeyState(mTextView, content, states);
} catch (AbstractMethodError e) {
// This is an old listener that doesn't implement the
// new method.
}
}
return true;
}
@Override
public boolean commitCompletion(CompletionInfo text) {
if (DEBUG) Log.v(TAG, "commitCompletion " + text);
mTextView.beginBatchEdit();
mTextView.onCommitCompletion(text);
mTextView.endBatchEdit();
return true;
}
/**
* Calls the {@link TextView#onCommitCorrection} method of the associated TextView.
*/
@Override
public boolean commitCorrection(CorrectionInfo correctionInfo) {
if (DEBUG) Log.v(TAG, "commitCorrection" + correctionInfo);
mTextView.beginBatchEdit();
mTextView.onCommitCorrection(correctionInfo);
mTextView.endBatchEdit();
return true;
}
@Override
public boolean performEditorAction(int actionCode) {
if (DEBUG) Log.v(TAG, "performEditorAction " + actionCode);
mTextView.onEditorAction(actionCode);
return true;
}
@Override
public boolean performContextMenuAction(int id) {
if (DEBUG) Log.v(TAG, "performContextMenuAction " + id);
mTextView.beginBatchEdit();
mTextView.onTextContextMenuItem(id);
mTextView.endBatchEdit();
return true;
}
@Override
public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
if (mTextView != null) {
ExtractedText et = new ExtractedText();
if (mTextView.extractText(request, et)) {
if ((flags & GET_EXTRACTED_TEXT_MONITOR) != 0) {
mTextView.setExtracting(request);
}
return et;
}
}
return null;
}
@Override
public boolean performSpellCheck() {
mTextView.onPerformSpellCheck();
return true;
}
@Override
public boolean performPrivateCommand(String action, Bundle data) {
mTextView.onPrivateIMECommand(action, data);
return true;
}
@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
if (mTextView == null) {
return super.commitText(text, newCursorPosition);
}
mTextView.resetErrorChangedFlag();
boolean success = super.commitText(text, newCursorPosition);
mTextView.hideErrorIfUnchanged();
return success;
}
@Override
public boolean requestCursorUpdates(
@CursorUpdateMode int cursorUpdateMode, @CursorUpdateFilter int cursorUpdateFilter) {
// TODO(b/210039666): use separate attrs for updateMode and updateFilter.
return requestCursorUpdates(cursorUpdateMode | cursorUpdateFilter);
}
@Override
public boolean requestCursorUpdates(int cursorUpdateMode) {
if (DEBUG) Log.v(TAG, "requestUpdateCursorAnchorInfo " + cursorUpdateMode);
final int knownModeFlags = InputConnection.CURSOR_UPDATE_IMMEDIATE
| InputConnection.CURSOR_UPDATE_MONITOR;
final int knownFilterFlags = InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS
| InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER
| InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS
| InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS
| InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE;
// It is possible that any other bit is used as a valid flag in a future release.
// We should reject the entire request in such a case.
final int knownFlagMask = knownModeFlags | knownFilterFlags;
final int unknownFlags = cursorUpdateMode & ~knownFlagMask;
if (unknownFlags != 0) {
if (DEBUG) {
Log.d(TAG, "Rejecting requestUpdateCursorAnchorInfo due to unknown flags. "
+ "cursorUpdateMode=" + cursorUpdateMode + " unknownFlags=" + unknownFlags);
}
return false;
}
if (mIMM == null) {
// In this case, TYPE_CURSOR_ANCHOR_INFO is not handled.
// TODO: Return some notification code rather than false to indicate method that
// CursorAnchorInfo is temporarily unavailable.
return false;
}
mIMM.setUpdateCursorAnchorInfoMode(cursorUpdateMode); // for UnsupportedAppUsage
if (mTextView != null) {
mTextView.onRequestCursorUpdatesInternal(cursorUpdateMode & knownModeFlags,
cursorUpdateMode & knownFilterFlags);
}
return true;
}
@Override
public void requestTextBoundsInfo(
@NonNull RectF bounds, @Nullable @CallbackExecutor Executor executor,
@NonNull Consumer<TextBoundsInfoResult> consumer) {
final TextBoundsInfo textBoundsInfo = mTextView.getTextBoundsInfo(bounds);
final int resultCode;
if (textBoundsInfo != null) {
resultCode = TextBoundsInfoResult.CODE_SUCCESS;
} else {
resultCode = TextBoundsInfoResult.CODE_FAILED;
}
final TextBoundsInfoResult textBoundsInfoResult =
new TextBoundsInfoResult(resultCode, textBoundsInfo);
executor.execute(() -> consumer.accept(textBoundsInfoResult));
}
@Override
public boolean setImeConsumesInput(boolean imeConsumesInput) {
if (mTextView == null) {
return super.setImeConsumesInput(imeConsumesInput);
}
mTextView.setImeConsumesInput(imeConsumesInput);
return true;
}
@Override
public void performHandwritingGesture(
@NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
@Nullable IntConsumer consumer) {
int result;
if (gesture instanceof SelectGesture) {
result = mTextView.performHandwritingSelectGesture((SelectGesture) gesture);
} else if (gesture instanceof SelectRangeGesture) {
result = mTextView.performHandwritingSelectRangeGesture((SelectRangeGesture) gesture);
} else if (gesture instanceof DeleteGesture) {
result = mTextView.performHandwritingDeleteGesture((DeleteGesture) gesture);
} else if (gesture instanceof DeleteRangeGesture) {
result = mTextView.performHandwritingDeleteRangeGesture((DeleteRangeGesture) gesture);
} else if (gesture instanceof InsertGesture) {
result = mTextView.performHandwritingInsertGesture((InsertGesture) gesture);
} else if (gesture instanceof RemoveSpaceGesture) {
result = mTextView.performHandwritingRemoveSpaceGesture((RemoveSpaceGesture) gesture);
} else if (gesture instanceof JoinOrSplitGesture) {
result = mTextView.performHandwritingJoinOrSplitGesture((JoinOrSplitGesture) gesture);
} else if (gesture instanceof InsertModeGesture) {
result = mTextView.performHandwritingInsertModeGesture((InsertModeGesture) gesture);
} else {
result = HANDWRITING_GESTURE_RESULT_UNSUPPORTED;
}
if (executor != null && consumer != null) {
executor.execute(() -> consumer.accept(result));
}
}
@Override
public boolean previewHandwritingGesture(
@NonNull PreviewableHandwritingGesture gesture,
@Nullable CancellationSignal cancellationSignal) {
return mTextView.previewHandwritingGesture(gesture, cancellationSignal);
}
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
final Editable content = getEditable();
if (content != null) {
int start = Selection.getSelectionStart(content);
int end = Selection.getSelectionEnd(content);
proto.write(SELECTED_TEXT_START, start);
proto.write(SELECTED_TEXT_END, end);
}
proto.write(CURSOR_CAPS_MODE, getCursorCapsMode(0));
proto.end(token);
}
}