1 /**
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11  * express or implied. See the License for the specific language governing permissions and
12  * limitations under the License.
13  */
14 
15 package android.accessibilityservice.cts;
16 
17 import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
18 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
19 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
20 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
21 
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertNotEquals;
24 import static org.junit.Assert.assertTrue;
25 
26 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
27 import android.accessibility.cts.common.InstrumentedAccessibilityService;
28 import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
29 import android.accessibility.cts.common.ShellCommandBuilder;
30 import android.accessibilityservice.AccessibilityService.SoftKeyboardController;
31 import android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener;
32 import android.accessibilityservice.cts.activities.AccessibilityTestActivity;
33 import android.accessibilityservice.cts.utils.AsyncUtils;
34 import android.app.Instrumentation;
35 import android.inputmethodservice.cts.common.Ime1Constants;
36 import android.inputmethodservice.cts.common.test.ShellCommandUtils;
37 import android.os.Bundle;
38 import android.os.SystemClock;
39 import android.platform.test.annotations.AppModeFull;
40 import android.provider.Settings;
41 
42 import androidx.test.InstrumentationRegistry;
43 import androidx.test.runner.AndroidJUnit4;
44 
45 import org.junit.Before;
46 import org.junit.Rule;
47 import org.junit.Test;
48 import org.junit.rules.RuleChain;
49 import org.junit.runner.RunWith;
50 
51 /**
52  * Test cases for {@code SoftKeyboardController}. It tests the accessibility APIs for interacting
53  * with the soft keyboard show mode.
54  */
55 @RunWith(AndroidJUnit4.class)
56 @AppModeFull
57 public class AccessibilitySoftKeyboardTest {
58     private Instrumentation mInstrumentation;
59     private int mLastCallbackValue;
60 
61     private InstrumentedAccessibilityService mService;
62     private final Object mLock = new Object();
63     private final OnShowModeChangedListener mListener = (c, showMode) -> {
64         synchronized (mLock) {
65             mLastCallbackValue = showMode;
66             mLock.notifyAll();
67         }
68     };
69 
70     private InstrumentedAccessibilityServiceTestRule<InstrumentedAccessibilityService>
71             mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
72                     InstrumentedAccessibilityService.class);
73 
74     private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
75             new AccessibilityDumpOnFailureRule();
76 
77     @Rule
78     public final RuleChain mRuleChain = RuleChain
79             .outerRule(mServiceRule)
80             .around(mDumpOnFailureRule);
81 
82     @Before
setUp()83     public void setUp() {
84         mInstrumentation = InstrumentationRegistry.getInstrumentation();
85         mService = mServiceRule.getService();
86     }
87 
88     @Test
testApiReturnValues_shouldChangeValueOnRequestAndSendCallback()89     public void testApiReturnValues_shouldChangeValueOnRequestAndSendCallback() throws Exception {
90         final SoftKeyboardController controller = mService.getSoftKeyboardController();
91 
92         // Confirm that we start in the default state
93         assertEquals(SHOW_MODE_AUTO, controller.getShowMode());
94 
95         controller.addOnShowModeChangedListener(mListener);
96         assertCanSetAndGetShowModeAndCallbackHappens(SHOW_MODE_HIDDEN, mService);
97         assertCanSetAndGetShowModeAndCallbackHappens(SHOW_MODE_IGNORE_HARD_KEYBOARD, mService);
98         assertCanSetAndGetShowModeAndCallbackHappens(SHOW_MODE_AUTO, mService);
99 
100         // Make sure we can remove our listener.
101         assertTrue(controller.removeOnShowModeChangedListener(mListener));
102     }
103 
104     @Test
secondServiceChangingTheShowMode_updatesModeAndNotifiesFirstService()105     public void secondServiceChangingTheShowMode_updatesModeAndNotifiesFirstService()
106             throws Exception {
107 
108         final SoftKeyboardController controller = mService.getSoftKeyboardController();
109         // Confirm that we start in the default state
110         assertEquals(SHOW_MODE_AUTO, controller.getShowMode());
111 
112         final InstrumentedAccessibilityService secondService =
113                 enableService(StubAccessibilityButtonService.class);
114         try {
115             // Listen on the first service
116             controller.addOnShowModeChangedListener(mListener);
117             assertCanSetAndGetShowModeAndCallbackHappens(SHOW_MODE_HIDDEN, mService);
118 
119             // Change the mode on the second service
120             assertCanSetAndGetShowModeAndCallbackHappens(SHOW_MODE_IGNORE_HARD_KEYBOARD,
121                     secondService);
122         } finally {
123             secondService.runOnServiceSync(() -> secondService.disableSelf());
124         }
125 
126         // Shutting down the second service, which was controlling the mode, should put us back
127         // to the default
128         waitForCallbackValueWithLock(SHOW_MODE_AUTO);
129         final int showMode = mService.getOnService(() -> controller.getShowMode());
130         assertEquals(SHOW_MODE_AUTO, showMode);
131     }
132 
133     @Test
testSwitchToInputMethod()134     public void testSwitchToInputMethod() throws Exception {
135         final SoftKeyboardController controller = mService.getSoftKeyboardController();
136         String currentIME = Settings.Secure.getString(
137                 mService.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
138         assertNotEquals(Ime1Constants.IME_ID, currentIME);
139         // Enable a dummy IME for this test.
140         try (TestImeSession imeSession = new TestImeSession(Ime1Constants.IME_ID)) {
141             // Switch to the dummy IME.
142             final boolean success = controller.switchToInputMethod(Ime1Constants.IME_ID);
143             currentIME = Settings.Secure.getString(
144                     mService.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
145 
146             // The current IME should be set to the dummy IME successfully.
147             assertTrue(success);
148             assertEquals(Ime1Constants.IME_ID, currentIME);
149         }
150     }
151 
assertCanSetAndGetShowModeAndCallbackHappens( int mode, InstrumentedAccessibilityService service)152     private void assertCanSetAndGetShowModeAndCallbackHappens(
153             int mode, InstrumentedAccessibilityService service)
154             throws Exception  {
155         final SoftKeyboardController controller = service.getSoftKeyboardController();
156         mLastCallbackValue = -1;
157         final boolean setShowModeReturns =
158                 service.getOnService(() -> controller.setShowMode(mode));
159         assertTrue(setShowModeReturns);
160         waitForCallbackValueWithLock(mode);
161         assertEquals(mode, controller.getShowMode());
162     }
163 
waitForCallbackValueWithLock(int expectedValue)164     private void waitForCallbackValueWithLock(int expectedValue) throws Exception {
165         long timeoutTimeMillis = SystemClock.uptimeMillis() + AsyncUtils.DEFAULT_TIMEOUT_MS;
166 
167         while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
168             synchronized(mLock) {
169                 if (mLastCallbackValue == expectedValue) {
170                     return;
171                 }
172                 try {
173                     mLock.wait(timeoutTimeMillis - SystemClock.uptimeMillis());
174                 } catch (InterruptedException e) {
175                     // Wait until timeout.
176                 }
177             }
178         }
179 
180         throw new IllegalStateException("last callback value <" + mLastCallbackValue
181                 + "> does not match expected value < " + expectedValue + ">");
182     }
183 
184     /**
185      * Activity for testing the AccessibilityService API for hiding and showing the soft keyboard.
186      */
187     public static class SoftKeyboardModesActivity extends AccessibilityTestActivity {
SoftKeyboardModesActivity()188         public SoftKeyboardModesActivity() {
189             super();
190         }
191 
192         @Override
onCreate(Bundle savedInstanceState)193         public void onCreate(Bundle savedInstanceState) {
194             super.onCreate(savedInstanceState);
195             setContentView(R.layout.accessibility_soft_keyboard_modes_test);
196         }
197     }
198 
199     private class TestImeSession implements AutoCloseable {
TestImeSession(String imeId)200         TestImeSession(String imeId) {
201             // Enable the dummy IME by shell command.
202             final String enableImeCommand = ShellCommandUtils.enableIme(imeId);
203             ShellCommandBuilder.create(mInstrumentation)
204                     .addCommand(enableImeCommand)
205                     .run();
206         }
207 
208         @Override
close()209         public void close() throws Exception {
210             // Reset IMEs by shell command.
211             ShellCommandBuilder.create(mInstrumentation)
212                     .addCommand(ShellCommandUtils.resetImes())
213                     .run();
214         }
215     }
216 }
217