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 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
28 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
29 
30 import android.content.Intent;
31 import android.graphics.Bitmap;
32 import android.inputmethodservice.InputMethodService;
33 import android.inputmethodservice.cts.DeviceEvent;
34 import android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType;
35 import android.inputmethodservice.cts.ime.ImeCommandReceiver.ImeCommandCallbacks;
36 import android.os.Bundle;
37 import android.os.Process;
38 import android.util.Log;
39 import android.util.TypedValue;
40 import android.view.Gravity;
41 import android.view.View;
42 import android.view.inputmethod.EditorInfo;
43 import android.view.inputmethod.InputConnection;
44 import android.widget.FrameLayout;
45 import android.widget.ImageView;
46 import android.widget.LinearLayout;
47 import android.widget.TextView;
48 
49 import java.util.function.Consumer;
50 
51 /**
52  * Base class to create test {@link InputMethodService}.
53  */
54 public abstract class CtsBaseInputMethod extends InputMethodService implements ImeCommandCallbacks {
55 
56     protected static final boolean DEBUG = false;
57 
58     public static final String EDITOR_INFO_KEY_REPLY_USER_HANDLE_SESSION_ID =
59             "android.inputmethodservice.cts.ime.ReplyUserHandleSessionId";
60 
61     public static final String ACTION_KEY_REPLY_USER_HANDLE =
62             "android.inputmethodservice.cts.ime.ReplyUserHandle";
63 
64     public static final String BUNDLE_KEY_REPLY_USER_HANDLE =
65             "android.inputmethodservice.cts.ime.ReplyUserHandle";
66 
67     public static final String BUNDLE_KEY_REPLY_USER_HANDLE_SESSION_ID =
68             "android.inputmethodservice.cts.ime.ReplyUserHandleSessionId";
69 
70     private final ImeCommandReceiver<CtsBaseInputMethod> mImeCommandReceiver =
71             new ImeCommandReceiver<>();
72     private String mLogTag;
73 
74     @Override
onCreate()75     public void onCreate() {
76         mLogTag = getClass().getSimpleName();
77         if (DEBUG) {
78             Log.d(mLogTag, "onCreate:");
79         }
80         sendEvent(ON_CREATE);
81 
82         super.onCreate();
83 
84         mImeCommandReceiver.register(this /* ime */);
85     }
86 
createInputViewInternal(Watermark watermark)87     protected View createInputViewInternal(Watermark watermark) {
88         final FrameLayout layout = new FrameLayout(this);
89         layout.setForegroundGravity(Gravity.CENTER);
90 
91         final TextView textView = new TextView(this);
92         textView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
93         textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
94         textView.setGravity(Gravity.CENTER);
95         textView.setText(getClass().getName());
96         textView.setBackgroundColor(0xffffaa99);
97         layout.addView(textView);
98 
99         final ImageView imageView = new ImageView(this);
100         final Bitmap bitmap = watermark.toBitmap();
101         imageView.setImageBitmap(bitmap);
102         layout.addView(imageView, new FrameLayout.LayoutParams(
103                 bitmap.getWidth(), bitmap.getHeight(), Gravity.CENTER));
104         return layout;
105     }
106 
107     @Override
onBindInput()108     public void onBindInput() {
109         if (DEBUG) {
110             Log.d(mLogTag, "onBindInput");
111         }
112         sendEvent(ON_BIND_INPUT);
113         super.onBindInput();
114     }
115 
116     @Override
onEvaluateFullscreenMode()117     public boolean onEvaluateFullscreenMode() {
118         // Opt-out the fullscreen mode regardless of the existence of the hardware keyboard
119         // and screen rotation.  Otherwise test scenarios become unpredictable.
120         return false;
121     }
122 
123     @Override
onEvaluateInputViewShown()124     public boolean onEvaluateInputViewShown() {
125         // Always returns true regardless of the existence of the hardware keyboard.
126         // Otherwise test scenarios become unpredictable.
127         return true;
128     }
129 
130     @Override
onShowInputRequested(int flags, boolean configChange)131     public boolean onShowInputRequested(int flags, boolean configChange) {
132         // Always returns true regardless of the existence of the hardware keyboard.
133         // Otherwise test scenarios become unpredictable.
134         return true;
135     }
136 
137     @Override
onStartInput(EditorInfo editorInfo, boolean restarting)138     public void onStartInput(EditorInfo editorInfo, boolean restarting) {
139         if (DEBUG) {
140             Log.d(mLogTag, "onStartInput:"
141                     + " editorInfo=" + editorInfo
142                     + " restarting=" + restarting);
143         }
144         sendEvent(ON_START_INPUT, editorInfo, restarting);
145 
146         super.onStartInput(editorInfo, restarting);
147 
148         if (editorInfo.extras != null) {
149             final String sessionKey =
150                     editorInfo.extras.getString(EDITOR_INFO_KEY_REPLY_USER_HANDLE_SESSION_ID, null);
151             if (sessionKey != null) {
152                 final Bundle bundle = new Bundle();
153                 bundle.putString(BUNDLE_KEY_REPLY_USER_HANDLE_SESSION_ID, sessionKey);
154                 bundle.putParcelable(BUNDLE_KEY_REPLY_USER_HANDLE, Process.myUserHandle());
155                 getCurrentInputConnection().performPrivateCommand(
156                         ACTION_KEY_REPLY_USER_HANDLE, bundle);
157             }
158         }
159     }
160 
161     @Override
onStartInputView(EditorInfo editorInfo, boolean restarting)162     public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
163         if (DEBUG) {
164             Log.d(mLogTag, "onStartInputView:"
165                     + " editorInfo=" + editorInfo
166                     + " restarting=" + restarting);
167         }
168         sendEvent(ON_START_INPUT_VIEW, editorInfo, restarting);
169 
170         super.onStartInputView(editorInfo, restarting);
171     }
172 
173     @Override
onUnbindInput()174     public void onUnbindInput() {
175         super.onUnbindInput();
176         if (DEBUG) {
177             Log.d(mLogTag, "onUnbindInput");
178         }
179         sendEvent(ON_UNBIND_INPUT);
180     }
181 
182     @Override
onFinishInputView(boolean finishingInput)183     public void onFinishInputView(boolean finishingInput) {
184         if (DEBUG) {
185             Log.d(mLogTag, "onFinishInputView: finishingInput=" + finishingInput);
186         }
187         sendEvent(ON_FINISH_INPUT_VIEW, finishingInput);
188 
189         super.onFinishInputView(finishingInput);
190     }
191 
192     @Override
onFinishInput()193     public void onFinishInput() {
194         if (DEBUG) {
195             Log.d(mLogTag, "onFinishInput:");
196         }
197         sendEvent(ON_FINISH_INPUT);
198 
199         super.onFinishInput();
200     }
201 
202     @Override
onDestroy()203     public void onDestroy() {
204         if (DEBUG) {
205             Log.d(mLogTag, "onDestroy:");
206         }
207         sendEvent(ON_DESTROY);
208 
209         super.onDestroy();
210 
211         unregisterReceiver(mImeCommandReceiver);
212     }
213 
214     //
215     // Implementations of {@link ImeCommandCallbacks}.
216     //
217 
218     @Override
commandCommitText(CharSequence text, int newCursorPosition)219     public void commandCommitText(CharSequence text, int newCursorPosition) {
220         executeOnInputConnection(ic -> {
221             // TODO: Log the return value of {@link InputConnection#commitText(CharSequence,int)}.
222             ic.commitText(text, newCursorPosition);
223         });
224     }
225 
226     @Override
commandSwitchInputMethod(String imeId)227     public void commandSwitchInputMethod(String imeId) {
228         switchInputMethod(imeId);
229     }
230 
231     @Override
commandRequestHideSelf(int flags)232     public void commandRequestHideSelf(int flags) {
233         requestHideSelf(flags);
234     }
235 
executeOnInputConnection(Consumer<InputConnection> consumer)236     private void executeOnInputConnection(Consumer<InputConnection> consumer) {
237         final InputConnection ic = getCurrentInputConnection();
238         // TODO: Check and log whether {@code ic} is null or equals to
239         // {@link #getCurrentInputBindin().getConnection()}.
240         if (ic != null) {
241             consumer.accept(ic);
242         }
243     }
244 
sendEvent(DeviceEventType type, Object... args)245     private void sendEvent(DeviceEventType type, Object... args) {
246         final String sender = getClass().getName();
247         final Intent intent = DeviceEvent.newDeviceEventIntent(sender, type);
248         // TODO: Send arbitrary {@code args} in {@code intent}.
249         sendBroadcast(intent);
250     }
251 }
252