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