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