OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.content.browser.input; |
| 6 |
| 7 import static org.junit.Assert.assertFalse; |
| 8 import static org.junit.Assert.assertNotNull; |
| 9 import static org.junit.Assert.assertNull; |
| 10 import static org.junit.Assert.assertTrue; |
| 11 import static org.junit.Assert.fail; |
| 12 import static org.mockito.ArgumentMatchers.any; |
| 13 import static org.mockito.Mockito.doAnswer; |
| 14 import static org.mockito.Mockito.inOrder; |
| 15 import static org.mockito.Mockito.when; |
| 16 |
| 17 import android.content.Context; |
| 18 import android.os.Handler; |
| 19 import android.view.View; |
| 20 import android.view.inputmethod.EditorInfo; |
| 21 import android.view.inputmethod.InputConnection; |
| 22 import android.view.inputmethod.InputMethodManager; |
| 23 |
| 24 import org.chromium.base.ThreadUtils; |
| 25 import org.chromium.base.test.util.Feature; |
| 26 import org.chromium.testing.local.LocalRobolectricTestRunner; |
| 27 import org.junit.Before; |
| 28 import org.junit.Test; |
| 29 import org.junit.runner.RunWith; |
| 30 import org.mockito.InOrder; |
| 31 import org.mockito.Mock; |
| 32 import org.mockito.Mockito; |
| 33 import org.mockito.MockitoAnnotations; |
| 34 import org.mockito.invocation.InvocationOnMock; |
| 35 import org.mockito.stubbing.Answer; |
| 36 import org.robolectric.Robolectric; |
| 37 import org.robolectric.annotation.Config; |
| 38 import org.robolectric.internal.ShadowExtractor; |
| 39 import org.robolectric.shadows.ShadowLooper; |
| 40 |
| 41 import java.util.concurrent.Callable; |
| 42 |
| 43 /** |
| 44 * Unit tests for {@ThreadedInputConnectionFactory}. |
| 45 */ |
| 46 @RunWith(LocalRobolectricTestRunner.class) |
| 47 @Config(manifest = Config.NONE) |
| 48 public class ThreadedInputConnectionFactoryTest { |
| 49 |
| 50 /** |
| 51 * A testable version of ThreadedInputConnectionFactory. |
| 52 */ |
| 53 private class TestFactory extends ThreadedInputConnectionFactory { |
| 54 |
| 55 private boolean mSucceeded; |
| 56 private boolean mFailed; |
| 57 |
| 58 TestFactory(InputMethodManagerWrapper inputMethodManagerWrapper) { |
| 59 super(inputMethodManagerWrapper); |
| 60 } |
| 61 |
| 62 @Override |
| 63 protected Handler createHandler() { |
| 64 mImeHandler = super.createHandler(); |
| 65 mImeShadowLooper = (ShadowLooper) ShadowExtractor.extract(mImeHandle
r.getLooper()); |
| 66 return mImeHandler; |
| 67 } |
| 68 |
| 69 @Override |
| 70 protected ThreadedInputConnectionProxyView createProxyView(Handler handl
er, |
| 71 View containerView) { |
| 72 return mProxyView; |
| 73 } |
| 74 |
| 75 @Override |
| 76 protected InputMethodUma createInputMethodUma() { |
| 77 return null; |
| 78 } |
| 79 |
| 80 @Override |
| 81 protected void onRegisterProxyViewSuccess() { |
| 82 mSucceeded = true; |
| 83 } |
| 84 |
| 85 @Override |
| 86 protected void onRegisterProxyViewFailure() { |
| 87 mFailed = true; |
| 88 } |
| 89 |
| 90 public boolean hasFailed() { |
| 91 return mFailed; |
| 92 } |
| 93 public boolean hasSucceeded() { |
| 94 return mSucceeded; |
| 95 } |
| 96 } |
| 97 |
| 98 @Mock |
| 99 private ImeAdapter mImeAdapter; |
| 100 @Mock |
| 101 private View mContainerView; |
| 102 @Mock |
| 103 private ThreadedInputConnectionProxyView mProxyView; |
| 104 @Mock |
| 105 private InputMethodManager mInputMethodManager; |
| 106 @Mock |
| 107 private Context mContext; |
| 108 |
| 109 private EditorInfo mEditorInfo; |
| 110 private Handler mImeHandler; |
| 111 private Handler mUiHandler; |
| 112 private ShadowLooper mImeShadowLooper; |
| 113 private TestFactory mFactory; |
| 114 private InputConnection mInputConnection; |
| 115 private InOrder mInOrder; |
| 116 private boolean mWindowFocusChanged; |
| 117 |
| 118 @Before |
| 119 public void setUp() throws Exception { |
| 120 MockitoAnnotations.initMocks(this); |
| 121 |
| 122 mEditorInfo = new EditorInfo(); |
| 123 mUiHandler = new Handler(); |
| 124 |
| 125 mContext = Mockito.mock(Context.class); |
| 126 mContainerView = Mockito.mock(View.class); |
| 127 mImeAdapter = Mockito.mock(ImeAdapter.class); |
| 128 mInputMethodManager = Mockito.mock(InputMethodManager.class); |
| 129 |
| 130 mFactory = new TestFactory(new InputMethodManagerWrapper(mContext)); |
| 131 |
| 132 when(mContext.getSystemService(Context.INPUT_METHOD_SERVICE)) |
| 133 .thenReturn(mInputMethodManager); |
| 134 when(mContainerView.getContext()).thenReturn(mContext); |
| 135 when(mContainerView.getHandler()).thenReturn(mUiHandler); |
| 136 when(mContainerView.hasFocus()).thenReturn(true); |
| 137 when(mContainerView.hasWindowFocus()).thenReturn(true); |
| 138 |
| 139 mProxyView = Mockito.mock(ThreadedInputConnectionProxyView.class); |
| 140 when(mProxyView.getContext()).thenReturn(mContext); |
| 141 when(mProxyView.requestFocus()).thenReturn(true); |
| 142 when(mProxyView.getHandler()).thenReturn(mImeHandler); |
| 143 doAnswer(new Answer<Void>() { |
| 144 @Override |
| 145 public Void answer(InvocationOnMock invocation) throws Throwable { |
| 146 mWindowFocusChanged = true; |
| 147 return null; |
| 148 } |
| 149 }).when(mProxyView).onWindowFocusChanged(true); |
| 150 final Callable<InputConnection> callable = new Callable<InputConnection>
() { |
| 151 @Override |
| 152 public InputConnection call() throws Exception { |
| 153 return mFactory.initializeAndGet( |
| 154 mContainerView, mImeAdapter, 1, 0, 0, 0, mEditorInfo); |
| 155 } |
| 156 }; |
| 157 when(mProxyView.onCreateInputConnection(any(EditorInfo.class))).thenAnsw
er( |
| 158 new Answer<InputConnection>() { |
| 159 @Override |
| 160 public InputConnection answer(InvocationOnMock invocation) t
hrows Throwable { |
| 161 return ThreadUtils.runOnUiThreadBlockingNoException(call
able); |
| 162 } |
| 163 }); |
| 164 |
| 165 when(mInputMethodManager.isActive(mContainerView)).thenAnswer(new Answer
<Boolean>() { |
| 166 private int mCount; |
| 167 |
| 168 @Override |
| 169 public Boolean answer(InvocationOnMock invocation) throws Throwable
{ |
| 170 mCount++; |
| 171 if (mCount == 1 && mWindowFocusChanged) { |
| 172 mInputConnection = mProxyView.onCreateInputConnection(mEdito
rInfo); |
| 173 return false; |
| 174 } else if (mCount == 2) { |
| 175 return true; |
| 176 } |
| 177 fail(); |
| 178 return false; |
| 179 } |
| 180 }); |
| 181 when(mInputMethodManager.isActive(mProxyView)).thenReturn(true); |
| 182 |
| 183 mInOrder = inOrder(mImeAdapter, mInputMethodManager, mContainerView, mPr
oxyView); |
| 184 } |
| 185 |
| 186 private void activateInput() { |
| 187 mUiHandler.post(new Runnable() { |
| 188 @Override |
| 189 public void run() { |
| 190 assertNull(mFactory.initializeAndGet( |
| 191 mContainerView, mImeAdapter, 1, 0, 0, 0, mEditorInfo)); |
| 192 } |
| 193 }); |
| 194 } |
| 195 |
| 196 private void runOneUiTask() { |
| 197 assertTrue(Robolectric.getForegroundThreadScheduler().runOneTask()); |
| 198 } |
| 199 |
| 200 @Test |
| 201 @Feature({"TextInput"}) |
| 202 public void testCreateInputConnection_Success() { |
| 203 // Pause all the loopers. |
| 204 Robolectric.getForegroundThreadScheduler().pause(); |
| 205 mImeShadowLooper.pause(); |
| 206 |
| 207 activateInput(); |
| 208 |
| 209 // The first onCreateInputConnection(). |
| 210 runOneUiTask(); |
| 211 mInOrder.verify(mContainerView).hasFocus(); |
| 212 mInOrder.verify(mContainerView).hasWindowFocus(); |
| 213 mInOrder.verify(mProxyView).requestFocus(); |
| 214 mInOrder.verify(mContainerView).getHandler(); |
| 215 mInOrder.verifyNoMoreInteractions(); |
| 216 assertNull(mInputConnection); |
| 217 |
| 218 // The second onCreateInputConnection(). |
| 219 runOneUiTask(); |
| 220 mInOrder.verify(mProxyView).onWindowFocusChanged(true); |
| 221 mInOrder.verify(mInputMethodManager).isActive(mContainerView); |
| 222 mInOrder.verify(mProxyView).onCreateInputConnection(any(EditorInfo.class
)); |
| 223 mInOrder.verify(mContainerView).getContext(); // BaseInputConnection#<i
nit> |
| 224 mInOrder.verifyNoMoreInteractions(); |
| 225 assertNotNull(mInputConnection); |
| 226 assertTrue(ThreadedInputConnection.class.isInstance(mInputConnection)); |
| 227 |
| 228 // Verification process. |
| 229 mImeShadowLooper.runOneTask(); |
| 230 runOneUiTask(); |
| 231 |
| 232 mInOrder.verify(mInputMethodManager).isActive(mProxyView); |
| 233 mInOrder.verifyNoMoreInteractions(); |
| 234 |
| 235 assertTrue(mFactory.hasSucceeded()); |
| 236 assertFalse(mFactory.hasFailed()); |
| 237 } |
| 238 |
| 239 @Test |
| 240 @Feature({"TextInput"}) |
| 241 public void testCreateInputConnection_WindowFocusLostOnFirstLoop() { |
| 242 // Somehow input was activated right after window focus was lost. |
| 243 when(mContainerView.hasWindowFocus()).thenReturn(false); |
| 244 |
| 245 // Pause all the loopers. |
| 246 Robolectric.getForegroundThreadScheduler().pause(); |
| 247 mImeShadowLooper.pause(); |
| 248 |
| 249 activateInput(); |
| 250 |
| 251 // The first onCreateInputConnection(). |
| 252 runOneUiTask(); |
| 253 mInOrder.verify(mContainerView).hasFocus(); |
| 254 mInOrder.verify(mContainerView).hasWindowFocus(); |
| 255 mInOrder.verifyNoMoreInteractions(); |
| 256 assertNull(mInputConnection); |
| 257 assertFalse(mFactory.hasSucceeded()); |
| 258 assertFalse(mFactory.hasFailed()); |
| 259 } |
| 260 |
| 261 @Test |
| 262 @Feature({"TextInput"}) |
| 263 public void testCreateInputConnection_WindowFocusLostOnSecondLoop() { |
| 264 // Pause all the loopers. |
| 265 Robolectric.getForegroundThreadScheduler().pause(); |
| 266 mImeShadowLooper.pause(); |
| 267 |
| 268 activateInput(); |
| 269 |
| 270 // The first onCreateInputConnection(). |
| 271 runOneUiTask(); |
| 272 mInOrder.verify(mContainerView).hasFocus(); |
| 273 mInOrder.verify(mContainerView).hasWindowFocus(); |
| 274 mInOrder.verify(mProxyView).requestFocus(); |
| 275 mInOrder.verify(mContainerView).getHandler(); |
| 276 mInOrder.verifyNoMoreInteractions(); |
| 277 assertNull(mInputConnection); |
| 278 |
| 279 // Now window focus was lost before the second onCreateInputConnection()
. |
| 280 mFactory.onWindowFocusChanged(false); |
| 281 |
| 282 // The second onCreateInputConnection() gets ignored. |
| 283 runOneUiTask(); |
| 284 mInOrder.verify(mProxyView).onOriginalViewWindowFocusChanged(false); |
| 285 mInOrder.verifyNoMoreInteractions(); |
| 286 assertNull(mInputConnection); |
| 287 assertFalse(mFactory.hasSucceeded()); |
| 288 assertFalse(mFactory.hasFailed()); |
| 289 } |
| 290 } |
OLD | NEW |