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.os.Process; 24 import android.os.RemoteCallback; 25 import android.os.UserHandle; 26 import android.view.inputmethod.InputMethodSubtype; 27 28 import androidx.annotation.ColorInt; 29 import androidx.annotation.IntDef; 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 import androidx.window.extensions.layout.WindowLayoutInfo; 33 34 import java.lang.annotation.Retention; 35 import java.util.Objects; 36 37 /** 38 * An immutable data store to control the behavior of {@link MockIme}. 39 */ 40 public class ImeSettings { 41 42 @NonNull 43 private final String mClientPackageName; 44 45 @NonNull 46 private final String mEventCallbackActionName; 47 48 private static final String EVENT_CALLBACK_INTENT_ACTION_KEY = "eventCallbackActionName"; 49 private static final String CHANNEL_KEY = "channel"; 50 private static final String DATA_KEY = "data"; 51 52 private static final String BACKGROUND_COLOR_KEY = "BackgroundColor"; 53 private static final String NAVIGATION_BAR_COLOR_KEY = "NavigationBarColor"; 54 private static final String INPUT_VIEW_HEIGHT = 55 "InputViewHeightWithoutSystemWindowInset"; 56 private static final String DRAWS_BEHIND_NAV_BAR = "drawsBehindNavBar"; 57 private static final String WINDOW_FLAGS = "WindowFlags"; 58 private static final String WINDOW_FLAGS_MASK = "WindowFlagsMask"; 59 private static final String FULLSCREEN_MODE_POLICY = "FullscreenModePolicy"; 60 private static final String INPUT_VIEW_SYSTEM_UI_VISIBILITY = "InputViewSystemUiVisibility"; 61 private static final String WATERMARK_ENABLED = "WatermarkEnabled"; 62 private static final String WATERMARK_GRAVITY = "WatermarkGravity"; 63 private static final String HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED = 64 "HardKeyboardConfigurationBehaviorAllowed"; 65 private static final String INLINE_SUGGESTIONS_ENABLED = "InlineSuggestionsEnabled"; 66 private static final String INLINE_SUGGESTION_VIEW_CONTENT_DESC = 67 "InlineSuggestionViewContentDesc"; 68 private static final String STRICT_MODE_ENABLED = "StrictModeEnabled"; 69 private static final String VERIFY_CONTEXT_APIS_IN_ON_CREATE = "VerifyContextApisInOnCreate"; 70 private static final String WINDOW_LAYOUT_INFO_CALLBACK_ENABLED = 71 "WindowLayoutInfoCallbackEnabled"; 72 private static final String CONNECTIONLESS_HANDWRITING_ENABLED = 73 "ConnectionlessHandwritingEnabled"; 74 75 /** 76 * Simulate the manifest flag enableOnBackInvokedCallback being true for the IME. 77 */ 78 private static final String ON_BACK_CALLBACK_ENABLED = "onBackCallbackEnabled"; 79 80 private static final String USE_CUSTOM_EXTRACT_TEXT_VIEW = "useCustomExtractTextView"; 81 82 private static final String ZERO_INSETS = "zeroInsets"; 83 84 @NonNull 85 private final PersistableBundle mBundle; 86 private final SessionChannel mChannel; 87 88 @Retention(SOURCE) 89 @IntDef(value = { 90 FullscreenModePolicy.NO_FULLSCREEN, 91 FullscreenModePolicy.FORCE_FULLSCREEN, 92 FullscreenModePolicy.OS_DEFAULT, 93 }) 94 public @interface FullscreenModePolicy { 95 /** 96 * Let {@link MockIme} always return {@code false} from 97 * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}. 98 * 99 * <p>This is chosen to be the default behavior of {@link MockIme} to make CTS tests most 100 * deterministic.</p> 101 */ 102 int NO_FULLSCREEN = 0; 103 104 /** 105 * Let {@link MockIme} always return {@code true} from 106 * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}. 107 * 108 * <p>This can be used to test the behaviors when a full-screen IME is running.</p> 109 */ 110 int FORCE_FULLSCREEN = 1; 111 112 /** 113 * Let {@link MockIme} always return the default behavior of 114 * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}. 115 * 116 * <p>This can be used to test the default behavior of that public API.</p> 117 */ 118 int OS_DEFAULT = 2; 119 } 120 ImeSettings(@onNull String clientPackageName, @NonNull Bundle bundle)121 ImeSettings(@NonNull String clientPackageName, @NonNull Bundle bundle) { 122 mClientPackageName = clientPackageName; 123 mEventCallbackActionName = bundle.getString(EVENT_CALLBACK_INTENT_ACTION_KEY); 124 mBundle = bundle.getParcelable(DATA_KEY); 125 mChannel = new SessionChannel(bundle.getParcelable(CHANNEL_KEY, RemoteCallback.class)); 126 } 127 128 @Nullable getEventCallbackActionName()129 String getEventCallbackActionName() { 130 return mEventCallbackActionName; 131 } 132 getChannel()133 SessionChannel getChannel() { 134 return mChannel; 135 } 136 137 @NonNull getClientPackageName()138 String getClientPackageName() { 139 return mClientPackageName; 140 } 141 142 @FullscreenModePolicy fullscreenModePolicy()143 public int fullscreenModePolicy() { 144 return mBundle.getInt(FULLSCREEN_MODE_POLICY); 145 } 146 147 @ColorInt getBackgroundColor(@olorInt int defaultColor)148 public int getBackgroundColor(@ColorInt int defaultColor) { 149 return mBundle.getInt(BACKGROUND_COLOR_KEY, defaultColor); 150 } 151 hasNavigationBarColor()152 public boolean hasNavigationBarColor() { 153 return mBundle.keySet().contains(NAVIGATION_BAR_COLOR_KEY); 154 } 155 156 @ColorInt getNavigationBarColor()157 public int getNavigationBarColor() { 158 return mBundle.getInt(NAVIGATION_BAR_COLOR_KEY); 159 } 160 getInputViewHeight(int defaultHeight)161 public int getInputViewHeight(int defaultHeight) { 162 return mBundle.getInt(INPUT_VIEW_HEIGHT, defaultHeight); 163 } 164 getDrawsBehindNavBar()165 public boolean getDrawsBehindNavBar() { 166 return mBundle.getBoolean(DRAWS_BEHIND_NAV_BAR, false); 167 } 168 getWindowFlags(int defaultFlags)169 public int getWindowFlags(int defaultFlags) { 170 return mBundle.getInt(WINDOW_FLAGS, defaultFlags); 171 } 172 getWindowFlagsMask(int defaultFlags)173 public int getWindowFlagsMask(int defaultFlags) { 174 return mBundle.getInt(WINDOW_FLAGS_MASK, defaultFlags); 175 } 176 getInputViewSystemUiVisibility(int defaultFlags)177 public int getInputViewSystemUiVisibility(int defaultFlags) { 178 return mBundle.getInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, defaultFlags); 179 } 180 isWatermarkEnabled(boolean defaultValue)181 public boolean isWatermarkEnabled(boolean defaultValue) { 182 return mBundle.getBoolean(WATERMARK_ENABLED, defaultValue); 183 } 184 getWatermarkGravity(int defaultValue)185 public int getWatermarkGravity(int defaultValue) { 186 return mBundle.getInt(WATERMARK_GRAVITY, defaultValue); 187 } 188 getHardKeyboardConfigurationBehaviorAllowed(boolean defaultValue)189 public boolean getHardKeyboardConfigurationBehaviorAllowed(boolean defaultValue) { 190 return mBundle.getBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, defaultValue); 191 } 192 getInlineSuggestionsEnabled()193 public boolean getInlineSuggestionsEnabled() { 194 return mBundle.getBoolean(INLINE_SUGGESTIONS_ENABLED); 195 } 196 197 @Nullable getInlineSuggestionViewContentDesc(@ullable String defaultValue)198 public String getInlineSuggestionViewContentDesc(@Nullable String defaultValue) { 199 return mBundle.getString(INLINE_SUGGESTION_VIEW_CONTENT_DESC, defaultValue); 200 } 201 isStrictModeEnabled()202 public boolean isStrictModeEnabled() { 203 return mBundle.getBoolean(STRICT_MODE_ENABLED, false); 204 } 205 isVerifyContextApisInOnCreate()206 public boolean isVerifyContextApisInOnCreate() { 207 return mBundle.getBoolean(VERIFY_CONTEXT_APIS_IN_ON_CREATE, false); 208 } 209 isWindowLayoutInfoCallbackEnabled()210 public boolean isWindowLayoutInfoCallbackEnabled() { 211 return mBundle.getBoolean(WINDOW_LAYOUT_INFO_CALLBACK_ENABLED, false); 212 } 213 isConnectionlessHandwritingEnabled()214 public boolean isConnectionlessHandwritingEnabled() { 215 return mBundle.getBoolean(CONNECTIONLESS_HANDWRITING_ENABLED, false); 216 } 217 isOnBackCallbackEnabled()218 public boolean isOnBackCallbackEnabled() { 219 return mBundle.getBoolean(ON_BACK_CALLBACK_ENABLED, false); 220 } 221 close()222 public void close() { 223 if (mChannel != null) { 224 mChannel.close(); 225 } 226 } 227 228 /** Whether or not custom extract view hierarchy should be used. */ isCustomExtractTextViewEnabled()229 public boolean isCustomExtractTextViewEnabled() { 230 return mBundle.getBoolean(USE_CUSTOM_EXTRACT_TEXT_VIEW, false); 231 } 232 233 /** Whether the IME should provide zero insets when shown. */ isZeroInsetsEnabled()234 public boolean isZeroInsetsEnabled() { 235 return mBundle.getBoolean(ZERO_INSETS, false); 236 } 237 serializeToBundle(@onNull String eventCallbackActionName, @Nullable Builder builder, @NonNull RemoteCallback channel)238 static Bundle serializeToBundle(@NonNull String eventCallbackActionName, 239 @Nullable Builder builder, @NonNull RemoteCallback channel) { 240 final Bundle result = new Bundle(); 241 result.putString(EVENT_CALLBACK_INTENT_ACTION_KEY, eventCallbackActionName); 242 result.putParcelable(DATA_KEY, builder != null ? builder.mBundle : PersistableBundle.EMPTY); 243 result.putParcelable(CHANNEL_KEY, channel); 244 return result; 245 } 246 247 /** 248 * The builder class for {@link ImeSettings}. 249 */ 250 public static final class Builder { 251 private final PersistableBundle mBundle = new PersistableBundle(); 252 253 @MockImePackageNames 254 @NonNull 255 String mMockImePackageName = MockImePackageNames.MockIme1; 256 257 /** 258 * Specifies a non-default {@link MockIme} package name, which is by default 259 * {@code com.android.cts.mockime}. 260 * 261 * <p>You can use this to interact with multiple {@link MockIme} sessions at the same time. 262 * </p> 263 * 264 * @param packageName One of {@link MockImePackageNames}. 265 * @return this {@link Builder} object 266 */ setMockImePackageName(@ockImePackageNames String packageName)267 public Builder setMockImePackageName(@MockImePackageNames String packageName) { 268 mMockImePackageName = packageName; 269 return this; 270 } 271 272 @NonNull 273 UserHandle mTargetUser = Process.myUserHandle(); 274 275 /** 276 * Specifies a different user than the current user. 277 * 278 * @param targetUser The user whose {@link MockIme} will be connected to. 279 * @return this {@link Builder} object 280 */ setTargetUser(@onNull UserHandle targetUser)281 public Builder setTargetUser(@NonNull UserHandle targetUser) { 282 mTargetUser = Objects.requireNonNull(targetUser); 283 return this; 284 } 285 286 /** 287 * Whether the IME should only be initialized and enabled, but not set as the current IME. 288 */ 289 boolean mSuppressSetIme = false; 290 291 /** 292 * Sets whether the IME should only be initialized and enabled, but not set as 293 * the current IME. 294 */ setSuppressSetIme(boolean suppressSetIme)295 public Builder setSuppressSetIme(boolean suppressSetIme) { 296 mSuppressSetIme = suppressSetIme; 297 return this; 298 } 299 300 boolean mSuppressResetIme = false; 301 302 /** 303 * Specifies whether {@code adb shell ime reset} should be suppressed or not on 304 * {@link MockImeSession#create(android.content.Context)} and 305 * {@link MockImeSession#close()}. 306 * 307 * <p>The default value is {@code false}.</p> 308 * 309 * @param suppressResetIme {@code true} to suppress {@code adb shell ime reset} upon 310 * initialize and cleanup processes of {@link MockImeSession}. 311 * @return this {@link Builder} object 312 */ setSuppressResetIme(boolean suppressResetIme)313 public Builder setSuppressResetIme(boolean suppressResetIme) { 314 mSuppressResetIme = suppressResetIme; 315 return this; 316 } 317 318 @Nullable 319 InputMethodSubtype[] mAdditionalSubtypes; 320 321 /** 322 * Specifies additional {@link InputMethodSubtype}s to be set before launching 323 * {@link MockIme} by using 324 * {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes( 325 * String, InputMethodSubtype[])}. 326 * 327 * @param subtypes An array of {@link InputMethodSubtype}. 328 * @return this {@link Builder} object 329 */ setAdditionalSubtypes(InputMethodSubtype... subtypes)330 public Builder setAdditionalSubtypes(InputMethodSubtype... subtypes) { 331 mAdditionalSubtypes = subtypes; 332 return this; 333 } 334 335 /** 336 * Controls how MockIme reacts to 337 * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}. 338 * 339 * @param policy one of {@link FullscreenModePolicy} 340 * @see MockIme#onEvaluateFullscreenMode() 341 */ setFullscreenModePolicy(@ullscreenModePolicy int policy)342 public Builder setFullscreenModePolicy(@FullscreenModePolicy int policy) { 343 mBundle.putInt(FULLSCREEN_MODE_POLICY, policy); 344 return this; 345 } 346 347 /** 348 * Sets the background color of the {@link MockIme}. 349 * @param color background color to be used 350 */ setBackgroundColor(@olorInt int color)351 public Builder setBackgroundColor(@ColorInt int color) { 352 mBundle.putInt(BACKGROUND_COLOR_KEY, color); 353 return this; 354 } 355 356 /** 357 * Sets the color to be passed to {@link android.view.Window#setNavigationBarColor(int)}. 358 * 359 * @param color color to be passed to {@link android.view.Window#setNavigationBarColor(int)} 360 * @see android.view.View 361 */ setNavigationBarColor(@olorInt int color)362 public Builder setNavigationBarColor(@ColorInt int color) { 363 mBundle.putInt(NAVIGATION_BAR_COLOR_KEY, color); 364 return this; 365 } 366 367 /** 368 * Sets the input view height measured from the bottom of the screen. 369 * 370 * @param height height of the soft input view. This includes the system window inset such 371 * as navigation bar. 372 */ setInputViewHeight(int height)373 public Builder setInputViewHeight(int height) { 374 mBundle.putInt(INPUT_VIEW_HEIGHT, height); 375 return this; 376 } 377 378 /** 379 * Sets whether IME draws behind navigation bar. 380 */ setDrawsBehindNavBar(boolean drawsBehindNavBar)381 public Builder setDrawsBehindNavBar(boolean drawsBehindNavBar) { 382 mBundle.putBoolean(DRAWS_BEHIND_NAV_BAR, drawsBehindNavBar); 383 return this; 384 } 385 386 /** 387 * Sets window flags to be specified to {@link android.view.Window#setFlags(int, int)} of 388 * the main {@link MockIme} window. 389 * 390 * <p>When {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_OVERSCAN} is set, 391 * {@link MockIme} tries to render the navigation bar by itself.</p> 392 * 393 * @param flags flags to be specified 394 * @param flagsMask mask bits that specify what bits need to be cleared before setting 395 * {@code flags} 396 * @see android.view.WindowManager 397 */ setWindowFlags(int flags, int flagsMask)398 public Builder setWindowFlags(int flags, int flagsMask) { 399 mBundle.putInt(WINDOW_FLAGS, flags); 400 mBundle.putInt(WINDOW_FLAGS_MASK, flagsMask); 401 return this; 402 } 403 404 /** 405 * Sets flags to be specified to {@link android.view.View#setSystemUiVisibility(int)} of 406 * the main soft input view (the returned view from {@link MockIme#onCreateInputView()}). 407 * 408 * @param visibilityFlags flags to be specified 409 * @see android.view.View 410 */ setInputViewSystemUiVisibility(int visibilityFlags)411 public Builder setInputViewSystemUiVisibility(int visibilityFlags) { 412 mBundle.putInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, visibilityFlags); 413 return this; 414 } 415 416 /** 417 * Sets whether a unique watermark image needs to be shown on the software keyboard or not. 418 * 419 * <p>This needs to be enabled to use</p> 420 * 421 * @param enabled {@code true} when such a watermark image is requested. 422 */ setWatermarkEnabled(boolean enabled)423 public Builder setWatermarkEnabled(boolean enabled) { 424 mBundle.putBoolean(WATERMARK_ENABLED, enabled); 425 return this; 426 } 427 428 /** 429 * Sets the {@link android.view.Gravity} flags for the watermark image. 430 * 431 * <p>{@link android.view.Gravity#CENTER} will be used if not set.</p> 432 * 433 * @param gravity {@code true} {@link android.view.Gravity} flags to be set. 434 */ setWatermarkGravity(int gravity)435 public Builder setWatermarkGravity(int gravity) { 436 mBundle.putInt(WATERMARK_GRAVITY, gravity); 437 return this; 438 } 439 440 /** 441 * Controls whether {@link MockIme} is allowed to change the behavior based on 442 * {@link android.content.res.Configuration#keyboard} and 443 * {@link android.content.res.Configuration#hardKeyboardHidden}. 444 * 445 * <p>Methods in {@link android.inputmethodservice.InputMethodService} such as 446 * {@link android.inputmethodservice.InputMethodService#onEvaluateInputViewShown()} and 447 * {@link android.inputmethodservice.InputMethodService#onShowInputRequested(int, boolean)} 448 * change their behaviors when a hardware keyboard is attached. This is confusing when 449 * writing tests so by default {@link MockIme} tries to cancel those behaviors. This 450 * settings re-enables such a behavior.</p> 451 * 452 * @param allowed {@code true} when {@link MockIme} is allowed to change the behavior when 453 * a hardware keyboard is attached 454 * 455 * @see android.inputmethodservice.InputMethodService#onEvaluateInputViewShown() 456 * @see android.inputmethodservice.InputMethodService#onShowInputRequested(int, boolean) 457 */ setHardKeyboardConfigurationBehaviorAllowed(boolean allowed)458 public Builder setHardKeyboardConfigurationBehaviorAllowed(boolean allowed) { 459 mBundle.putBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, allowed); 460 return this; 461 } 462 463 /** 464 * Controls whether inline suggestions are enabled for {@link MockIme}. If enabled, a 465 * suggestion strip will be rendered at the top of the keyboard. 466 * 467 * @param enabled {@code true} when {@link MockIme} is enabled to show inline suggestions. 468 */ setInlineSuggestionsEnabled(boolean enabled)469 public Builder setInlineSuggestionsEnabled(boolean enabled) { 470 mBundle.putBoolean(INLINE_SUGGESTIONS_ENABLED, enabled); 471 return this; 472 } 473 474 /** 475 * Controls whether inline suggestions are enabled for {@link MockIme}. If enabled, a 476 * suggestion strip will be rendered at the top of the keyboard. 477 * 478 * @param contentDesc content description to be set to the inline suggestion View. 479 */ setInlineSuggestionViewContentDesc(@onNull String contentDesc)480 public Builder setInlineSuggestionViewContentDesc(@NonNull String contentDesc) { 481 mBundle.putString(INLINE_SUGGESTION_VIEW_CONTENT_DESC, contentDesc); 482 return this; 483 } 484 485 /** Sets whether to enable {@link android.os.StrictMode} or not. */ setStrictModeEnabled(boolean enabled)486 public Builder setStrictModeEnabled(boolean enabled) { 487 mBundle.putBoolean(STRICT_MODE_ENABLED, enabled); 488 return this; 489 } 490 491 /** 492 * Sets whether to verify below {@link android.content.Context} APIs or not: 493 * <ul> 494 * <li>{@link android.inputmethodservice.InputMethodService#getDisplay}</li> 495 * <li>{@link android.inputmethodservice.InputMethodService#isUiContext}</li> 496 * </ul> 497 */ setVerifyUiContextApisInOnCreate(boolean enabled)498 public Builder setVerifyUiContextApisInOnCreate(boolean enabled) { 499 mBundle.putBoolean(VERIFY_CONTEXT_APIS_IN_ON_CREATE, enabled); 500 return this; 501 } 502 503 /** 504 * Sets whether to enable {@link WindowLayoutInfo} callbacks for {@link MockIme}. 505 */ setWindowLayoutInfoCallbackEnabled(boolean enabled)506 public Builder setWindowLayoutInfoCallbackEnabled(boolean enabled) { 507 mBundle.putBoolean(WINDOW_LAYOUT_INFO_CALLBACK_ENABLED, enabled); 508 return this; 509 } 510 511 /** 512 * Sets whether to enable {@link 513 * android.inputmethodservice.InputMethodService#onStartConnectionlessStylusHandwriting}. 514 */ setConnectionlessHandwritingEnabled(boolean enabled)515 public Builder setConnectionlessHandwritingEnabled(boolean enabled) { 516 mBundle.putBoolean(CONNECTIONLESS_HANDWRITING_ENABLED, enabled); 517 return this; 518 } 519 520 /** 521 * Sets whether the IME's 522 * {@link android.content.pm.ApplicationInfo#isOnBackInvokedCallbackEnabled()} 523 * should be set to {@code true}. 524 */ setOnBackCallbackEnabled(boolean enabled)525 public Builder setOnBackCallbackEnabled(boolean enabled) { 526 mBundle.putBoolean(ON_BACK_CALLBACK_ENABLED, enabled); 527 return this; 528 } 529 530 /** Sets whether or not custom extract view hierarchy should be used. */ setCustomExtractTextViewEnabled(boolean enabled)531 public Builder setCustomExtractTextViewEnabled(boolean enabled) { 532 mBundle.putBoolean(USE_CUSTOM_EXTRACT_TEXT_VIEW, enabled); 533 return this; 534 } 535 536 /** 537 * Sets whether {@link android.inputmethodservice.InputMethodService#onComputeInsets} 538 * should return zero insets. 539 */ setZeroInsets(boolean enabled)540 public Builder setZeroInsets(boolean enabled) { 541 mBundle.putBoolean(ZERO_INSETS, enabled); 542 return this; 543 } 544 } 545 } 546