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