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.android.apps.inputmethod.simpleime.ims; 18 19 import android.content.res.Configuration; 20 import android.inputmethodservice.InputMethodService; 21 import android.util.Log; 22 import android.view.inputmethod.EditorInfo; 23 24 import androidx.annotation.IntDef; 25 import androidx.annotation.NonNull; 26 import androidx.annotation.Nullable; 27 28 import java.lang.annotation.Retention; 29 import java.lang.annotation.RetentionPolicy; 30 import java.lang.ref.WeakReference; 31 import java.util.concurrent.CountDownLatch; 32 33 /** Wrapper of {@link InputMethodService} to expose interfaces for testing purpose. */ 34 public class InputMethodServiceWrapper extends InputMethodService { 35 36 private static final String TAG = "InputMethodServiceWrapper"; 37 38 /** Last created instance of this wrapper. */ 39 @NonNull 40 private static WeakReference<InputMethodServiceWrapper> sInstance = new WeakReference<>(null); 41 42 /** IME show event ({@link #onStartInputView}). */ 43 public static final int EVENT_SHOW = 0; 44 45 /** IME hide event ({@link #onFinishInputView}). */ 46 public static final int EVENT_HIDE = 1; 47 48 /** IME configuration change event ({@link #onConfigurationChanged}). */ 49 public static final int EVENT_CONFIG = 2; 50 51 /** The type of event that can be waited with a latch. */ 52 @IntDef(value = { 53 EVENT_SHOW, 54 EVENT_HIDE, 55 EVENT_CONFIG, 56 }) 57 @Retention(RetentionPolicy.SOURCE) 58 public @interface Event {} 59 60 /** The IME event type that the current latch, if any, waits on. */ 61 @Event 62 private int mLatchEvent; 63 64 private boolean mInputViewStarted; 65 66 /** 67 * @see #setCountDownLatchForTesting 68 */ 69 @Nullable 70 private CountDownLatch mCountDownLatch; 71 72 @Override onCreate()73 public void onCreate() { 74 Log.i(TAG, "onCreate()"); 75 super.onCreate(); 76 sInstance = new WeakReference<>(this); 77 } 78 79 @Override onStartInput(EditorInfo info, boolean restarting)80 public void onStartInput(EditorInfo info, boolean restarting) { 81 Log.i(TAG, "onStartInput() editor=" + dumpEditorInfo(info) + ", restarting=" + restarting); 82 super.onStartInput(info, restarting); 83 } 84 85 @Override onFinishInput()86 public void onFinishInput() { 87 Log.i(TAG, "onFinishInput()"); 88 super.onFinishInput(); 89 } 90 91 @Override onStartInputView(EditorInfo info, boolean restarting)92 public void onStartInputView(EditorInfo info, boolean restarting) { 93 Log.i(TAG, "onStartInputView() editor=" + dumpEditorInfo(info) 94 + ", restarting=" + restarting); 95 super.onStartInputView(info, restarting); 96 mInputViewStarted = true; 97 if (mCountDownLatch != null && mLatchEvent == EVENT_SHOW) { 98 mCountDownLatch.countDown(); 99 } 100 } 101 102 @Override onFinishInputView(boolean finishingInput)103 public void onFinishInputView(boolean finishingInput) { 104 Log.i(TAG, "onFinishInputView()"); 105 super.onFinishInputView(finishingInput); 106 mInputViewStarted = false; 107 108 if (mCountDownLatch != null && mLatchEvent == EVENT_HIDE) { 109 mCountDownLatch.countDown(); 110 } 111 } 112 113 @Override onConfigurationChanged(Configuration newConfig)114 public void onConfigurationChanged(Configuration newConfig) { 115 Log.i(TAG, "onConfigurationChanged() " + newConfig); 116 super.onConfigurationChanged(newConfig); 117 118 if (mCountDownLatch != null && mLatchEvent == EVENT_CONFIG) { 119 mCountDownLatch.countDown(); 120 } 121 } 122 getCurrentInputViewStarted()123 public boolean getCurrentInputViewStarted() { 124 return mInputViewStarted; 125 } 126 127 /** 128 * Sets the latch used to wait for the IME event. 129 * 130 * @param latch the latch to wait on. 131 * @param latchEvent the event to set the latch on. 132 */ setCountDownLatchForTesting(@ullable CountDownLatch latch, @Event int latchEvent)133 public void setCountDownLatchForTesting(@Nullable CountDownLatch latch, @Event int latchEvent) { 134 mCountDownLatch = latch; 135 mLatchEvent = latchEvent; 136 } 137 138 /** Gets the last created instance of this wrapper, if available. */ 139 @Nullable getInstance()140 public static InputMethodServiceWrapper getInstance() { 141 return sInstance.get(); 142 } 143 144 /** 145 * Gets the string representation of the IME event that is being waited on. 146 * 147 * @param eventType the IME event type. 148 */ 149 @NonNull eventToString(@vent int eventType)150 public static String eventToString(@Event int eventType) { 151 return switch (eventType) { 152 case EVENT_SHOW -> "onStartInputView"; 153 case EVENT_HIDE -> "onFinishInputView"; 154 case EVENT_CONFIG -> "onConfigurationChanged"; 155 default -> "unknownEvent"; 156 }; 157 } 158 159 @NonNull dumpEditorInfo(EditorInfo info)160 private String dumpEditorInfo(EditorInfo info) { 161 if (info == null) { 162 return "null"; 163 } 164 return "EditorInfo{packageName=" + info.packageName 165 + " fieldId=" + info.fieldId 166 + " hintText=" + info.hintText 167 + " privateImeOptions=" + info.privateImeOptions 168 + "}"; 169 } 170 } 171