1 /* 2 * Copyright (C) 2019 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.autofillservice.cts.testcore; 18 19 import static android.autofillservice.cts.testcore.Timeouts.CONNECTION_TIMEOUT; 20 import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS; 21 22 import static com.android.compatibility.common.util.ShellUtils.runShellCommand; 23 24 import static com.google.common.truth.Truth.assertWithMessage; 25 26 import android.app.Activity; 27 import android.app.assist.AssistStructure; 28 import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest; 29 import android.content.ComponentName; 30 import android.content.Context; 31 import android.service.autofill.augmented.FillRequest; 32 import android.util.Log; 33 import android.util.Pair; 34 import android.view.autofill.AutofillId; 35 import android.view.autofill.AutofillValue; 36 import android.view.inputmethod.InlineSuggestionsRequest; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 41 import com.android.cts.mockime.MockImeSession; 42 43 import java.util.List; 44 import java.util.Objects; 45 import java.util.concurrent.CountDownLatch; 46 import java.util.concurrent.TimeUnit; 47 48 /** 49 * Helper for common funcionalities. 50 */ 51 public final class AugmentedHelper { 52 53 private static final String TAG = AugmentedHelper.class.getSimpleName(); 54 55 @NonNull getActivityName(@ullable FillRequest request)56 public static String getActivityName(@Nullable FillRequest request) { 57 if (request == null) return "N/A (null request)"; 58 59 final ComponentName componentName = request.getActivityComponent(); 60 if (componentName == null) return "N/A (no component name)"; 61 62 return componentName.flattenToShortString(); 63 } 64 65 /** 66 * Sets the augmented capture service. 67 */ setAugmentedService(@onNull String service, Context context)68 public static void setAugmentedService(@NonNull String service, Context context) { 69 int userId = context.getUserId(); 70 Log.d(TAG, "Setting service to " + service + " for user " + userId); 71 72 runShellCommand("cmd autofill set temporary-augmented-service %d %s %d", userId, 73 service, MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS); 74 } 75 76 /** 77 * Resets the content capture service. 78 */ resetAugmentedService(Context context)79 public static void resetAugmentedService(Context context) { 80 int userId = context.getUserId(); 81 Log.d(TAG, "Resetting back to default service" + " for user " + userId); 82 runShellCommand("cmd autofill set temporary-augmented-service %d", userId); 83 } 84 85 /** 86 * Returns whether MockIme is available. 87 */ mockImeIsAvailable(Context context)88 public static boolean mockImeIsAvailable(Context context) { 89 return MockImeSession.getUnavailabilityReason(context) == null; 90 } 91 assertBasicRequestInfo(@onNull AugmentedFillRequest request, @NonNull Activity activity, @NonNull AutofillId expectedFocusedId, @Nullable AutofillValue expectedFocusedValue)92 public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request, 93 @NonNull Activity activity, @NonNull AutofillId expectedFocusedId, 94 @Nullable AutofillValue expectedFocusedValue) { 95 final boolean hasDefaultInlineRequest = mockImeIsAvailable(activity.getBaseContext()); 96 assertBasicRequestInfo(request, activity, expectedFocusedId, expectedFocusedValue, 97 hasDefaultInlineRequest); 98 } 99 assertBasicRequestInfo(@onNull AugmentedFillRequest request, @NonNull Activity activity, @NonNull AutofillId expectedFocusedId, @Nullable AutofillValue expectedFocusedValue, boolean hasInlineRequest)100 public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request, 101 @NonNull Activity activity, @NonNull AutofillId expectedFocusedId, 102 @Nullable AutofillValue expectedFocusedValue, boolean hasInlineRequest) { 103 Objects.requireNonNull(activity); 104 Objects.requireNonNull(expectedFocusedId); 105 assertWithMessage("no AugmentedFillRequest").that(request).isNotNull(); 106 assertWithMessage("no FillRequest on %s", request).that(request.request).isNotNull(); 107 assertWithMessage("no FillController on %s", request).that(request.controller).isNotNull(); 108 assertWithMessage("no FillCallback on %s", request).that(request.callback).isNotNull(); 109 assertWithMessage("no CancellationSignal on %s", request).that(request.cancellationSignal) 110 .isNotNull(); 111 // NOTE: task id can change, we might need to set it in the activity's onCreate() 112 assertWithMessage("wrong task id on %s", request).that(request.request.getTaskId()) 113 .isEqualTo(activity.getTaskId()); 114 115 final ComponentName actualComponentName = request.request.getActivityComponent(); 116 assertWithMessage("no activity name on %s", request).that(actualComponentName).isNotNull(); 117 assertWithMessage("wrong activity name on %s", request).that(actualComponentName) 118 .isEqualTo(activity.getComponentName()); 119 final AutofillId actualFocusedId = request.request.getFocusedId(); 120 assertWithMessage("no focused id on %s", request).that(actualFocusedId).isNotNull(); 121 assertWithMessage("wrong focused id on %s", request).that(actualFocusedId) 122 .isEqualTo(expectedFocusedId); 123 final AutofillValue actualFocusedValue = request.request.getFocusedValue(); 124 if (expectedFocusedValue != null) { 125 assertWithMessage("no focused value on %s", request).that( 126 actualFocusedValue).isNotNull(); 127 assertAutofillValue(expectedFocusedValue, actualFocusedValue); 128 } else { 129 assertWithMessage("expecting null focused value on %s", request).that( 130 actualFocusedValue).isNull(); 131 } 132 if (expectedFocusedId.isNonVirtual()) { 133 final AssistStructure.ViewNode focusedViewNode = request.request.getFocusedViewNode(); 134 assertWithMessage("no focused view node on %s", request).that( 135 focusedViewNode).isNotNull(); 136 assertWithMessage("wrong autofill id in focused view node %s", focusedViewNode).that( 137 focusedViewNode.getAutofillId()).isEqualTo(expectedFocusedId); 138 assertWithMessage("unexpected autofill value in focused view node %s", 139 focusedViewNode).that(focusedViewNode.getAutofillValue()).isEqualTo( 140 expectedFocusedValue); 141 assertWithMessage("children nodes should not be populated for focused view node %s", 142 focusedViewNode).that( 143 focusedViewNode.getChildCount()).isEqualTo(0); 144 } 145 final InlineSuggestionsRequest inlineRequest = 146 request.request.getInlineSuggestionsRequest(); 147 if (hasInlineRequest) { 148 assertWithMessage("no inline request on %s", request).that(inlineRequest).isNotNull(); 149 } else { 150 assertWithMessage("exist inline request on %s", request).that(inlineRequest).isNull(); 151 } 152 } 153 assertAutofillValue(@onNull AutofillValue expectedValue, @NonNull AutofillValue actualValue)154 public static void assertAutofillValue(@NonNull AutofillValue expectedValue, 155 @NonNull AutofillValue actualValue) { 156 // It only supports text values for now... 157 assertWithMessage("expected value is not text: %s", expectedValue) 158 .that(expectedValue.isText()).isTrue(); 159 assertAutofillValue(expectedValue.getTextValue().toString(), actualValue); 160 } 161 assertAutofillValue(@onNull String expectedValue, @NonNull AutofillValue actualValue)162 public static void assertAutofillValue(@NonNull String expectedValue, 163 @NonNull AutofillValue actualValue) { 164 assertWithMessage("actual value is not text: %s", actualValue) 165 .that(actualValue.isText()).isTrue(); 166 167 assertWithMessage("wrong autofill value").that(actualValue.getTextValue().toString()) 168 .isEqualTo(expectedValue); 169 } 170 171 @NonNull toString(@ullable List<Pair<AutofillId, AutofillValue>> values)172 public static String toString(@Nullable List<Pair<AutofillId, AutofillValue>> values) { 173 if (values == null) return "null"; 174 final StringBuilder string = new StringBuilder("["); 175 final int size = values.size(); 176 for (int i = 0; i < size; i++) { 177 final Pair<AutofillId, AutofillValue> value = values.get(i); 178 string.append(i).append(':').append(value.first).append('=') 179 .append(Helper.toString(value.second)); 180 if (i < size - 1) { 181 string.append(", "); 182 } 183 184 } 185 return string.append(']').toString(); 186 } 187 188 @NonNull toString(@ullable FillRequest request)189 public static String toString(@Nullable FillRequest request) { 190 if (request == null) return "(null request)"; 191 192 final StringBuilder string = 193 new StringBuilder("FillRequest[act=").append(getActivityName(request)) 194 .append(", taskId=").append(request.getTaskId()); 195 196 final AutofillId focusedId = request.getFocusedId(); 197 if (focusedId != null) { 198 string.append(", focusedId=").append(focusedId); 199 } 200 final AutofillValue focusedValue = request.getFocusedValue(); 201 if (focusedValue != null) { 202 string.append(", focusedValue=").append(focusedValue); 203 } 204 205 return string.append(']').toString(); 206 } 207 208 // Used internally by UiBot to assert the UI getContentDescriptionForUi(@onNull AutofillId focusedId)209 public static String getContentDescriptionForUi(@NonNull AutofillId focusedId) { 210 return "ui_for_" + focusedId; 211 } 212 AugmentedHelper()213 private AugmentedHelper() { 214 throw new UnsupportedOperationException("contain static methods only"); 215 } 216 217 /** 218 * Awaits for a latch to be counted down. 219 */ await(@onNull CountDownLatch latch, @NonNull String fmt, @Nullable Object... args)220 public static void await(@NonNull CountDownLatch latch, @NonNull String fmt, 221 @Nullable Object... args) 222 throws InterruptedException { 223 final boolean called = latch.await(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS); 224 if (!called) { 225 throw new IllegalStateException(String.format(fmt, args) 226 + " in " + CONNECTION_TIMEOUT.ms() + "ms"); 227 } 228 } 229 } 230