1 /* 2 * Copyright (C) 2017 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.cts.mockime; 18 19 import static java.lang.annotation.RetentionPolicy.SOURCE; 20 21 import android.os.Bundle; 22 import android.os.PersistableBundle; 23 import android.view.inputmethod.InputMethodSubtype; 24 25 import androidx.annotation.ColorInt; 26 import androidx.annotation.IntDef; 27 import androidx.annotation.NonNull; 28 import androidx.annotation.Nullable; 29 import androidx.window.extensions.layout.WindowLayoutInfo; 30 31 import java.lang.annotation.Retention; 32 33 /** 34 * An immutable data store to control the behavior of {@link MockIme}. 35 */ 36 public class ImeSettings { 37 38 @NonNull 39 private final String mClientPackageName; 40 41 @NonNull 42 private final String mEventCallbackActionName; 43 44 private static final String EVENT_CALLBACK_INTENT_ACTION_KEY = "eventCallbackActionName"; 45 private static final String DATA_KEY = "data"; 46 47 private static final String BACKGROUND_COLOR_KEY = "BackgroundColor"; 48 private static final String NAVIGATION_BAR_COLOR_KEY = "NavigationBarColor"; 49 private static final String INPUT_VIEW_HEIGHT = 50 "InputViewHeightWithoutSystemWindowInset"; 51 private static final String DRAWS_BEHIND_NAV_BAR = "drawsBehindNavBar"; 52 private static final String WINDOW_FLAGS = "WindowFlags"; 53 private static final String WINDOW_FLAGS_MASK = "WindowFlagsMask"; 54 private static final String FULLSCREEN_MODE_POLICY = "FullscreenModePolicy"; 55 private static final String INPUT_VIEW_SYSTEM_UI_VISIBILITY = "InputViewSystemUiVisibility"; 56 private static final String WATERMARK_ENABLED = "WatermarkEnabled"; 57 private static final String WATERMARK_GRAVITY = "WatermarkGravity"; 58 private static final String HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED = 59 "HardKeyboardConfigurationBehaviorAllowed"; 60 private static final String INLINE_SUGGESTIONS_ENABLED = "InlineSuggestionsEnabled"; 61 private static final String INLINE_SUGGESTION_VIEW_CONTENT_DESC = 62 "InlineSuggestionViewContentDesc"; 63 private static final String STRICT_MODE_ENABLED = "StrictModeEnabled"; 64 private static final String VERIFY_CONTEXT_APIS_IN_ON_CREATE = "VerifyContextApisInOnCreate"; 65 private static final String WINDOW_LAYOUT_INFO_CALLBACK_ENABLED = 66 "WindowLayoutInfoCallbackEnabled"; 67 68 /** 69 * Simulate the manifest flag enableOnBackInvokedCallback being true for the IME. 70 */ 71 private static final String ON_BACK_CALLBACK_ENABLED = "onBackCallbackEnabled"; 72 73 @NonNull 74 private final PersistableBundle mBundle; 75 76 77 @Retention(SOURCE) 78 @IntDef(value = { 79 FullscreenModePolicy.NO_FULLSCREEN, 80 FullscreenModePolicy.FORCE_FULLSCREEN, 81 FullscreenModePolicy.OS_DEFAULT, 82 }) 83 public @interface FullscreenModePolicy { 84 /** 85 * Let {@link MockIme} always return {@code false} from 86 * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}. 87 * 88 * <p>This is chosen to be the default behavior of {@link MockIme} to make CTS tests most 89 * deterministic.</p> 90 */ 91 int NO_FULLSCREEN = 0; 92 93 /** 94 * Let {@link MockIme} always return {@code true} from 95 * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}. 96 * 97 * <p>This can be used to test the behaviors when a full-screen IME is running.</p> 98 */ 99 int FORCE_FULLSCREEN = 1; 100 101 /** 102 * Let {@link MockIme} always return the default behavior of 103 * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}. 104 * 105 * <p>This can be used to test the default behavior of that public API.</p> 106 */ 107 int OS_DEFAULT = 2; 108 } 109 ImeSettings(@onNull String clientPackageName, @NonNull Bundle bundle)110 ImeSettings(@NonNull String clientPackageName, @NonNull Bundle bundle) { 111 mClientPackageName = clientPackageName; 112 mEventCallbackActionName = bundle.getString(EVENT_CALLBACK_INTENT_ACTION_KEY); 113 mBundle = bundle.getParcelable(DATA_KEY); 114 } 115 116 @Nullable getEventCallbackActionName()117 String getEventCallbackActionName() { 118 return mEventCallbackActionName; 119 } 120 121 @NonNull getClientPackageName()122 String getClientPackageName() { 123 return mClientPackageName; 124 } 125 126 @FullscreenModePolicy fullscreenModePolicy()127 public int fullscreenModePolicy() { 128 return mBundle.getInt(FULLSCREEN_MODE_POLICY); 129 } 130 131 @ColorInt getBackgroundColor(@olorInt int defaultColor)132 public int getBackgroundColor(@ColorInt int defaultColor) { 133 return mBundle.getInt(BACKGROUND_COLOR_KEY, defaultColor); 134 } 135 hasNavigationBarColor()136 public boolean hasNavigationBarColor() { 137 return mBundle.keySet().contains(NAVIGATION_BAR_COLOR_KEY); 138 } 139 140 @ColorInt getNavigationBarColor()141 public int getNavigationBarColor() { 142 return mBundle.getInt(NAVIGATION_BAR_COLOR_KEY); 143 } 144 getInputViewHeight(int defaultHeight)145 public int getInputViewHeight(int defaultHeight) { 146 return mBundle.getInt(INPUT_VIEW_HEIGHT, defaultHeight); 147 } 148 getDrawsBehindNavBar()149 public boolean getDrawsBehindNavBar() { 150 return mBundle.getBoolean(DRAWS_BEHIND_NAV_BAR, false); 151 } 152 getWindowFlags(int defaultFlags)153 public int getWindowFlags(int defaultFlags) { 154 return mBundle.getInt(WINDOW_FLAGS, defaultFlags); 155 } 156 getWindowFlagsMask(int defaultFlags)157 public int getWindowFlagsMask(int defaultFlags) { 158 return mBundle.getInt(WINDOW_FLAGS_MASK, defaultFlags); 159 } 160 getInputViewSystemUiVisibility(int defaultFlags)161 public int getInputViewSystemUiVisibility(int defaultFlags) { 162 return mBundle.getInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, defaultFlags); 163 } 164 isWatermarkEnabled(boolean defaultValue)165 public boolean isWatermarkEnabled(boolean defaultValue) { 166 return mBundle.getBoolean(WATERMARK_ENABLED, defaultValue); 167 } 168 getWatermarkGravity(int defaultValue)169 public int getWatermarkGravity(int defaultValue) { 170 return mBundle.getInt(WATERMARK_GRAVITY, defaultValue); 171 } 172 getHardKeyboardConfigurationBehaviorAllowed(boolean defaultValue)173 public boolean getHardKeyboardConfigurationBehaviorAllowed(boolean defaultValue) { 174 return mBundle.getBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, defaultValue); 175 } 176 getInlineSuggestionsEnabled()177 public boolean getInlineSuggestionsEnabled() { 178 return mBundle.getBoolean(INLINE_SUGGESTIONS_ENABLED); 179 } 180 181 @Nullable getInlineSuggestionViewContentDesc(@ullable String defaultValue)182 public String getInlineSuggestionViewContentDesc(@Nullable String defaultValue) { 183 return mBundle.getString(INLINE_SUGGESTION_VIEW_CONTENT_DESC, defaultValue); 184 } 185 isStrictModeEnabled()186 public boolean isStrictModeEnabled() { 187 return mBundle.getBoolean(STRICT_MODE_ENABLED, false); 188 } 189 isVerifyContextApisInOnCreate()190 public boolean isVerifyContextApisInOnCreate() { 191 return mBundle.getBoolean(VERIFY_CONTEXT_APIS_IN_ON_CREATE, false); 192 } 193 isWindowLayoutInfoCallbackEnabled()194 public boolean isWindowLayoutInfoCallbackEnabled() { 195 return mBundle.getBoolean(WINDOW_LAYOUT_INFO_CALLBACK_ENABLED, false); 196 } 197 isOnBackCallbackEnabled()198 public boolean isOnBackCallbackEnabled() { 199 return mBundle.getBoolean(ON_BACK_CALLBACK_ENABLED, false); 200 } 201 serializeToBundle(@onNull String eventCallbackActionName, @Nullable Builder builder)202 static Bundle serializeToBundle(@NonNull String eventCallbackActionName, 203 @Nullable Builder builder) { 204 final Bundle result = new Bundle(); 205 result.putString(EVENT_CALLBACK_INTENT_ACTION_KEY, eventCallbackActionName); 206 result.putParcelable(DATA_KEY, builder != null ? builder.mBundle : PersistableBundle.EMPTY); 207 return result; 208 } 209 210 /** 211 * The builder class for {@link ImeSettings}. 212 */ 213 public static final class Builder { 214 private final PersistableBundle mBundle = new PersistableBundle(); 215 216 @Nullable 217 InputMethodSubtype[] mAdditionalSubtypes; 218 219 /** 220 * Specifies additional {@link InputMethodSubtype}s to be set before launching 221 * {@link MockIme} by using 222 * {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes( 223 * String, InputMethodSubtype[])}. 224 * 225 * @param subtypes An array of {@link InputMethodSubtype}. 226 * @return this {@link Builder} object 227 */ setAdditionalSubtypes(InputMethodSubtype... subtypes)228 public Builder setAdditionalSubtypes(InputMethodSubtype... subtypes) { 229 mAdditionalSubtypes = subtypes; 230 return this; 231 } 232 233 /** 234 * Controls how MockIme reacts to 235 * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}. 236 * 237 * @param policy one of {@link FullscreenModePolicy} 238 * @see MockIme#onEvaluateFullscreenMode() 239 */ setFullscreenModePolicy(@ullscreenModePolicy int policy)240 public Builder setFullscreenModePolicy(@FullscreenModePolicy int policy) { 241 mBundle.putInt(FULLSCREEN_MODE_POLICY, policy); 242 return this; 243 } 244 245 /** 246 * Sets the background color of the {@link MockIme}. 247 * @param color background color to be used 248 */ setBackgroundColor(@olorInt int color)249 public Builder setBackgroundColor(@ColorInt int color) { 250 mBundle.putInt(BACKGROUND_COLOR_KEY, color); 251 return this; 252 } 253 254 /** 255 * Sets the color to be passed to {@link android.view.Window#setNavigationBarColor(int)}. 256 * 257 * @param color color to be passed to {@link android.view.Window#setNavigationBarColor(int)} 258 * @see android.view.View 259 */ setNavigationBarColor(@olorInt int color)260 public Builder setNavigationBarColor(@ColorInt int color) { 261 mBundle.putInt(NAVIGATION_BAR_COLOR_KEY, color); 262 return this; 263 } 264 265 /** 266 * Sets the input view height measured from the bottom of the screen. 267 * 268 * @param height height of the soft input view. This includes the system window inset such 269 * as navigation bar. 270 */ setInputViewHeight(int height)271 public Builder setInputViewHeight(int height) { 272 mBundle.putInt(INPUT_VIEW_HEIGHT, height); 273 return this; 274 } 275 276 /** 277 * Sets whether IME draws behind navigation bar. 278 */ setDrawsBehindNavBar(boolean drawsBehindNavBar)279 public Builder setDrawsBehindNavBar(boolean drawsBehindNavBar) { 280 mBundle.putBoolean(DRAWS_BEHIND_NAV_BAR, drawsBehindNavBar); 281 return this; 282 } 283 284 /** 285 * Sets window flags to be specified to {@link android.view.Window#setFlags(int, int)} of 286 * the main {@link MockIme} window. 287 * 288 * <p>When {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_OVERSCAN} is set, 289 * {@link MockIme} tries to render the navigation bar by itself.</p> 290 * 291 * @param flags flags to be specified 292 * @param flagsMask mask bits that specify what bits need to be cleared before setting 293 * {@code flags} 294 * @see android.view.WindowManager 295 */ setWindowFlags(int flags, int flagsMask)296 public Builder setWindowFlags(int flags, int flagsMask) { 297 mBundle.putInt(WINDOW_FLAGS, flags); 298 mBundle.putInt(WINDOW_FLAGS_MASK, flagsMask); 299 return this; 300 } 301 302 /** 303 * Sets flags to be specified to {@link android.view.View#setSystemUiVisibility(int)} of 304 * the main soft input view (the returned view from {@link MockIme#onCreateInputView()}). 305 * 306 * @param visibilityFlags flags to be specified 307 * @see android.view.View 308 */ setInputViewSystemUiVisibility(int visibilityFlags)309 public Builder setInputViewSystemUiVisibility(int visibilityFlags) { 310 mBundle.putInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, visibilityFlags); 311 return this; 312 } 313 314 /** 315 * Sets whether a unique watermark image needs to be shown on the software keyboard or not. 316 * 317 * <p>This needs to be enabled to use</p> 318 * 319 * @param enabled {@code true} when such a watermark image is requested. 320 */ setWatermarkEnabled(boolean enabled)321 public Builder setWatermarkEnabled(boolean enabled) { 322 mBundle.putBoolean(WATERMARK_ENABLED, enabled); 323 return this; 324 } 325 326 /** 327 * Sets the {@link android.view.Gravity} flags for the watermark image. 328 * 329 * <p>{@link android.view.Gravity#CENTER} will be used if not set.</p> 330 * 331 * @param gravity {@code true} {@link android.view.Gravity} flags to be set. 332 */ setWatermarkGravity(int gravity)333 public Builder setWatermarkGravity(int gravity) { 334 mBundle.putInt(WATERMARK_GRAVITY, gravity); 335 return this; 336 } 337 338 /** 339 * Controls whether {@link MockIme} is allowed to change the behavior based on 340 * {@link android.content.res.Configuration#keyboard} and 341 * {@link android.content.res.Configuration#hardKeyboardHidden}. 342 * 343 * <p>Methods in {@link android.inputmethodservice.InputMethodService} such as 344 * {@link android.inputmethodservice.InputMethodService#onEvaluateInputViewShown()} and 345 * {@link android.inputmethodservice.InputMethodService#onShowInputRequested(int, boolean)} 346 * change their behaviors when a hardware keyboard is attached. This is confusing when 347 * writing tests so by default {@link MockIme} tries to cancel those behaviors. This 348 * settings re-enables such a behavior.</p> 349 * 350 * @param allowed {@code true} when {@link MockIme} is allowed to change the behavior when 351 * a hardware keyboard is attached 352 * 353 * @see android.inputmethodservice.InputMethodService#onEvaluateInputViewShown() 354 * @see android.inputmethodservice.InputMethodService#onShowInputRequested(int, boolean) 355 */ setHardKeyboardConfigurationBehaviorAllowed(boolean allowed)356 public Builder setHardKeyboardConfigurationBehaviorAllowed(boolean allowed) { 357 mBundle.putBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, allowed); 358 return this; 359 } 360 361 /** 362 * Controls whether inline suggestions are enabled for {@link MockIme}. If enabled, a 363 * suggestion strip will be rendered at the top of the keyboard. 364 * 365 * @param enabled {@code true} when {@link MockIme} is enabled to show inline suggestions. 366 */ setInlineSuggestionsEnabled(boolean enabled)367 public Builder setInlineSuggestionsEnabled(boolean enabled) { 368 mBundle.putBoolean(INLINE_SUGGESTIONS_ENABLED, enabled); 369 return this; 370 } 371 372 /** 373 * Controls whether inline suggestions are enabled for {@link MockIme}. If enabled, a 374 * suggestion strip will be rendered at the top of the keyboard. 375 * 376 * @param contentDesc content description to be set to the inline suggestion View. 377 */ setInlineSuggestionViewContentDesc(@onNull String contentDesc)378 public Builder setInlineSuggestionViewContentDesc(@NonNull String contentDesc) { 379 mBundle.putString(INLINE_SUGGESTION_VIEW_CONTENT_DESC, contentDesc); 380 return this; 381 } 382 383 /** Sets whether to enable {@link android.os.StrictMode} or not. */ setStrictModeEnabled(boolean enabled)384 public Builder setStrictModeEnabled(boolean enabled) { 385 mBundle.putBoolean(STRICT_MODE_ENABLED, enabled); 386 return this; 387 } 388 389 /** 390 * Sets whether to verify below {@link android.content.Context} APIs or not: 391 * <ul> 392 * <li>{@link android.inputmethodservice.InputMethodService#getDisplay}</li> 393 * <li>{@link android.inputmethodservice.InputMethodService#isUiContext}</li> 394 * </ul> 395 */ setVerifyUiContextApisInOnCreate(boolean enabled)396 public Builder setVerifyUiContextApisInOnCreate(boolean enabled) { 397 mBundle.putBoolean(VERIFY_CONTEXT_APIS_IN_ON_CREATE, enabled); 398 return this; 399 } 400 401 /** 402 * Sets whether to enable {@link WindowLayoutInfo} callbacks for {@link MockIme}. 403 */ setWindowLayoutInfoCallbackEnabled(boolean enabled)404 public Builder setWindowLayoutInfoCallbackEnabled(boolean enabled) { 405 mBundle.putBoolean(WINDOW_LAYOUT_INFO_CALLBACK_ENABLED, enabled); 406 return this; 407 } 408 409 /** 410 * Sets whether the IME's 411 * {@link android.content.pm.ApplicationInfo#isOnBackInvokedCallbackEnabled()} 412 * should be set to {@code true}. 413 */ setOnBackCallbackEnabled(boolean enabled)414 public Builder setOnBackCallbackEnabled(boolean enabled) { 415 mBundle.putBoolean(ON_BACK_CALLBACK_ENABLED, enabled); 416 return this; 417 } 418 } 419 } 420