xref: /aosp_15_r20/cts/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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