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 boolean mSuppressResetIme = false; 287 /** 288 * Specifies whether {@code adb shell ime reset} should be suppressed or not on 289 * {@link MockImeSession#create(android.content.Context)} and 290 * {@link MockImeSession#close()}. 291 * 292 * <p>The default value is {@code false}.</p> 293 * 294 * @param suppressResetIme {@code true} to suppress {@code adb shell ime reset} upon 295 * initialize and cleanup processes of {@link MockImeSession}. 296 * @return this {@link Builder} object 297 */ setSuppressResetIme(boolean suppressResetIme)298 public Builder setSuppressResetIme(boolean suppressResetIme) { 299 mSuppressResetIme = suppressResetIme; 300 return this; 301 } 302 303 @Nullable 304 InputMethodSubtype[] mAdditionalSubtypes; 305 306 /** 307 * Specifies additional {@link InputMethodSubtype}s to be set before launching 308 * {@link MockIme} by using 309 * {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes( 310 * String, InputMethodSubtype[])}. 311 * 312 * @param subtypes An array of {@link InputMethodSubtype}. 313 * @return this {@link Builder} object 314 */ setAdditionalSubtypes(InputMethodSubtype... subtypes)315 public Builder setAdditionalSubtypes(InputMethodSubtype... subtypes) { 316 mAdditionalSubtypes = subtypes; 317 return this; 318 } 319 320 /** 321 * Controls how MockIme reacts to 322 * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}. 323 * 324 * @param policy one of {@link FullscreenModePolicy} 325 * @see MockIme#onEvaluateFullscreenMode() 326 */ setFullscreenModePolicy(@ullscreenModePolicy int policy)327 public Builder setFullscreenModePolicy(@FullscreenModePolicy int policy) { 328 mBundle.putInt(FULLSCREEN_MODE_POLICY, policy); 329 return this; 330 } 331 332 /** 333 * Sets the background color of the {@link MockIme}. 334 * @param color background color to be used 335 */ setBackgroundColor(@olorInt int color)336 public Builder setBackgroundColor(@ColorInt int color) { 337 mBundle.putInt(BACKGROUND_COLOR_KEY, color); 338 return this; 339 } 340 341 /** 342 * Sets the color to be passed to {@link android.view.Window#setNavigationBarColor(int)}. 343 * 344 * @param color color to be passed to {@link android.view.Window#setNavigationBarColor(int)} 345 * @see android.view.View 346 */ setNavigationBarColor(@olorInt int color)347 public Builder setNavigationBarColor(@ColorInt int color) { 348 mBundle.putInt(NAVIGATION_BAR_COLOR_KEY, color); 349 return this; 350 } 351 352 /** 353 * Sets the input view height measured from the bottom of the screen. 354 * 355 * @param height height of the soft input view. This includes the system window inset such 356 * as navigation bar. 357 */ setInputViewHeight(int height)358 public Builder setInputViewHeight(int height) { 359 mBundle.putInt(INPUT_VIEW_HEIGHT, height); 360 return this; 361 } 362 363 /** 364 * Sets whether IME draws behind navigation bar. 365 */ setDrawsBehindNavBar(boolean drawsBehindNavBar)366 public Builder setDrawsBehindNavBar(boolean drawsBehindNavBar) { 367 mBundle.putBoolean(DRAWS_BEHIND_NAV_BAR, drawsBehindNavBar); 368 return this; 369 } 370 371 /** 372 * Sets window flags to be specified to {@link android.view.Window#setFlags(int, int)} of 373 * the main {@link MockIme} window. 374 * 375 * <p>When {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_OVERSCAN} is set, 376 * {@link MockIme} tries to render the navigation bar by itself.</p> 377 * 378 * @param flags flags to be specified 379 * @param flagsMask mask bits that specify what bits need to be cleared before setting 380 * {@code flags} 381 * @see android.view.WindowManager 382 */ setWindowFlags(int flags, int flagsMask)383 public Builder setWindowFlags(int flags, int flagsMask) { 384 mBundle.putInt(WINDOW_FLAGS, flags); 385 mBundle.putInt(WINDOW_FLAGS_MASK, flagsMask); 386 return this; 387 } 388 389 /** 390 * Sets flags to be specified to {@link android.view.View#setSystemUiVisibility(int)} of 391 * the main soft input view (the returned view from {@link MockIme#onCreateInputView()}). 392 * 393 * @param visibilityFlags flags to be specified 394 * @see android.view.View 395 */ setInputViewSystemUiVisibility(int visibilityFlags)396 public Builder setInputViewSystemUiVisibility(int visibilityFlags) { 397 mBundle.putInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, visibilityFlags); 398 return this; 399 } 400 401 /** 402 * Sets whether a unique watermark image needs to be shown on the software keyboard or not. 403 * 404 * <p>This needs to be enabled to use</p> 405 * 406 * @param enabled {@code true} when such a watermark image is requested. 407 */ setWatermarkEnabled(boolean enabled)408 public Builder setWatermarkEnabled(boolean enabled) { 409 mBundle.putBoolean(WATERMARK_ENABLED, enabled); 410 return this; 411 } 412 413 /** 414 * Sets the {@link android.view.Gravity} flags for the watermark image. 415 * 416 * <p>{@link android.view.Gravity#CENTER} will be used if not set.</p> 417 * 418 * @param gravity {@code true} {@link android.view.Gravity} flags to be set. 419 */ setWatermarkGravity(int gravity)420 public Builder setWatermarkGravity(int gravity) { 421 mBundle.putInt(WATERMARK_GRAVITY, gravity); 422 return this; 423 } 424 425 /** 426 * Controls whether {@link MockIme} is allowed to change the behavior based on 427 * {@link android.content.res.Configuration#keyboard} and 428 * {@link android.content.res.Configuration#hardKeyboardHidden}. 429 * 430 * <p>Methods in {@link android.inputmethodservice.InputMethodService} such as 431 * {@link android.inputmethodservice.InputMethodService#onEvaluateInputViewShown()} and 432 * {@link android.inputmethodservice.InputMethodService#onShowInputRequested(int, boolean)} 433 * change their behaviors when a hardware keyboard is attached. This is confusing when 434 * writing tests so by default {@link MockIme} tries to cancel those behaviors. This 435 * settings re-enables such a behavior.</p> 436 * 437 * @param allowed {@code true} when {@link MockIme} is allowed to change the behavior when 438 * a hardware keyboard is attached 439 * 440 * @see android.inputmethodservice.InputMethodService#onEvaluateInputViewShown() 441 * @see android.inputmethodservice.InputMethodService#onShowInputRequested(int, boolean) 442 */ setHardKeyboardConfigurationBehaviorAllowed(boolean allowed)443 public Builder setHardKeyboardConfigurationBehaviorAllowed(boolean allowed) { 444 mBundle.putBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, allowed); 445 return this; 446 } 447 448 /** 449 * Controls whether inline suggestions are enabled for {@link MockIme}. If enabled, a 450 * suggestion strip will be rendered at the top of the keyboard. 451 * 452 * @param enabled {@code true} when {@link MockIme} is enabled to show inline suggestions. 453 */ setInlineSuggestionsEnabled(boolean enabled)454 public Builder setInlineSuggestionsEnabled(boolean enabled) { 455 mBundle.putBoolean(INLINE_SUGGESTIONS_ENABLED, enabled); 456 return this; 457 } 458 459 /** 460 * Controls whether inline suggestions are enabled for {@link MockIme}. If enabled, a 461 * suggestion strip will be rendered at the top of the keyboard. 462 * 463 * @param contentDesc content description to be set to the inline suggestion View. 464 */ setInlineSuggestionViewContentDesc(@onNull String contentDesc)465 public Builder setInlineSuggestionViewContentDesc(@NonNull String contentDesc) { 466 mBundle.putString(INLINE_SUGGESTION_VIEW_CONTENT_DESC, contentDesc); 467 return this; 468 } 469 470 /** Sets whether to enable {@link android.os.StrictMode} or not. */ setStrictModeEnabled(boolean enabled)471 public Builder setStrictModeEnabled(boolean enabled) { 472 mBundle.putBoolean(STRICT_MODE_ENABLED, enabled); 473 return this; 474 } 475 476 /** 477 * Sets whether to verify below {@link android.content.Context} APIs or not: 478 * <ul> 479 * <li>{@link android.inputmethodservice.InputMethodService#getDisplay}</li> 480 * <li>{@link android.inputmethodservice.InputMethodService#isUiContext}</li> 481 * </ul> 482 */ setVerifyUiContextApisInOnCreate(boolean enabled)483 public Builder setVerifyUiContextApisInOnCreate(boolean enabled) { 484 mBundle.putBoolean(VERIFY_CONTEXT_APIS_IN_ON_CREATE, enabled); 485 return this; 486 } 487 488 /** 489 * Sets whether to enable {@link WindowLayoutInfo} callbacks for {@link MockIme}. 490 */ setWindowLayoutInfoCallbackEnabled(boolean enabled)491 public Builder setWindowLayoutInfoCallbackEnabled(boolean enabled) { 492 mBundle.putBoolean(WINDOW_LAYOUT_INFO_CALLBACK_ENABLED, enabled); 493 return this; 494 } 495 496 /** 497 * Sets whether to enable {@link 498 * android.inputmethodservice.InputMethodService#onStartConnectionlessStylusHandwriting}. 499 */ setConnectionlessHandwritingEnabled(boolean enabled)500 public Builder setConnectionlessHandwritingEnabled(boolean enabled) { 501 mBundle.putBoolean(CONNECTIONLESS_HANDWRITING_ENABLED, enabled); 502 return this; 503 } 504 505 /** 506 * Sets whether the IME's 507 * {@link android.content.pm.ApplicationInfo#isOnBackInvokedCallbackEnabled()} 508 * should be set to {@code true}. 509 */ setOnBackCallbackEnabled(boolean enabled)510 public Builder setOnBackCallbackEnabled(boolean enabled) { 511 mBundle.putBoolean(ON_BACK_CALLBACK_ENABLED, enabled); 512 return this; 513 } 514 515 /** Sets whether or not custom extract view hierarchy should be used. */ setCustomExtractTextViewEnabled(boolean enabled)516 public Builder setCustomExtractTextViewEnabled(boolean enabled) { 517 mBundle.putBoolean(USE_CUSTOM_EXTRACT_TEXT_VIEW, enabled); 518 return this; 519 } 520 521 /** 522 * Sets whether {@link android.inputmethodservice.InputMethodService#onComputeInsets} 523 * should return zero insets. 524 */ setZeroInsets(boolean enabled)525 public Builder setZeroInsets(boolean enabled) { 526 mBundle.putBoolean(ZERO_INSETS, enabled); 527 return this; 528 } 529 } 530 } 531