• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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