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