1 /*
2  * Copyright (C) 2017 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 android.inputmethodservice.cts.ime;
18 
19 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_BIND_INPUT;
20 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_CREATE;
21 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_DESTROY;
22 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT;
23 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_FINISH_INPUT_VIEW;
24 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT;
25 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_START_INPUT_VIEW;
26 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.ON_UNBIND_INPUT;
27 
28 import android.content.Intent;
29 import android.inputmethodservice.InputMethodService;
30 import android.inputmethodservice.cts.DeviceEvent;
31 import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
32 import android.inputmethodservice.cts.ime.ImeCommandReceiver.ImeCommandCallbacks;
33 import android.os.Bundle;
34 import android.os.Process;
35 import android.util.Log;
36 import android.view.inputmethod.EditorInfo;
37 import android.view.inputmethod.InputConnection;
38 
39 import java.util.function.Consumer;
40 
41 /**
42  * Base class to create test {@link InputMethodService}.
43  */
44 public abstract class CtsBaseInputMethod extends InputMethodService implements ImeCommandCallbacks {
45 
46     protected static final boolean DEBUG = false;
47 
48     public static final String EDITOR_INFO_KEY_REPLY_USER_HANDLE_SESSION_ID =
49             "android.inputmethodservice.cts.ime.ReplyUserHandleSessionId";
50 
51     public static final String ACTION_KEY_REPLY_USER_HANDLE =
52             "android.inputmethodservice.cts.ime.ReplyUserHandle";
53 
54     public static final String BUNDLE_KEY_REPLY_USER_HANDLE =
55             "android.inputmethodservice.cts.ime.ReplyUserHandle";
56 
57     public static final String BUNDLE_KEY_REPLY_USER_HANDLE_SESSION_ID =
58             "android.inputmethodservice.cts.ime.ReplyUserHandleSessionId";
59 
60     private final ImeCommandReceiver<CtsBaseInputMethod> mImeCommandReceiver =
61             new ImeCommandReceiver<>();
62     private String mLogTag;
63 
64     @Override
onCreate()65     public void onCreate() {
66         mLogTag = getClass().getSimpleName();
67         if (DEBUG) {
68             Log.d(mLogTag, "onCreate:");
69         }
70         sendEvent(ON_CREATE);
71 
72         super.onCreate();
73 
74         mImeCommandReceiver.register(this /* ime */);
75     }
76 
77     @Override
onBindInput()78     public void onBindInput() {
79         if (DEBUG) {
80             Log.d(mLogTag, "onBindInput");
81         }
82         sendEvent(ON_BIND_INPUT);
83         super.onBindInput();
84     }
85 
86     @Override
onStartInput(EditorInfo editorInfo, boolean restarting)87     public void onStartInput(EditorInfo editorInfo, boolean restarting) {
88         if (DEBUG) {
89             Log.d(mLogTag, "onStartInput:"
90                     + " editorInfo=" + editorInfo
91                     + " restarting=" + restarting);
92         }
93         sendEvent(ON_START_INPUT, editorInfo, restarting);
94 
95         super.onStartInput(editorInfo, restarting);
96 
97         if (editorInfo.extras != null) {
98             final String sessionKey =
99                     editorInfo.extras.getString(EDITOR_INFO_KEY_REPLY_USER_HANDLE_SESSION_ID, null);
100             if (sessionKey != null) {
101                 final Bundle bundle = new Bundle();
102                 bundle.putString(BUNDLE_KEY_REPLY_USER_HANDLE_SESSION_ID, sessionKey);
103                 bundle.putParcelable(BUNDLE_KEY_REPLY_USER_HANDLE, Process.myUserHandle());
104                 getCurrentInputConnection().performPrivateCommand(
105                         ACTION_KEY_REPLY_USER_HANDLE, bundle);
106             }
107         }
108     }
109 
110     @Override
onStartInputView(EditorInfo editorInfo, boolean restarting)111     public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
112         if (DEBUG) {
113             Log.d(mLogTag, "onStartInputView:"
114                     + " editorInfo=" + editorInfo
115                     + " restarting=" + restarting);
116         }
117         sendEvent(ON_START_INPUT_VIEW, editorInfo, restarting);
118 
119         super.onStartInputView(editorInfo, restarting);
120     }
121 
122     @Override
onUnbindInput()123     public void onUnbindInput() {
124         super.onUnbindInput();
125         if (DEBUG) {
126             Log.d(mLogTag, "onUnbindInput");
127         }
128         sendEvent(ON_UNBIND_INPUT);
129     }
130 
131     @Override
onFinishInputView(boolean finishingInput)132     public void onFinishInputView(boolean finishingInput) {
133         if (DEBUG) {
134             Log.d(mLogTag, "onFinishInputView: finishingInput=" + finishingInput);
135         }
136         sendEvent(ON_FINISH_INPUT_VIEW, finishingInput);
137 
138         super.onFinishInputView(finishingInput);
139     }
140 
141     @Override
onFinishInput()142     public void onFinishInput() {
143         if (DEBUG) {
144             Log.d(mLogTag, "onFinishInput:");
145         }
146         sendEvent(ON_FINISH_INPUT);
147 
148         super.onFinishInput();
149     }
150 
151     @Override
onDestroy()152     public void onDestroy() {
153         if (DEBUG) {
154             Log.d(mLogTag, "onDestroy:");
155         }
156         sendEvent(ON_DESTROY);
157 
158         super.onDestroy();
159 
160         unregisterReceiver(mImeCommandReceiver);
161     }
162 
163     //
164     // Implementations of {@link ImeCommandCallbacks}.
165     //
166 
167     @Override
commandCommitText(CharSequence text, int newCursorPosition)168     public void commandCommitText(CharSequence text, int newCursorPosition) {
169         executeOnInputConnection(ic -> {
170             // TODO: Log the return value of {@link InputConnection#commitText(CharSequence,int)}.
171             ic.commitText(text, newCursorPosition);
172         });
173     }
174 
175     @Override
commandSwitchInputMethod(String imeId)176     public void commandSwitchInputMethod(String imeId) {
177         switchInputMethod(imeId);
178     }
179 
180     @Override
commandRequestHideSelf(int flags)181     public void commandRequestHideSelf(int flags) {
182         requestHideSelf(flags);
183     }
184 
executeOnInputConnection(Consumer<InputConnection> consumer)185     private void executeOnInputConnection(Consumer<InputConnection> consumer) {
186         final InputConnection ic = getCurrentInputConnection();
187         // TODO: Check and log whether {@code ic} is null or equals to
188         // {@link #getCurrentInputBindin().getConnection()}.
189         if (ic != null) {
190             consumer.accept(ic);
191         }
192     }
193 
sendEvent(DeviceEventType type, Object... args)194     private void sendEvent(DeviceEventType type, Object... args) {
195         final String sender = getClass().getName();
196         final Intent intent = DeviceEvent.newDeviceEventIntent(sender, type);
197         // TODO: Send arbitrary {@code args} in {@code intent}.
198         sendBroadcast(intent);
199     }
200 }
201