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