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.anyLong; 29 import static org.mockito.ArgumentMatchers.anyString; 30 import static org.mockito.ArgumentMatchers.notNull; 31 import static org.mockito.Mockito.eq; 32 import static org.mockito.Mockito.mock; 33 import static org.mockito.Mockito.times; 34 import static org.mockito.Mockito.verify; 35 import static org.mockito.Mockito.when; 36 37 import android.app.ActivityManagerInternal; 38 import android.content.Context; 39 import android.content.pm.PackageManagerInternal; 40 import android.content.res.Configuration; 41 import android.hardware.input.IInputManager; 42 import android.hardware.input.InputManagerGlobal; 43 import android.os.Binder; 44 import android.os.IBinder; 45 import android.os.Process; 46 import android.os.RemoteException; 47 import android.os.ServiceManager; 48 import android.os.UserHandle; 49 import android.view.InputChannel; 50 import android.view.inputmethod.EditorInfo; 51 import android.window.ImeOnBackInvokedDispatcher; 52 53 import androidx.test.platform.app.InstrumentationRegistry; 54 55 import com.android.internal.compat.IPlatformCompat; 56 import com.android.internal.inputmethod.IInputMethod; 57 import com.android.internal.inputmethod.IInputMethodClient; 58 import com.android.internal.inputmethod.IInputMethodSession; 59 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; 60 import com.android.internal.inputmethod.IRemoteInputConnection; 61 import com.android.internal.inputmethod.InputBindResult; 62 import com.android.internal.view.IInputMethodManager; 63 import com.android.server.LocalServices; 64 import com.android.server.ServiceThread; 65 import com.android.server.SystemServerInitThreadPool; 66 import com.android.server.SystemService; 67 import com.android.server.input.InputManagerInternal; 68 import com.android.server.pm.UserManagerInternal; 69 import com.android.server.wm.ImeTargetVisibilityPolicy; 70 import com.android.server.wm.WindowManagerInternal; 71 72 import org.junit.After; 73 import org.junit.Before; 74 import org.junit.BeforeClass; 75 import org.mockito.Mock; 76 import org.mockito.MockitoSession; 77 import org.mockito.quality.Strictness; 78 79 /** Base class for testing {@link InputMethodManagerService}. */ 80 public class InputMethodManagerServiceTestBase { 81 private static final int NO_VERIFY_SHOW_FLAGS = -1; 82 83 protected static final String TEST_SELECTED_IME_ID = "test.ime"; 84 protected static final String TEST_EDITOR_PKG_NAME = "test.editor"; 85 protected static final String TEST_FOCUSED_WINDOW_NAME = "test.editor/activity"; 86 protected static final WindowManagerInternal.ImeTargetInfo TEST_IME_TARGET_INFO = 87 new WindowManagerInternal.ImeTargetInfo( 88 TEST_FOCUSED_WINDOW_NAME, 89 TEST_FOCUSED_WINDOW_NAME, 90 TEST_FOCUSED_WINDOW_NAME, 91 TEST_FOCUSED_WINDOW_NAME, 92 TEST_FOCUSED_WINDOW_NAME); 93 protected static final InputBindResult SUCCESS_WAITING_IME_BINDING_RESULT = 94 new InputBindResult( 95 InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, 96 null, 97 null, 98 null, 99 "0", 100 0, 101 false); 102 103 @Mock protected WindowManagerInternal mMockWindowManagerInternal; 104 @Mock protected ActivityManagerInternal mMockActivityManagerInternal; 105 @Mock protected PackageManagerInternal mMockPackageManagerInternal; 106 @Mock protected InputManagerInternal mMockInputManagerInternal; 107 @Mock protected UserManagerInternal mMockUserManagerInternal; 108 @Mock protected InputMethodBindingController mMockInputMethodBindingController; 109 @Mock protected IInputMethodClient mMockInputMethodClient; 110 @Mock protected IInputMethodSession mMockInputMethodSession; 111 @Mock protected IBinder mWindowToken; 112 @Mock protected IRemoteInputConnection mMockRemoteInputConnection; 113 @Mock protected IRemoteAccessibilityInputConnection mMockRemoteAccessibilityInputConnection; 114 @Mock protected ImeOnBackInvokedDispatcher mMockImeOnBackInvokedDispatcher; 115 @Mock protected IInputMethodManager.Stub mMockIInputMethodManager; 116 @Mock protected IPlatformCompat.Stub mMockIPlatformCompat; 117 @Mock protected IInputMethod mMockInputMethod; 118 @Mock protected IBinder mMockInputMethodBinder; 119 @Mock protected IInputManager mMockIInputManager; 120 @Mock protected ImeTargetVisibilityPolicy mMockImeTargetVisibilityPolicy; 121 122 protected Context mContext; 123 protected MockitoSession mMockingSession; 124 protected int mTargetSdkVersion; 125 protected int mCallingUserId; 126 protected EditorInfo mEditorInfo; 127 protected IInputMethodInvoker mMockInputMethodInvoker; 128 protected InputMethodManagerService mInputMethodManagerService; 129 protected ServiceThread mServiceThread; 130 protected ServiceThread mIoThread; 131 protected boolean mIsLargeScreen; 132 private InputManagerGlobal.TestSession mInputManagerGlobalSession; 133 134 @BeforeClass setupClass()135 public static void setupClass() { 136 // Make sure DeviceConfig's lazy-initialized ContentProvider gets 137 // a real instance before we stub out all system services below. 138 // TODO(b/272229177): remove dependency on real ContentProvider 139 new InputMethodDeviceConfigs().destroy(); 140 } 141 142 @Before setUp()143 public void setUp() throws RemoteException { 144 mMockingSession = 145 mockitoSession() 146 .initMocks(this) 147 .strictness(Strictness.LENIENT) 148 .spyStatic(LocalServices.class) 149 .mockStatic(ServiceManager.class) 150 .mockStatic(SystemServerInitThreadPool.class) 151 .startMocking(); 152 153 mContext = spy(InstrumentationRegistry.getInstrumentation().getContext()); 154 155 mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; 156 mIsLargeScreen = mContext.getResources().getConfiguration() 157 .isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); 158 mCallingUserId = UserHandle.getCallingUserId(); 159 mEditorInfo = new EditorInfo(); 160 mEditorInfo.packageName = TEST_EDITOR_PKG_NAME; 161 162 // Injecting and mocking local services. 163 doReturn(mMockWindowManagerInternal) 164 .when(() -> LocalServices.getService(WindowManagerInternal.class)); 165 doReturn(mMockActivityManagerInternal) 166 .when(() -> LocalServices.getService(ActivityManagerInternal.class)); 167 doReturn(mMockPackageManagerInternal) 168 .when(() -> LocalServices.getService(PackageManagerInternal.class)); 169 doReturn(mMockInputManagerInternal) 170 .when(() -> LocalServices.getService(InputManagerInternal.class)); 171 doReturn(mMockUserManagerInternal) 172 .when(() -> LocalServices.getService(UserManagerInternal.class)); 173 doReturn(mMockImeTargetVisibilityPolicy) 174 .when(() -> LocalServices.getService(ImeTargetVisibilityPolicy.class)); 175 doReturn(mMockIInputMethodManager) 176 .when(() -> ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE)); 177 doReturn(mMockIPlatformCompat) 178 .when(() -> ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); 179 180 // Stubbing out context related methods to avoid the system holding strong references to 181 // InputMethodManagerService. 182 doNothing().when(mContext).enforceCallingPermission(anyString(), anyString()); 183 doNothing().when(mContext).sendBroadcastAsUser(any(), any()); 184 doReturn(null).when(mContext).registerReceiver(any(), any()); 185 doReturn(null) 186 .when(mContext) 187 .registerReceiverAsUser(any(), any(), any(), anyString(), any(), anyInt()); 188 189 // Injecting and mocked InputMethodBindingController and InputMethod. 190 mMockInputMethodInvoker = IInputMethodInvoker.create(mMockInputMethod); 191 mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mMockIInputManager); 192 synchronized (ImfLock.class) { 193 when(mMockInputMethodBindingController.getCurMethod()) 194 .thenReturn(mMockInputMethodInvoker); 195 when(mMockInputMethodBindingController.bindCurrentMethod()) 196 .thenReturn(SUCCESS_WAITING_IME_BINDING_RESULT); 197 doNothing().when(mMockInputMethodBindingController).unbindCurrentMethod(); 198 when(mMockInputMethodBindingController.getSelectedMethodId()) 199 .thenReturn(TEST_SELECTED_IME_ID); 200 } 201 202 // Shuffling around all other initialization to make the test runnable. 203 when(mMockIInputManager.getInputDeviceIds()).thenReturn(new int[0]); 204 when(mMockIInputMethodManager.isImeTraceEnabled()).thenReturn(false); 205 when(mMockIPlatformCompat.isChangeEnabledByUid(anyLong(), anyInt())).thenReturn(true); 206 when(mMockUserManagerInternal.isUserRunning(anyInt())).thenReturn(true); 207 when(mMockUserManagerInternal.getProfileIds(anyInt(), anyBoolean())) 208 .thenReturn(new int[] {0}); 209 when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[] {0}); 210 when(mMockActivityManagerInternal.isSystemReady()).thenReturn(true); 211 when(mMockActivityManagerInternal.getCurrentUserId()).thenReturn(mCallingUserId); 212 when(mMockPackageManagerInternal.getPackageUid(anyString(), anyLong(), anyInt())) 213 .thenReturn(Binder.getCallingUid()); 214 when(mMockPackageManagerInternal.isSameApp(anyString(), anyLong(), anyInt(), anyInt())) 215 .thenReturn(true); 216 when(mMockWindowManagerInternal.onToggleImeRequested(anyBoolean(), any(), any(), anyInt())) 217 .thenReturn(TEST_IME_TARGET_INFO); 218 when(mMockInputMethodClient.asBinder()).thenReturn(mMockInputMethodBinder); 219 220 // Used by lazy initializing draw IMS nav bar at InputMethodManagerService#systemRunning(), 221 // which is ok to be mocked out for now. 222 doReturn(null).when(() -> SystemServerInitThreadPool.submit(any(), anyString())); 223 224 mServiceThread = 225 new ServiceThread( 226 "immstest1", 227 Process.THREAD_PRIORITY_FOREGROUND, 228 true /* allowIo */); 229 mIoThread = 230 new ServiceThread( 231 "immstest2", 232 Process.THREAD_PRIORITY_FOREGROUND, 233 true /* allowIo */); 234 mInputMethodManagerService = new InputMethodManagerService(mContext, 235 InputMethodManagerService.shouldEnableExperimentalConcurrentMultiUserMode(mContext), 236 mServiceThread, mIoThread, 237 unusedUserId -> mMockInputMethodBindingController); 238 spyOn(mInputMethodManagerService); 239 240 // Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of 241 // InputMethodManagerService, which is closer to the real situation. 242 InputMethodManagerService.Lifecycle lifecycle = 243 new InputMethodManagerService.Lifecycle(mContext, mInputMethodManagerService); 244 245 // Public local InputMethodManagerService. 246 LocalServices.removeServiceForTest(InputMethodManagerInternal.class); 247 lifecycle.onStart(); 248 try { 249 // After this boot phase, services can broadcast Intents. 250 lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY); 251 } catch (SecurityException e) { 252 // Security exception to permission denial is expected in test, mocking out to ensure 253 // InputMethodManagerService as system ready state. 254 if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { 255 throw e; 256 } 257 } 258 259 // Call InputMethodManagerService#addClient() as a preparation to start interacting with it. 260 mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection, 0); 261 createSessionForClient(mMockInputMethodClient); 262 } 263 264 @After tearDown()265 public void tearDown() { 266 if (mInputMethodManagerService != null) { 267 mInputMethodManagerService.mInputMethodDeviceConfigs.destroy(); 268 } 269 270 if (mIoThread != null) { 271 mIoThread.quitSafely(); 272 } 273 274 if (mServiceThread != null) { 275 mServiceThread.quitSafely(); 276 } 277 278 if (mMockingSession != null) { 279 mMockingSession.finishMocking(); 280 } 281 282 if (mInputManagerGlobalSession != null) { 283 mInputManagerGlobalSession.close(); 284 } 285 LocalServices.removeServiceForTest(InputMethodManagerInternal.class); 286 } 287 verifyShowSoftInput(boolean setVisible, boolean showSoftInput)288 protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput) 289 throws RemoteException { 290 verifyShowSoftInput(setVisible, showSoftInput, NO_VERIFY_SHOW_FLAGS); 291 } 292 verifyShowSoftInput(boolean setVisible, boolean showSoftInput, int showFlags)293 protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput, int showFlags) 294 throws RemoteException { 295 synchronized (ImfLock.class) { 296 verify(mMockInputMethodBindingController, times(setVisible ? 1 : 0)) 297 .setCurrentMethodVisible(); 298 } 299 verify(mMockInputMethod, times(showSoftInput ? 1 : 0)) 300 .showSoftInput(any() /* showInputToken */ , notNull() /* statsToken */, 301 showFlags != NO_VERIFY_SHOW_FLAGS ? eq(showFlags) : anyInt() /* flags*/, 302 any() /* resultReceiver */); 303 } 304 verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput)305 protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput) 306 throws RemoteException { 307 synchronized (ImfLock.class) { 308 verify(mMockInputMethodBindingController, times(setNotVisible ? 1 : 0)) 309 .setCurrentMethodNotVisible(); 310 } 311 verify(mMockInputMethod, times(hideSoftInput ? 1 : 0)) 312 .hideSoftInput(any() /* hideInputToken */, notNull() /* statsToken */, 313 anyInt() /* flags */, any() /* resultReceiver */); 314 } 315 createSessionForClient(IInputMethodClient client)316 protected void createSessionForClient(IInputMethodClient client) { 317 synchronized (ImfLock.class) { 318 ClientState cs = mInputMethodManagerService.getClientStateLocked(client); 319 cs.mCurSession = new InputMethodManagerService.SessionState(cs, 320 mMockInputMethodInvoker, mMockInputMethodSession, mock( 321 InputChannel.class)); 322 } 323 } 324 } 325