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.runWithShellPermissionIdentity; 23 24 import android.content.ComponentName; 25 import android.content.Intent; 26 import android.net.Uri; 27 28 import androidx.annotation.NonNull; 29 import androidx.annotation.Nullable; 30 import androidx.test.platform.app.InstrumentationRegistry; 31 import androidx.test.uiautomator.By; 32 import androidx.test.uiautomator.BySelector; 33 import androidx.test.uiautomator.UiDevice; 34 import androidx.test.uiautomator.Until; 35 36 import java.util.Map; 37 38 /** 39 * Provides constants and utility methods to interact with 40 * {@link android.view.inputmethod.ctstestapp.MainActivity}. 41 */ 42 public final class MockTestActivityUtil { 43 private static final ComponentName TEST_ACTIVITY = new ComponentName( 44 "android.view.inputmethod.ctstestapp", 45 "android.view.inputmethod.ctstestapp.MainActivity"); 46 private static final Uri TEST_ACTIVITY_URI = 47 Uri.parse("https://example.com/android/view/inputmethod/ctstestapp"); 48 49 private static final String ACTION_TRIGGER = "broadcast_action_trigger"; 50 51 /** 52 * A key to be used as the {@code key} of {@link Map} passed as {@code extras} parameter of 53 * {@link #launchSync(boolean, long, Map)}. 54 * 55 * <p>A valid {@code value} is either {@code "true"} or {@code "false"}.</p> 56 */ 57 public static final String EXTRA_KEY_SHOW_DIALOG = 58 "android.view.inputmethod.ctstestapp.EXTRA_KEY_SHOW_DIALOG"; 59 60 /** 61 * A key to be used as the {@code key} of {@link Map} passed as {@code extras} parameter of 62 * {@link #launchSync(boolean, long, Map)}. 63 * 64 * <p>The specified {@code value} will be set to 65 * {@link android.view.inputmethod.EditorInfo#privateImeOptions}.</p> 66 */ 67 public static final String EXTRA_KEY_PRIVATE_IME_OPTIONS = 68 "android.view.inputmethod.ctstestapp.EXTRA_KEY_PRIVATE_IME_OPTIONS"; 69 70 /** 71 * Can be passed to {@link #sendBroadcastAction(String)} to dismiss the dialog box if exists. 72 */ 73 public static final String EXTRA_DISMISS_DIALOG = "extra_dismiss_dialog"; 74 75 /** 76 * Can be passed to {@link #sendBroadcastAction(String)} call 77 * {@link android.view.inputmethod.InputMethodManager#showSoftInput(android.view.View, int)}. 78 */ 79 public static final String EXTRA_SHOW_SOFT_INPUT = "extra_show_soft_input"; 80 81 @NonNull formatStringIntentParam(@onNull Uri uri, Map<String, String> extras)82 private static Uri formatStringIntentParam(@NonNull Uri uri, Map<String, String> extras) { 83 if (extras == null) { 84 return uri; 85 } 86 final Uri.Builder builder = uri.buildUpon(); 87 extras.forEach(builder::appendQueryParameter); 88 return builder.build(); 89 } 90 91 /** 92 * Launches {@link "android.view.inputmethod.ctstestapp.MainActivity"}. 93 * 94 * @param instant {@code true} when the Activity is installed as an instant app. 95 * @param timeout the timeout to wait until the Activity becomes ready. 96 * @return {@link AutoCloseable} object to automatically stop the test Activity package. 97 */ launchSync(boolean instant, long timeout)98 public static AutoCloseable launchSync(boolean instant, long timeout) { 99 return launchSync(instant, timeout, null); 100 } 101 102 /** 103 * Launches {@link "android.view.inputmethod.ctstestapp.MainActivity"}. 104 * 105 * @param instant {@code true} when the Activity is installed as an instant app. 106 * @param timeout the timeout to wait until the Activity becomes ready. 107 * @param extras extra parameters to be passed to the Activity. 108 * @return {@link AutoCloseable} object to automatically stop the test Activity package. 109 */ launchSync(boolean instant, long timeout, @Nullable Map<String, String> extras)110 public static AutoCloseable launchSync(boolean instant, long timeout, 111 @Nullable Map<String, String> extras) { 112 final StringBuilder commandBuilder = new StringBuilder(); 113 if (instant) { 114 // Override app-links domain verification. 115 runShellCommand( 116 String.format("pm set-app-links-user-selection --user cur --package %s true %s", 117 TEST_ACTIVITY.getPackageName(), TEST_ACTIVITY_URI.getHost())); 118 final Uri uri = formatStringIntentParam(TEST_ACTIVITY_URI, extras); 119 commandBuilder.append(String.format("am start -a %s -c %s --activity-clear-task %s", 120 Intent.ACTION_VIEW, Intent.CATEGORY_BROWSABLE, uri.toString())); 121 } else { 122 commandBuilder.append("am start --activity-clear-task -n ") 123 .append(TEST_ACTIVITY.flattenToShortString()); 124 if (extras != null) { 125 extras.forEach((key, value) -> commandBuilder.append(" --es ") 126 .append(key).append(" ").append(value)); 127 } 128 } 129 130 runWithShellPermissionIdentity(() -> { 131 runShellCommand(commandBuilder.toString()); 132 }); 133 UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 134 BySelector activitySelector = By.pkg(TEST_ACTIVITY.getPackageName()).depth(0); 135 uiDevice.wait(Until.hasObject(activitySelector), timeout); 136 137 // Make sure to stop package after test finished for resource reclaim. 138 return () -> TestUtils.forceStopPackage(TEST_ACTIVITY.getPackageName()); 139 } 140 141 /** 142 * Sends a broadcast to {@link "android.view.inputmethod.ctstestapp.MainActivity"}. 143 * 144 * @param extra {@link #EXTRA_DISMISS_DIALOG} or {@link #EXTRA_SHOW_SOFT_INPUT}. 145 */ sendBroadcastAction(String extra)146 public static void sendBroadcastAction(String extra) { 147 final StringBuilder commandBuilder = new StringBuilder(); 148 commandBuilder.append("am broadcast -a ").append(ACTION_TRIGGER).append(" -p ").append( 149 TEST_ACTIVITY.getPackageName()); 150 commandBuilder.append(" -f 0x").append( 151 Integer.toHexString(FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS)); 152 commandBuilder.append(" --ez " + extra + " true"); 153 runWithShellPermissionIdentity(() -> { 154 runShellCommand(commandBuilder.toString()); 155 }); 156 } 157 158 /** 159 * Force-stops {@link "android.view.inputmethod.ctstestapp"} package. 160 */ forceStopPackage()161 public static void forceStopPackage() { 162 TestUtils.forceStopPackage(TEST_ACTIVITY.getPackageName()); 163 } 164 165 /** 166 * @return {@code "android.view.inputmethod.ctstestapp"}. 167 */ getPackageName()168 public static String getPackageName() { 169 return TEST_ACTIVITY.getPackageName(); 170 } 171 } 172