1 /*
2  * Copyright (C) 2022 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 com.example.android.sampleinputmethodaccessibilityservice;
18 
19 import android.os.Parcel;
20 import android.view.inputmethod.EditorInfo;
21 
22 import androidx.annotation.Nullable;
23 
24 final class EventMonitor {
25     @FunctionalInterface
26     interface DebugMessageCallback {
onMessageChanged(String message)27         void onMessageChanged(String message);
28     }
29 
30     private enum State {
31         BeforeFirstStartInput,
32         InputStarted,
33         InputRestarted,
34         InputFinished,
35     }
36 
37     private State mState = State.BeforeFirstStartInput;
38     private int mStartInputCount = 0;
39     private int mUpdateSelectionCount = 0;
40     private int mFinishInputCount = 0;
41     private int mSelStart = -1;
42     private int mSelEnd = -1;
43     private int mCompositionStart = -1;
44     private int mCompositionEnd = -1;
45     @Nullable
46     private EditorInfo mEditorInfo;
47 
48     @Nullable
49     private final DebugMessageCallback mDebugMessageCallback;
50 
onStartInput(EditorInfo attribute, boolean restarting)51     void onStartInput(EditorInfo attribute, boolean restarting) {
52         ++mStartInputCount;
53         mState = restarting ? State.InputRestarted : State.InputStarted;
54         mSelStart = attribute.initialSelStart;
55         mSelEnd = attribute.initialSelEnd;
56         mCompositionStart = -1;
57         mCompositionEnd = -1;
58         mEditorInfo = cloneEditorInfo(attribute);
59         updateMessage();
60     }
61 
onFinishInput()62     void onFinishInput() {
63         ++mFinishInputCount;
64         mState = State.InputFinished;
65         mEditorInfo = null;
66         updateMessage();
67     }
68 
onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd)69     void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart,
70             int newSelEnd, int candidatesStart, int candidatesEnd) {
71         ++mUpdateSelectionCount;
72         mSelStart = newSelStart;
73         mSelEnd = newSelEnd;
74         mCompositionStart = candidatesStart;
75         mCompositionEnd = candidatesEnd;
76         updateMessage();
77     }
78 
EventMonitor(@ullable DebugMessageCallback callback)79     EventMonitor(@Nullable DebugMessageCallback callback) {
80         mDebugMessageCallback = callback;
81     }
82 
updateMessage()83     private void updateMessage() {
84         if (mDebugMessageCallback == null) {
85             return;
86         }
87 
88         final StringBuilder sb = new StringBuilder();
89         sb.append("state=").append(mState).append("\n")
90                 .append("startInputCount=").append(mStartInputCount).append("\n")
91                 .append("finishInputCount=").append(mFinishInputCount).append("\n")
92                 .append("updateSelectionCount=").append(mUpdateSelectionCount).append("\n");
93         if (mSelStart == -1 && mSelEnd == -1) {
94             sb.append("selection=none\n");
95         } else {
96             sb.append("selection=(").append(mSelStart).append(",").append(mSelEnd).append(")\n");
97         }
98         if (mCompositionStart == -1 && mCompositionEnd == -1) {
99             sb.append("composition=none");
100         } else {
101             sb.append("composition=(")
102                     .append(mCompositionStart).append(",").append(mCompositionEnd).append(")");
103         }
104         if (mEditorInfo != null) {
105             sb.append("\n");
106             sb.append("packageName=").append(mEditorInfo.packageName).append("\n");
107             sb.append("inputType=");
108             EditorInfoUtil.dumpInputType(sb, mEditorInfo.inputType);
109             sb.append("\n");
110             sb.append("imeOptions=");
111             EditorInfoUtil.dumpImeOptions(sb, mEditorInfo.imeOptions);
112         }
113 
114         mDebugMessageCallback.onMessageChanged(sb.toString());
115     }
116 
cloneEditorInfo(EditorInfo original)117     private static EditorInfo cloneEditorInfo(EditorInfo original) {
118         Parcel parcel = null;
119         try {
120             parcel = Parcel.obtain();
121             original.writeToParcel(parcel, 0);
122             parcel.setDataPosition(0);
123             return EditorInfo.CREATOR.createFromParcel(parcel);
124         } finally {
125             if (parcel != null) {
126                 parcel.recycle();
127             }
128         }
129     }
130 }
131