/* * Copyright (C) 2022 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.example.android.sampleinputmethodaccessibilityservice; import android.os.Parcel; import android.view.inputmethod.EditorInfo; import androidx.annotation.Nullable; final class EventMonitor { @FunctionalInterface interface DebugMessageCallback { void onMessageChanged(String message); } private enum State { BeforeFirstStartInput, InputStarted, InputRestarted, InputFinished, } private State mState = State.BeforeFirstStartInput; private int mStartInputCount = 0; private int mUpdateSelectionCount = 0; private int mFinishInputCount = 0; private int mSelStart = -1; private int mSelEnd = -1; private int mCompositionStart = -1; private int mCompositionEnd = -1; @Nullable private EditorInfo mEditorInfo; @Nullable private final DebugMessageCallback mDebugMessageCallback; void onStartInput(EditorInfo attribute, boolean restarting) { ++mStartInputCount; mState = restarting ? State.InputRestarted : State.InputStarted; mSelStart = attribute.initialSelStart; mSelEnd = attribute.initialSelEnd; mCompositionStart = -1; mCompositionEnd = -1; mEditorInfo = cloneEditorInfo(attribute); updateMessage(); } void onFinishInput() { ++mFinishInputCount; mState = State.InputFinished; mEditorInfo = null; updateMessage(); } void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { ++mUpdateSelectionCount; mSelStart = newSelStart; mSelEnd = newSelEnd; mCompositionStart = candidatesStart; mCompositionEnd = candidatesEnd; updateMessage(); } EventMonitor(@Nullable DebugMessageCallback callback) { mDebugMessageCallback = callback; } private void updateMessage() { if (mDebugMessageCallback == null) { return; } final StringBuilder sb = new StringBuilder(); sb.append("state=").append(mState).append("\n") .append("startInputCount=").append(mStartInputCount).append("\n") .append("finishInputCount=").append(mFinishInputCount).append("\n") .append("updateSelectionCount=").append(mUpdateSelectionCount).append("\n"); if (mSelStart == -1 && mSelEnd == -1) { sb.append("selection=none\n"); } else { sb.append("selection=(").append(mSelStart).append(",").append(mSelEnd).append(")\n"); } if (mCompositionStart == -1 && mCompositionEnd == -1) { sb.append("composition=none"); } else { sb.append("composition=(") .append(mCompositionStart).append(",").append(mCompositionEnd).append(")"); } if (mEditorInfo != null) { sb.append("\n"); sb.append("packageName=").append(mEditorInfo.packageName).append("\n"); sb.append("inputType="); EditorInfoUtil.dumpInputType(sb, mEditorInfo.inputType); sb.append("\n"); sb.append("imeOptions="); EditorInfoUtil.dumpImeOptions(sb, mEditorInfo.imeOptions); } mDebugMessageCallback.onMessageChanged(sb.toString()); } private static EditorInfo cloneEditorInfo(EditorInfo original) { Parcel parcel = null; try { parcel = Parcel.obtain(); original.writeToParcel(parcel, 0); parcel.setDataPosition(0); return EditorInfo.CREATOR.createFromParcel(parcel); } finally { if (parcel != null) { parcel.recycle(); } } } }