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