package android.accessibilityservice;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteException;
import android.os.Trace;
import android.util.Log;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.SurroundingText;
import android.view.inputmethod.TextAttribute;
import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
public class InputMethod {
private static final String LOG_TAG = "A11yInputMethod";
private final AccessibilityService mService;
private boolean mInputStarted;
private RemoteAccessibilityInputConnection mStartedInputConnection;
private EditorInfo mInputEditorInfo;
public InputMethod(@NonNull AccessibilityService service) {
mService = service;
}
@Nullable
public final AccessibilityInputConnection getCurrentInputConnection() {
if (mStartedInputConnection != null) {
return new AccessibilityInputConnection(mStartedInputConnection);
}
return null;
}
public final boolean getCurrentInputStarted() {
return mInputStarted;
}
@Nullable
public final EditorInfo getCurrentInputEditorInfo() {
return mInputEditorInfo;
}
public void onStartInput(@NonNull EditorInfo attribute, boolean restarting) {
// Intentionally empty
}
public void onFinishInput() {
// Intentionally empty
}
public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart,
int newSelEnd, int candidatesStart, int candidatesEnd) {
// Intentionally empty
}
final void createImeSession(IAccessibilityInputMethodSessionCallback callback) {
final AccessibilityInputMethodSessionWrapper wrapper =
new AccessibilityInputMethodSessionWrapper(mService.getMainLooper(),
new SessionImpl());
try {
callback.sessionCreated(wrapper, mService.getConnectionId());
} catch (RemoteException ignored) {
}
}
final void startInput(@Nullable RemoteAccessibilityInputConnection ic,
@NonNull EditorInfo attribute) {
Log.v(LOG_TAG, "startInput(): editor=" + attribute);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AccessibilityService.startInput");
doStartInput(ic, attribute, false /* restarting */);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
final void restartInput(@Nullable RemoteAccessibilityInputConnection ic,
@NonNull EditorInfo attribute) {
Log.v(LOG_TAG, "restartInput(): editor=" + attribute);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AccessibilityService.restartInput");
doStartInput(ic, attribute, true /* restarting */);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
final void doStartInput(RemoteAccessibilityInputConnection ic, EditorInfo attribute,
boolean restarting) {
if ((ic == null || !restarting) && mInputStarted) {
doFinishInput();
if (ic == null) {
// Unlike InputMethodService, A11y IME should not observe fallback InputConnection.
return;
}
}
mInputStarted = true;
mStartedInputConnection = ic;
mInputEditorInfo = attribute;
Log.v(LOG_TAG, "CALL: onStartInput");
onStartInput(attribute, restarting);
}
final void doFinishInput() {
Log.v(LOG_TAG, "CALL: doFinishInput");
if (mInputStarted) {
Log.v(LOG_TAG, "CALL: onFinishInput");
onFinishInput();
}
mInputStarted = false;
mStartedInputConnection = null;
mInputEditorInfo = null;
}
public final class AccessibilityInputConnection {
private final RemoteAccessibilityInputConnection mIc;
AccessibilityInputConnection(RemoteAccessibilityInputConnection ic) {
this.mIc = ic;
}
public void commitText(@NonNull CharSequence text, int newCursorPosition,
@Nullable TextAttribute textAttribute) {
if (mIc != null) {
mIc.commitText(text, newCursorPosition, textAttribute);
}
}
public void setSelection(int start, int end) {
if (mIc != null) {
mIc.setSelection(start, end);
}
}
@Nullable
public SurroundingText getSurroundingText(
@IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength,
@InputConnection.GetTextType int flags) {
if (mIc != null) {
return mIc.getSurroundingText(beforeLength, afterLength, flags);
}
return null;
}
public void deleteSurroundingText(int beforeLength, int afterLength) {
if (mIc != null) {
mIc.deleteSurroundingText(beforeLength, afterLength);
}
}
public void sendKeyEvent(@NonNull KeyEvent event) {
if (mIc != null) {
mIc.sendKeyEvent(event);
}
}
public void performEditorAction(int editorAction) {
if (mIc != null) {
mIc.performEditorAction(editorAction);
}
}
public void performContextMenuAction(int id) {
if (mIc != null) {
mIc.performContextMenuAction(id);
}
}
public int getCursorCapsMode(int reqModes) {
if (mIc != null) {
return mIc.getCursorCapsMode(reqModes);
}
return 0;
}
public void clearMetaKeyStates(int states) {
if (mIc != null) {
mIc.clearMetaKeyStates(states);
}
}
}
/**
* Concrete implementation of {@link AccessibilityInputMethodSession} that provides all of the
* standard behavior for an A11y input method session.
*/
private final class SessionImpl implements AccessibilityInputMethodSession {
boolean mEnabled = true;
@Override
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
@Override
public void finishInput() {
if (mEnabled) {
doFinishInput();
}
}
@Override
public void updateSelection(int oldSelStart, int oldSelEnd, int newSelStart,
int newSelEnd, int candidatesStart, int candidatesEnd) {
if (mEnabled) {
InputMethod.this.onUpdateSelection(oldSelEnd, oldSelEnd, newSelStart,
newSelEnd, candidatesStart, candidatesEnd);
}
}
@Override
public void invalidateInput(EditorInfo editorInfo,
IRemoteAccessibilityInputConnection connection, int sessionId) {
if (!mEnabled || mStartedInputConnection == null
|| !mStartedInputConnection.isSameConnection(connection)) {
// This is not an error, and can be safely ignored.
return;
}
editorInfo.makeCompatible(mService.getApplicationInfo().targetSdkVersion);
restartInput(new RemoteAccessibilityInputConnection(mStartedInputConnection, sessionId),
editorInfo);
}
}
}
这个代码是一个安卓的辅助服务类,用于提供输入法的API。代码的逻辑和作用如下:
InputMethod类:这个类是一个辅助服务的输入法类,它有以下主要方法:
AccessibilityInputConnection类:这个类是一个辅助服务的输入连接类,它封装了一个远程输入连接对象,提供了以下主要方法:
SessionImpl类:这个类是一个辅助服务的输入法会话类,它实现了AccessibilityInputMethodSession接口,提供了以下主要方法: