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