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 android.inputmethodservice.InputMethodService.IME_ACTIVE;
20 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
21 
22 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
23 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER;
24 import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
25 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
26 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
27 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
28 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_INVALID;
29 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
30 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_IMPLICIT;
31 
32 import static org.junit.Assert.assertThrows;
33 import static org.mockito.Mockito.any;
34 import static org.mockito.Mockito.anyInt;
35 import static org.mockito.Mockito.eq;
36 import static org.mockito.Mockito.mock;
37 import static org.mockito.Mockito.verify;
38 
39 import android.os.Binder;
40 import android.os.IBinder;
41 import android.os.RemoteException;
42 import android.view.Display;
43 
44 import androidx.test.ext.junit.runners.AndroidJUnit4;
45 
46 import com.android.dx.mockito.inline.extended.ExtendedMockito;
47 import com.android.internal.inputmethod.InputBindResult;
48 import com.android.internal.inputmethod.StartInputFlags;
49 import com.android.internal.inputmethod.StartInputReason;
50 
51 import org.junit.Before;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 
55 /**
56  * Test the behavior of {@link DefaultImeVisibilityApplier} when performing or applying the IME
57  * visibility state.
58  *
59  * Build/Install/Run:
60  * atest FrameworksInputMethodSystemServerTests:DefaultImeVisibilityApplierTest
61  */
62 @RunWith(AndroidJUnit4.class)
63 public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTestBase {
64     private DefaultImeVisibilityApplier mVisibilityApplier;
65 
66     @Before
setUp()67     public void setUp() throws RemoteException {
68         super.setUp();
69         mVisibilityApplier =
70                 (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier();
71         mInputMethodManagerService.setAttachedClientForTesting(
72                 mock(InputMethodManagerService.ClientState.class));
73     }
74 
75     @Test
testPerformShowIme()76     public void testPerformShowIme() throws Exception {
77         synchronized (ImfLock.class) {
78             mVisibilityApplier.performShowIme(new Binder() /* showInputToken */,
79                     null /* statsToken */, 0 /* showFlags */, null, SHOW_SOFT_INPUT);
80         }
81         verifyShowSoftInput(false, true, 0 /* showFlags */);
82     }
83 
84     @Test
testPerformHideIme()85     public void testPerformHideIme() throws Exception {
86         synchronized (ImfLock.class) {
87             mVisibilityApplier.performHideIme(new Binder() /* hideInputToken */,
88                     null /* statsToken */, null, HIDE_SOFT_INPUT);
89         }
90         verifyHideSoftInput(false, true);
91     }
92 
93     @Test
testApplyImeVisibility_throwForInvalidState()94     public void testApplyImeVisibility_throwForInvalidState() {
95         assertThrows(IllegalArgumentException.class,
96                 () -> mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_INVALID));
97     }
98 
99     @Test
testApplyImeVisibility_showIme()100     public void testApplyImeVisibility_showIme() {
101         mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME);
102         verify(mMockWindowManagerInternal).showImePostLayout(eq(mWindowToken), any());
103     }
104 
105     @Test
testApplyImeVisibility_hideIme()106     public void testApplyImeVisibility_hideIme() {
107         mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME);
108         verify(mMockWindowManagerInternal).hideIme(eq(mWindowToken), anyInt(), any());
109     }
110 
111     @Test
testApplyImeVisibility_hideImeExplicit()112     public void testApplyImeVisibility_hideImeExplicit() throws Exception {
113         mInputMethodManagerService.mImeWindowVis = IME_ACTIVE;
114         mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME_EXPLICIT);
115         verifyHideSoftInput(true, true);
116     }
117 
118     @Test
testApplyImeVisibility_hideNotAlways()119     public void testApplyImeVisibility_hideNotAlways() throws Exception {
120         mInputMethodManagerService.mImeWindowVis = IME_ACTIVE;
121         mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME_NOT_ALWAYS);
122         verifyHideSoftInput(true, true);
123     }
124 
125     @Test
testApplyImeVisibility_showImeImplicit()126     public void testApplyImeVisibility_showImeImplicit() throws Exception {
127         mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME_IMPLICIT);
128         verifyShowSoftInput(true, true, 0 /* showFlags */);
129     }
130 
131     @Test
testApplyImeVisibility_hideImeFromTargetOnSecondaryDisplay()132     public void testApplyImeVisibility_hideImeFromTargetOnSecondaryDisplay() {
133         // Init a IME target client on the secondary display to show IME.
134         mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection,
135                 10 /* selfReportedDisplayId */);
136         mInputMethodManagerService.setAttachedClientForTesting(null);
137         startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE);
138 
139         synchronized (ImfLock.class) {
140             final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked();
141             // Verify hideIme will apply the expected displayId when the default IME
142             // visibility applier app STATE_HIDE_IME.
143             mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME);
144             verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(
145                     eq(mWindowToken), eq(displayIdToShowIme), eq(null));
146         }
147     }
148 
149     @Test
testShowImeScreenshot()150     public void testShowImeScreenshot() {
151         synchronized (ImfLock.class) {
152             mVisibilityApplier.showImeScreenshot(mWindowToken, Display.DEFAULT_DISPLAY,
153                     null /* statsToken */);
154         }
155 
156         verify(mMockImeTargetVisibilityPolicy).showImeScreenshot(eq(mWindowToken),
157                 eq(Display.DEFAULT_DISPLAY));
158     }
159 
160     @Test
testRemoveImeScreenshot()161     public void testRemoveImeScreenshot() {
162         synchronized (ImfLock.class) {
163             mVisibilityApplier.removeImeScreenshot(Display.DEFAULT_DISPLAY);
164         }
165 
166         verify(mMockImeTargetVisibilityPolicy).removeImeScreenshot(eq(Display.DEFAULT_DISPLAY));
167     }
168 
169     @Test
testApplyImeVisibility_hideImeWhenUnbinding()170     public void testApplyImeVisibility_hideImeWhenUnbinding() {
171         mInputMethodManagerService.setAttachedClientForTesting(null);
172         startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE);
173         ExtendedMockito.spyOn(mVisibilityApplier);
174 
175         synchronized (ImfLock.class) {
176             // Simulate the system hides the IME when switching IME services in different users.
177             // (e.g. unbinding the IME from the current user to the profile user)
178             final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked();
179             mInputMethodManagerService.hideCurrentInputLocked(mWindowToken, null, 0, null,
180                     HIDE_SWITCH_USER);
181             mInputMethodManagerService.onUnbindCurrentMethodByReset();
182 
183             // Expects applyImeVisibility() -> hideIme() will be called to notify WM for syncing
184             // the IME hidden state.
185             verify(mVisibilityApplier).applyImeVisibility(eq(mWindowToken), any(),
186                     eq(STATE_HIDE_IME));
187             verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(
188                     eq(mWindowToken), eq(displayIdToShowIme), eq(null));
189         }
190     }
191 
startInputOrWindowGainedFocus(IBinder windowToken, int softInputMode)192     private InputBindResult startInputOrWindowGainedFocus(IBinder windowToken, int softInputMode) {
193         return mInputMethodManagerService.startInputOrWindowGainedFocus(
194                 StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */,
195                 mMockInputMethodClient /* client */,
196                 windowToken /* windowToken */,
197                 StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR,
198                 softInputMode /* softInputMode */,
199                 0 /* windowFlags */,
200                 mEditorInfo /* editorInfo */,
201                 mMockRemoteInputConnection /* inputConnection */,
202                 mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */,
203                 mTargetSdkVersion /* unverifiedTargetSdkVersion */,
204                 mCallingUserId /* userId */,
205                 mMockImeOnBackInvokedDispatcher /* imeDispatcher */);
206     }
207 }
208