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.server.inputmethod; 18 19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; 20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; 23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 24 25 import static org.mockito.ArgumentMatchers.any; 26 import static org.mockito.ArgumentMatchers.anyBoolean; 27 import static org.mockito.ArgumentMatchers.anyInt; 28 import static org.mockito.ArgumentMatchers.anyList; 29 import static org.mockito.ArgumentMatchers.anyLong; 30 import static org.mockito.ArgumentMatchers.anyString; 31 import static org.mockito.ArgumentMatchers.notNull; 32 import static org.mockito.Mockito.eq; 33 import static org.mockito.Mockito.mock; 34 import static org.mockito.Mockito.times; 35 import static org.mockito.Mockito.verify; 36 import static org.mockito.Mockito.when; 37 38 import android.app.ActivityManagerInternal; 39 import android.content.BroadcastReceiver; 40 import android.content.Context; 41 import android.content.IntentFilter; 42 import android.content.pm.PackageManager; 43 import android.content.pm.PackageManagerInternal; 44 import android.content.res.Configuration; 45 import android.hardware.input.IInputManager; 46 import android.hardware.input.InputManagerGlobal; 47 import android.os.Binder; 48 import android.os.Handler; 49 import android.os.IBinder; 50 import android.os.Process; 51 import android.os.RemoteException; 52 import android.os.ServiceManager; 53 import android.util.ArraySet; 54 import android.view.InputChannel; 55 import android.view.inputmethod.EditorInfo; 56 import android.view.inputmethod.ImeTracker; 57 import android.window.ImeOnBackInvokedDispatcher; 58 59 import androidx.test.platform.app.InstrumentationRegistry; 60 61 import com.android.internal.compat.IPlatformCompat; 62 import com.android.internal.inputmethod.DirectBootAwareness; 63 import com.android.internal.inputmethod.IInputMethod; 64 import com.android.internal.inputmethod.IInputMethodClient; 65 import com.android.internal.inputmethod.IInputMethodSession; 66 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; 67 import com.android.internal.inputmethod.IRemoteInputConnection; 68 import com.android.internal.inputmethod.InputBindResult; 69 import com.android.internal.protolog.IProtoLogConfigurationService; 70 import com.android.internal.view.IInputMethodManager; 71 import com.android.server.LocalServices; 72 import com.android.server.ServiceThread; 73 import com.android.server.SystemService; 74 import com.android.server.input.InputManagerInternal; 75 import com.android.server.pm.UserManagerInternal; 76 import com.android.server.wm.ImeTargetVisibilityPolicy; 77 import com.android.server.wm.WindowManagerInternal; 78 79 import org.junit.After; 80 import org.junit.Before; 81 import org.junit.BeforeClass; 82 import org.mockito.Mock; 83 import org.mockito.MockitoSession; 84 import org.mockito.quality.Strictness; 85 86 /** Base class for testing {@link InputMethodManagerService}. */ 87 public class InputMethodManagerServiceTestBase { 88 private static final int NO_VERIFY_SHOW_FLAGS = -1; 89 90 protected static final String TEST_SELECTED_IME_ID = "test.ime"; 91 protected static final String TEST_EDITOR_PKG_NAME = "test.editor"; 92 protected static final String TEST_FOCUSED_WINDOW_NAME = "test.editor/activity"; 93 protected static final WindowManagerInternal.ImeTargetInfo TEST_IME_TARGET_INFO = 94 new WindowManagerInternal.ImeTargetInfo( 95 TEST_FOCUSED_WINDOW_NAME, 96 TEST_FOCUSED_WINDOW_NAME, 97 TEST_FOCUSED_WINDOW_NAME, 98 TEST_FOCUSED_WINDOW_NAME, 99 TEST_FOCUSED_WINDOW_NAME); 100 protected static final InputBindResult SUCCESS_WAITING_IME_BINDING_RESULT = 101 new InputBindResult( 102 InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, 103 null, 104 null, 105 null, 106 "0", 107 0, 108 false); 109 110 @Mock protected WindowManagerInternal mMockWindowManagerInternal; 111 @Mock protected ActivityManagerInternal mMockActivityManagerInternal; 112 @Mock protected PackageManagerInternal mMockPackageManagerInternal; 113 @Mock protected InputManagerInternal mMockInputManagerInternal; 114 @Mock protected UserManagerInternal mMockUserManagerInternal; 115 @Mock protected InputMethodBindingController mMockInputMethodBindingController; 116 @Mock protected IInputMethodClient mMockInputMethodClient; 117 @Mock protected IInputMethodSession mMockInputMethodSession; 118 @Mock protected IBinder mWindowToken; 119 @Mock protected IRemoteInputConnection mMockFallbackInputConnection; 120 @Mock protected IRemoteAccessibilityInputConnection mMockRemoteAccessibilityInputConnection; 121 @Mock protected ImeOnBackInvokedDispatcher mMockImeOnBackInvokedDispatcher; 122 @Mock protected IInputMethodManager.Stub mMockIInputMethodManager; 123 @Mock protected IPlatformCompat.Stub mMockIPlatformCompat; 124 @Mock protected IInputMethod mMockInputMethod; 125 @Mock protected IBinder mMockInputMethodBinder; 126 @Mock protected IInputManager mMockIInputManager; 127 @Mock protected ImeTargetVisibilityPolicy mMockImeTargetVisibilityPolicy; 128 @Mock protected IProtoLogConfigurationService.Stub mMockProtoLogConfigurationService; 129 130 protected Context mContext; 131 protected MockitoSession mMockingSession; 132 protected int mTargetSdkVersion; 133 protected int mUserId; 134 protected EditorInfo mEditorInfo; 135 protected IInputMethodInvoker mMockInputMethodInvoker; 136 protected InputMethodManagerService mInputMethodManagerService; 137 protected ServiceThread mServiceThread; 138 protected ServiceThread mIoThread; 139 protected boolean mIsLargeScreen; 140 private InputManagerGlobal.TestSession mInputManagerGlobalSession; 141 142 private final ArraySet<Class<?>> mRegisteredLocalServices = new ArraySet<>(); 143 addLocalServiceMock(Class<T> type, T service)144 protected <T> void addLocalServiceMock(Class<T> type, T service) { 145 mRegisteredLocalServices.add(type); 146 LocalServices.removeServiceForTest(type); 147 LocalServices.addService(type, service); 148 } 149 150 @BeforeClass setupClass()151 public static void setupClass() { 152 // Make sure DeviceConfig's lazy-initialized ContentProvider gets 153 // a real instance before we stub out all system services below. 154 // TODO(b/272229177): remove dependency on real ContentProvider 155 new InputMethodDeviceConfigs().destroy(); 156 } 157 158 @Before setUp()159 public void setUp() throws RemoteException { 160 mMockingSession = 161 mockitoSession() 162 .initMocks(this) 163 .strictness(Strictness.LENIENT) 164 .spyStatic(InputMethodUtils.class) 165 .mockStatic(ServiceManager.class) 166 .spyStatic(AdditionalSubtypeMapRepository.class) 167 .spyStatic(AdditionalSubtypeUtils.class) 168 .startMocking(); 169 170 mContext = spy(InstrumentationRegistry.getInstrumentation().getTargetContext()); 171 172 mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; 173 mIsLargeScreen = mContext.getResources().getConfiguration() 174 .isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); 175 mUserId = mContext.getUserId(); 176 mEditorInfo = new EditorInfo(); 177 mEditorInfo.packageName = TEST_EDITOR_PKG_NAME; 178 179 // Injecting and mocking local services. 180 addLocalServiceMock(WindowManagerInternal.class, mMockWindowManagerInternal); 181 addLocalServiceMock(ActivityManagerInternal.class, mMockActivityManagerInternal); 182 addLocalServiceMock(PackageManagerInternal.class, mMockPackageManagerInternal); 183 addLocalServiceMock(InputManagerInternal.class, mMockInputManagerInternal); 184 addLocalServiceMock(UserManagerInternal.class, mMockUserManagerInternal); 185 addLocalServiceMock(ImeTargetVisibilityPolicy.class, mMockImeTargetVisibilityPolicy); 186 187 doReturn(mMockIInputMethodManager) 188 .when(() -> ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE)); 189 doReturn(mMockIPlatformCompat) 190 .when(() -> ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); 191 doReturn(mMockProtoLogConfigurationService) 192 .when(() -> ServiceManager.getServiceOrThrow( 193 Context.PROTOLOG_CONFIGURATION_SERVICE)); 194 195 // Stubbing out context related methods to avoid the system holding strong references to 196 // InputMethodManagerService. 197 doNothing().when(mContext).enforceCallingPermission(anyString(), anyString()); 198 doNothing().when(mContext).sendBroadcastAsUser(any(), any()); 199 doReturn(null).when(mContext).registerReceiver(any(), any()); 200 doReturn(null).when(mContext).registerReceiver( 201 any(BroadcastReceiver.class), 202 any(IntentFilter.class), anyString(), any(Handler.class)); 203 doReturn(null) 204 .when(mContext) 205 .registerReceiverAsUser(any(), any(), any(), anyString(), any(), anyInt()); 206 207 // Injecting and mocked InputMethodBindingController and InputMethod. 208 mMockInputMethodInvoker = IInputMethodInvoker.create(mMockInputMethod); 209 mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mMockIInputManager); 210 when(mMockInputMethodBindingController.getUserId()).thenReturn(mUserId); 211 synchronized (ImfLock.class) { 212 when(mMockInputMethodBindingController.getCurMethod()) 213 .thenReturn(mMockInputMethodInvoker); 214 when(mMockInputMethodBindingController.bindCurrentMethod()) 215 .thenReturn(SUCCESS_WAITING_IME_BINDING_RESULT); 216 doNothing().when(mMockInputMethodBindingController).unbindCurrentMethod(); 217 when(mMockInputMethodBindingController.getSelectedMethodId()) 218 .thenReturn(TEST_SELECTED_IME_ID); 219 } 220 221 // Shuffling around all other initialization to make the test runnable. 222 when(mMockIInputManager.getInputDeviceIds()).thenReturn(new int[0]); 223 when(mMockIInputMethodManager.isImeTraceEnabled()).thenReturn(false); 224 when(mMockIPlatformCompat.isChangeEnabledByUid(anyLong(), anyInt())).thenReturn(true); 225 when(mMockUserManagerInternal.isUserRunning(anyInt())).thenReturn(true); 226 when(mMockUserManagerInternal.getProfileIds(anyInt(), anyBoolean())) 227 .thenReturn(new int[] {0}); 228 when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[] {0}); 229 when(mMockActivityManagerInternal.isSystemReady()).thenReturn(true); 230 when(mMockActivityManagerInternal.getCurrentUserId()).thenReturn(mUserId); 231 when(mMockPackageManagerInternal.getPackageUid(anyString(), anyLong(), anyInt())) 232 .thenReturn(Binder.getCallingUid()); 233 when(mMockPackageManagerInternal.isSameApp(anyString(), anyLong(), anyInt(), anyInt())) 234 .thenReturn(true); 235 when(mMockWindowManagerInternal.onToggleImeRequested(anyBoolean(), any(), any(), anyInt())) 236 .thenReturn(TEST_IME_TARGET_INFO); 237 when(mMockInputMethodClient.asBinder()).thenReturn(mMockInputMethodBinder); 238 239 // This changes the real IME component state. Not appropriate to do in tests. 240 doNothing().when(() -> InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( 241 any(PackageManager.class), anyList())); 242 243 // The background writer thread in AdditionalSubtypeMapRepository should be stubbed out. 244 doNothing().when(AdditionalSubtypeMapRepository::startWriterThread); 245 doReturn(AdditionalSubtypeMap.EMPTY_MAP).when(() -> AdditionalSubtypeUtils.load(anyInt())); 246 247 mServiceThread = 248 new ServiceThread( 249 "immstest1", 250 Process.THREAD_PRIORITY_FOREGROUND, 251 true /* allowIo */); 252 mServiceThread.start(); 253 mIoThread = 254 new ServiceThread( 255 "immstest2", 256 Process.THREAD_PRIORITY_FOREGROUND, 257 true /* allowIo */); 258 mIoThread.start(); 259 260 final var ioHandler = spy(Handler.createAsync(mIoThread.getLooper())); 261 doReturn(true).when(ioHandler).post(any()); 262 263 mInputMethodManagerService = new InputMethodManagerService(mContext, 264 InputMethodManagerService.shouldEnableConcurrentMultiUserMode(mContext), 265 mServiceThread.getLooper(), ioHandler, 266 unusedUserId -> mMockInputMethodBindingController); 267 spyOn(mInputMethodManagerService); 268 269 synchronized (ImfLock.class) { 270 doReturn(true).when(mInputMethodManagerService).setImeVisibilityOnFocusedWindowClient( 271 anyBoolean(), any(UserData.class), any(ImeTracker.Token.class)); 272 } 273 274 // Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of 275 // InputMethodManagerService, which is closer to the real situation. 276 InputMethodManagerService.Lifecycle lifecycle = 277 new InputMethodManagerService.Lifecycle(mContext, mInputMethodManagerService); 278 279 // Public local InputMethodManagerService. 280 LocalServices.removeServiceForTest(InputMethodManagerInternal.class); 281 lifecycle.onStart(); 282 283 final var userData = mInputMethodManagerService.getUserData(mUserId); 284 285 // Certain tests rely on TEST_IME_ID that is installed with AndroidTest.xml. 286 // TODO(b/352615651): Consider just synthesizing test InputMethodInfo then injecting it. 287 AdditionalSubtypeMapRepository.initializeIfNecessary(mUserId); 288 final var rawMethodMap = InputMethodManagerService.queryRawInputMethodServiceMap(mContext, 289 mUserId); 290 userData.mRawInputMethodMap.set(rawMethodMap); 291 final var settings = InputMethodSettings.create(rawMethodMap.toInputMethodMap( 292 AdditionalSubtypeMap.EMPTY_MAP, DirectBootAwareness.AUTO, true /* userUnlocked */), 293 mUserId); 294 InputMethodSettingsRepository.put(mUserId, settings); 295 296 // Emulate that the user initialization is done. 297 userData.mBackgroundLoadLatch.countDown(); 298 299 // After this boot phase, services can broadcast Intents. 300 lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY); 301 302 // Call InputMethodManagerService#addClient() as a preparation to start interacting with it. 303 mInputMethodManagerService.addClient(mMockInputMethodClient, mMockFallbackInputConnection, 304 0 /* selfReportedDisplayId */); 305 createSessionForClient(mMockInputMethodClient); 306 } 307 308 @After tearDown()309 public void tearDown() { 310 InputMethodSettingsRepository.remove(mUserId); 311 312 if (mInputMethodManagerService != null) { 313 mInputMethodManagerService.mInputMethodDeviceConfigs.destroy(); 314 } 315 316 if (mIoThread != null) { 317 mIoThread.quitSafely(); 318 } 319 320 if (mServiceThread != null) { 321 mServiceThread.quitSafely(); 322 } 323 324 if (mMockingSession != null) { 325 mMockingSession.finishMocking(); 326 } 327 328 if (mInputManagerGlobalSession != null) { 329 mInputManagerGlobalSession.close(); 330 } 331 mRegisteredLocalServices.forEach(LocalServices::removeServiceForTest); 332 } 333 verifyShowSoftInput(boolean setVisible, boolean showSoftInput)334 protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput) 335 throws RemoteException { 336 verifyShowSoftInput(setVisible, showSoftInput, NO_VERIFY_SHOW_FLAGS); 337 } 338 verifyShowSoftInput(boolean setVisible, boolean showSoftInput, int showFlags)339 protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput, int showFlags) 340 throws RemoteException { 341 synchronized (ImfLock.class) { 342 verify(mMockInputMethodBindingController, times(setVisible ? 1 : 0)) 343 .setCurrentMethodVisible(); 344 } 345 verify(mMockInputMethod, times(showSoftInput ? 1 : 0)) 346 .showSoftInput(any() /* showInputToken */ , notNull() /* statsToken */, 347 showFlags != NO_VERIFY_SHOW_FLAGS ? eq(showFlags) : anyInt() /* flags*/, 348 any() /* resultReceiver */); 349 } 350 verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput)351 protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput) 352 throws RemoteException { 353 synchronized (ImfLock.class) { 354 verify(mMockInputMethodBindingController, times(setNotVisible ? 1 : 0)) 355 .setCurrentMethodNotVisible(); 356 } 357 verify(mMockInputMethod, times(hideSoftInput ? 1 : 0)) 358 .hideSoftInput(any() /* hideInputToken */, notNull() /* statsToken */, 359 anyInt() /* flags */, any() /* resultReceiver */); 360 } 361 verifySetImeVisibility(boolean setVisible, boolean invoked)362 protected void verifySetImeVisibility(boolean setVisible, boolean invoked) { 363 synchronized (ImfLock.class) { 364 verify(mInputMethodManagerService, 365 times(invoked ? 1 : 0)).setImeVisibilityOnFocusedWindowClient(eq(setVisible), 366 any(UserData.class), any(ImeTracker.Token.class)); 367 } 368 } 369 createSessionForClient(IInputMethodClient client)370 protected void createSessionForClient(IInputMethodClient client) { 371 synchronized (ImfLock.class) { 372 ClientState cs = mInputMethodManagerService.getClientStateLocked(client); 373 cs.mCurSession = new InputMethodManagerService.SessionState(cs, 374 mMockInputMethodInvoker, mMockInputMethodSession, mock(InputChannel.class), 375 mUserId); 376 } 377 } 378 } 379