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