xref: /aosp_15_r20/cts/tests/inputmethod/util/src/android/view/inputmethod/cts/util/MockTestActivityUtil.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2021 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 android.view.inputmethod.cts.util;
18 
19 import static android.content.Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS;
20 
21 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
22 import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
23 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
24 
25 import android.Manifest;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.net.Uri;
30 import android.os.RemoteCallback;
31 import android.os.SystemClock;
32 import android.os.UserHandle;
33 
34 import androidx.annotation.NonNull;
35 import androidx.annotation.Nullable;
36 import androidx.test.platform.app.InstrumentationRegistry;
37 import androidx.test.uiautomator.By;
38 import androidx.test.uiautomator.BySelector;
39 import androidx.test.uiautomator.UiDevice;
40 import androidx.test.uiautomator.Until;
41 
42 import java.util.Map;
43 
44 /**
45  * Provides constants and utility methods to interact with
46  * {@link android.view.inputmethod.ctstestapp.MainActivity}.
47  */
48 public final class MockTestActivityUtil {
49     public static final ComponentName TEST_ACTIVITY = new ComponentName(
50             "android.view.inputmethod.ctstestapp",
51             "android.view.inputmethod.ctstestapp.MainActivity");
52     private static final Uri TEST_ACTIVITY_URI =
53             Uri.parse("https://example.com/android/view/inputmethod/ctstestapp");
54 
55     public static final String ACTION_TRIGGER = "broadcast_action_trigger";
56 
57     /**
58      * A key to be used as the {@code key} of {@link Map} passed as {@code extras} parameter of
59      * {@link #launchSync(boolean, long, Map)}.
60      *
61      * <p>A valid {@code value} is the string representation of an integer.
62      */
63     public static final String EXTRA_SOFT_INPUT_MODE =
64             "android.view.inputmethod.ctstestapp.EXTRA_SOFT_INPUT_MODE";
65 
66     /**
67      * A key to be used as the {@code key} of {@link Map} passed as {@code extras} parameter of
68      * {@link #launchSync(boolean, long, Map)}.
69      *
70      * <p>A valid {@code value} is either {@code "true"} or {@code "false"}.</p>
71      */
72     public static final String EXTRA_KEY_SHOW_DIALOG =
73             "android.view.inputmethod.ctstestapp.EXTRA_KEY_SHOW_DIALOG";
74 
75     /**
76      * A key to be used as the {@code key} of {@link Map} passed as {@code extras} parameter of
77      * {@link #launchSync(boolean, long, Map)}.
78      *
79      * <p>The specified {@code value} will be set to
80      * {@link android.view.inputmethod.EditorInfo#privateImeOptions}.</p>
81      */
82     public static final String EXTRA_KEY_PRIVATE_IME_OPTIONS =
83             "android.view.inputmethod.ctstestapp.EXTRA_KEY_PRIVATE_IME_OPTIONS";
84 
85     /**
86      * Can be passed to {@link #sendBroadcastAction(String)} to dismiss the dialog box if exists.
87      */
88     public static final String EXTRA_DISMISS_DIALOG = "extra_dismiss_dialog";
89 
90     /**
91      * Can be passed to {@link #sendBroadcastAction(String)} call
92      * {@link android.view.inputmethod.InputMethodManager#showSoftInput(android.view.View, int)}.
93      */
94     public static final String EXTRA_SHOW_SOFT_INPUT = "extra_show_soft_input";
95 
96     /**
97      * Can be passed to {@link #sendBroadcastAction(String)} to declare editor as a
98      * {@link android.view.View#setIsHandwritingDelegate(boolean) handwriting delegate}.
99      */
100     public static final String EXTRA_HANDWRITING_DELEGATE = "extra_handwriting_delegate";
101 
102     /**
103      * Can be passed to {@link #sendBroadcastAction(String)} to declare editor as {@link
104      * android.view.View#setHomeScreenHandwritingDelegatorAllowed(boolean)}.
105      */
106     public static final String EXTRA_HOME_HANDWRITING_DELEGATOR_ALLOWED =
107             "extra_home_handwriting_delegator_allowed";
108 
109     /**
110      * Is used by the {@link RemoteCallback} in launchSyncAsUser()
111      */
112     public static final String ACTION_KEY_REPLY_USER_HANDLE =
113             "android.inputmethodservice.cts.ime.ReplyUserHandle";
114     public static final String EXTRA_ON_CREATE_INPUT_CONNECTION_CALLBACK =
115             "extra_on_create_input_connection_callback";
116     public static final String EXTRA_ON_CREATE_USER_HANDLE_SESSION_ID =
117             "extra_on_create_user_handle_session_id";
118 
119     @NonNull
formatStringIntentParam(@onNull Uri uri, Map<String, String> extras)120     private static Uri formatStringIntentParam(@NonNull Uri uri, Map<String, String> extras) {
121         if (extras == null) {
122             return uri;
123         }
124         final Uri.Builder builder = uri.buildUpon();
125         extras.forEach(builder::appendQueryParameter);
126         return builder.build();
127     }
128 
129     /**
130      * Launches {@link "android.view.inputmethod.ctstestapp.MainActivity"}.
131      *
132      * @param instant {@code true} when the Activity is installed as an instant app.
133      * @param timeout the timeout to wait until the Activity becomes ready.
134      * @return {@link AutoCloseable} object to automatically stop the test Activity package.
135      */
launchSync(boolean instant, long timeout)136     public static AutoCloseable launchSync(boolean instant, long timeout) {
137         return launchSync(instant, timeout, null);
138     }
139 
140     /**
141      * Launches {@link "android.view.inputmethod.ctstestapp.MainActivity"}.
142      *
143      * @param instant {@code true} when the Activity is installed as an instant app.
144      * @param timeout the timeout to wait until the Activity becomes ready.
145      * @param extras extra parameters to be passed to the Activity.
146      * @return {@link AutoCloseable} object to automatically stop the test Activity package.
147      */
launchSync(boolean instant, long timeout, @Nullable Map<String, String> extras)148     public static AutoCloseable launchSync(boolean instant, long timeout,
149             @Nullable Map<String, String> extras) {
150         final StringBuilder commandBuilder = new StringBuilder();
151         final int testUserId = UserHandle.myUserId();
152         if (instant) {
153             // Override app-links domain verification.
154             runShellCommandOrThrow(
155                     String.format("pm set-app-links-user-selection --user %d --package %s true %s",
156                             testUserId, TEST_ACTIVITY.getPackageName(),
157                             TEST_ACTIVITY_URI.getHost()));
158             final Uri uri = formatStringIntentParam(TEST_ACTIVITY_URI, extras);
159             commandBuilder.append(String.format("am start -a %s -c %s --activity-clear-task %s",
160                     Intent.ACTION_VIEW, Intent.CATEGORY_BROWSABLE, uri.toString()));
161         } else {
162             commandBuilder.append(
163                     String.format("am start -a %s -n %s --user %d --activity-clear-task",
164                             Intent.ACTION_MAIN, TEST_ACTIVITY.flattenToShortString(), testUserId));
165             if (extras != null) {
166                 extras.forEach((key, value) -> commandBuilder.append(" --es ")
167                         .append(key).append(" ").append(value));
168             }
169         }
170 
171         runWithShellPermissionIdentity(() -> {
172             runShellCommandOrThrow(commandBuilder.toString());
173         });
174         UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
175         BySelector activitySelector = By.pkg(TEST_ACTIVITY.getPackageName()).depth(0);
176         uiDevice.wait(Until.hasObject(activitySelector), timeout);
177 
178         // Make sure to stop package after test finished for resource reclaim.
179         return () -> TestUtils.forceStopPackage(TEST_ACTIVITY.getPackageName());
180     }
181 
182     /**
183      * Launches {@link android.view.inputmethod.ctstestapp.MainActivity}.
184      *
185      * @param userId the user id for which the Activity should be started
186      * @param instant {@code true} when the Activity is installed as an instant app.
187      * @param extras extra parameters to be passed to the Activity.
188      * @return {@link AutoCloseable} object to automatically stop the test Activity package.
189      */
launchAsUser(int userId, boolean instant, @Nullable Map<String, String> extras)190     public static AutoCloseable launchAsUser(int userId, boolean instant,
191             @Nullable Map<String, String> extras) {
192         final StringBuilder commandBuilder = new StringBuilder();
193         if (instant) {
194             // Override app-links domain verification.
195             runShellCommandOrThrow(
196                     String.format("pm set-app-links-user-selection --user %d --package %s true %s",
197                             userId, TEST_ACTIVITY.getPackageName(), TEST_ACTIVITY_URI.getHost()));
198             final Uri uri = formatStringIntentParam(TEST_ACTIVITY_URI, extras);
199             commandBuilder.append(
200                     String.format("am start -a %s -c %s --user %d --activity-clear-task %s",
201                             Intent.ACTION_VIEW, Intent.CATEGORY_BROWSABLE, userId, uri.toString()));
202         } else {
203             commandBuilder.append(
204                     String.format("am start -a %s -n %s --user %d --activity-clear-task",
205                             Intent.ACTION_MAIN, TEST_ACTIVITY.flattenToShortString(), userId));
206             if (extras != null) {
207                 extras.forEach((key, value) -> commandBuilder.append(" --es ")
208                         .append(key).append(" ").append(value));
209             }
210         }
211 
212         runWithShellPermissionIdentity(() -> {
213             runShellCommandOrThrow(commandBuilder.toString());
214         });
215         // Make sure to stop package after test finished for resource reclaim.
216         return () -> TestUtils.forceStopPackage(TEST_ACTIVITY.getPackageName(), userId);
217     }
218 
219     /**
220      * Launches {@link android.view.inputmethod.ctstestapp.MainActivity}.
221      *
222      * @param userId the user id for which the Activity should be started
223      * @param instant {@code true} when the Activity is installed as an instant app.
224      * @param extras extra parameters to be passed to the Activity.
225      * @param onCreateInputConnectionCallback the callback that will be invoked, once the input
226      *                                        connection was created
227      * @return {@link AutoCloseable} object to automatically stop the test Activity package.
228      */
launchSyncAsUser(int userId, boolean instant, @Nullable Map<String, String> extras, RemoteCallback onCreateInputConnectionCallback)229     public static AutoCloseable launchSyncAsUser(int userId, boolean instant,
230             @Nullable Map<String, String> extras, RemoteCallback onCreateInputConnectionCallback) {
231         Context context = InstrumentationRegistry.getInstrumentation().getContext();
232         final Intent intent = new Intent().setClassName(TEST_ACTIVITY.getPackageName(),
233                 TEST_ACTIVITY.getClassName()).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
234         if (extras != null) {
235             extras.forEach(intent::putExtra);
236         }
237         if (onCreateInputConnectionCallback != null) {
238             intent.putExtra(EXTRA_ON_CREATE_INPUT_CONNECTION_CALLBACK,
239                     onCreateInputConnectionCallback);
240             intent.putExtra(EXTRA_ON_CREATE_USER_HANDLE_SESSION_ID,
241                     Long.toString(SystemClock.elapsedRealtimeNanos()));
242         }
243 
244         if (instant) {
245             // Override app-links domain verification.
246             runShellCommand(
247                     String.format("pm set-app-links-user-selection --user %s --package %s true %s",
248                             userId, TEST_ACTIVITY.getPackageName(), TEST_ACTIVITY_URI.getHost()));
249             intent.setAction(Intent.ACTION_VIEW).addCategory(Intent.CATEGORY_BROWSABLE);
250             intent.setData(TEST_ACTIVITY_URI);
251         } else {
252             intent.setAction(Intent.ACTION_MAIN);
253         }
254         runWithShellPermissionIdentity(() -> {
255             context.startActivityAsUser(intent, UserHandle.of(userId));
256         }, Manifest.permission.INTERACT_ACROSS_USERS_FULL);
257 
258         // Make sure to stop package after test finished for resource reclaim.
259         return () -> TestUtils.forceStopPackage(TEST_ACTIVITY.getPackageName(), userId);
260     }
261 
262     /**
263      * Sends a broadcast to {@link "android.view.inputmethod.ctstestapp.MainActivity"}.
264      *
265      * @param extra {@link #EXTRA_DISMISS_DIALOG} or {@link #EXTRA_SHOW_SOFT_INPUT}.
266      */
sendBroadcastAction(String extra)267     public static void sendBroadcastAction(String extra) {
268         final StringBuilder commandBuilder = new StringBuilder();
269         commandBuilder.append("am broadcast -a ").append(ACTION_TRIGGER).append(" -p ").append(
270                 TEST_ACTIVITY.getPackageName());
271         commandBuilder.append(" -f 0x").append(
272                 Integer.toHexString(FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS));
273         commandBuilder.append(" --receiver-registered-only");
274         commandBuilder.append(" --ez " + extra + " true");
275         runWithShellPermissionIdentity(() -> {
276             runShellCommand(commandBuilder.toString());
277         });
278     }
279 
280     /**
281      * Sends a broadcast to {@link android.view.inputmethod.ctstestapp.MainActivity}.
282      *
283      * @param extra {@link #EXTRA_DISMISS_DIALOG} or {@link #EXTRA_SHOW_SOFT_INPUT}.
284      * @param userId The target user ID.
285      */
sendBroadcastAction(String extra, int userId)286     public static void sendBroadcastAction(String extra, int userId) {
287         final StringBuilder commandBuilder = new StringBuilder();
288         commandBuilder.append("am broadcast -a ").append(ACTION_TRIGGER).append(" -p ").append(
289                 TEST_ACTIVITY.getPackageName());
290         commandBuilder.append(" -f 0x").append(
291                 Integer.toHexString(FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS));
292         commandBuilder.append(" --receiver-registered-only");
293         commandBuilder.append(" --user " + userId);
294         commandBuilder.append(" --ez " + extra + " true");
295         runWithShellPermissionIdentity(() -> {
296             runShellCommand(commandBuilder.toString());
297         });
298     }
299 
300     /**
301      * Force-stops {@link "android.view.inputmethod.ctstestapp"} package.
302      */
forceStopPackage()303     public static void forceStopPackage() {
304         TestUtils.forceStopPackage(TEST_ACTIVITY.getPackageName());
305     }
306 
307     /**
308      * @return {@code "android.view.inputmethod.ctstestapp"}.
309      */
getPackageName()310     public static String getPackageName() {
311         return TEST_ACTIVITY.getPackageName();
312     }
313 }
314