1 /*
2  * Copyright (C) 2018 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 package android.view.autofill;
17 
18 import android.os.CancellationSignal;
19 import android.service.autofill.AutofillService;
20 import android.service.autofill.Dataset;
21 import android.service.autofill.FillCallback;
22 import android.service.autofill.FillRequest;
23 import android.service.autofill.FillResponse;
24 import android.service.autofill.SaveCallback;
25 import android.service.autofill.SaveRequest;
26 import android.util.Log;
27 import android.util.Pair;
28 import android.widget.RemoteViews;
29 
30 import androidx.annotation.NonNull;
31 import androidx.annotation.Nullable;
32 
33 import com.android.perftests.autofill.R;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.concurrent.BlockingQueue;
38 import java.util.concurrent.LinkedBlockingQueue;
39 import java.util.concurrent.TimeUnit;
40 
41 /**
42  * An {@link AutofillService} implementation whose replies can be programmed by the test case.
43  */
44 public class MyAutofillService extends AutofillService {
45 
46     private static final String TAG = "MyAutofillService";
47     private static final int TIMEOUT_MS = 5000;
48 
49     private static final String PACKAGE_NAME = "com.android.perftests.autofill";
50     static final String COMPONENT_NAME = PACKAGE_NAME + "/android.view.autofill.MyAutofillService";
51 
52     private static final BlockingQueue<FillRequest> sFillRequests = new LinkedBlockingQueue<>();
53     private static final BlockingQueue<CannedResponse> sCannedResponses =
54             new LinkedBlockingQueue<>();
55 
56     private static boolean sEnabled;
57 
58     /**
59      * Resets the static state associated with the service.
60      */
resetStaticState()61     static void resetStaticState() {
62         sFillRequests.clear();
63         sCannedResponses.clear();
64         sEnabled = false;
65     }
66 
67     /**
68      * Sets whether the service is enabled or not - when disabled, calls to
69      * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} will be ignored.
70      */
setEnabled(boolean enabled)71     static void setEnabled(boolean enabled) {
72         sEnabled = enabled;
73     }
74 
75     /**
76      * Gets the the last {@link FillRequest} passed to
77      * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} or throws an
78      * exception if that method was not called.
79      */
80     @NonNull
getLastFillRequest()81     static FillRequest getLastFillRequest() {
82         FillRequest request = null;
83         try {
84             request = sFillRequests.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
85         } catch (InterruptedException e) {
86             Thread.currentThread().interrupt();
87             throw new IllegalStateException("onFillRequest() interrupted");
88         }
89         if (request == null) {
90             throw new IllegalStateException("onFillRequest() not called in " + TIMEOUT_MS + "ms");
91         }
92         return request;
93     }
94 
95     @Override
onFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback)96     public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
97             FillCallback callback) {
98         try {
99             handleRequest(request, callback);
100         } catch (InterruptedException e) {
101             Thread.currentThread().interrupt();
102             onError("onFillRequest() interrupted", e, callback);
103         } catch (Exception e) {
104             onError("exception on onFillRequest()", e, callback);
105         }
106     }
107 
108 
handleRequest(FillRequest request, FillCallback callback)109     private void handleRequest(FillRequest request, FillCallback callback) throws Exception {
110         if (!sEnabled) {
111             onError("ignoring onFillRequest(): service is disabled", callback);
112             return;
113         }
114         CannedResponse response = sCannedResponses.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
115         if (response == null) {
116             onError("ignoring onFillRequest(): response not set", callback);
117             return;
118         }
119         Dataset.Builder dataset = new Dataset.Builder(newDatasetPresentation("dataset"));
120         boolean hasData = false;
121         if (response.mUsername != null) {
122             hasData = true;
123             dataset.setValue(response.mUsername.first,
124                     AutofillValue.forText(response.mUsername.second));
125         }
126         if (response.mPassword != null) {
127             hasData = true;
128             dataset.setValue(response.mPassword.first,
129                     AutofillValue.forText(response.mPassword.second));
130         }
131         if (hasData) {
132             FillResponse.Builder fillResponse = new FillResponse.Builder();
133             if (response.mIgnoredIds != null) {
134                 fillResponse.setIgnoredIds(response.mIgnoredIds);
135             }
136 
137             callback.onSuccess(fillResponse.addDataset(dataset.build()).build());
138         } else {
139             callback.onSuccess(null);
140         }
141         if (!sFillRequests.offer(request, TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
142             Log.w(TAG, "could not offer request in " + TIMEOUT_MS + "ms");
143         }
144     }
145 
146     @Override
onSaveRequest(SaveRequest request, SaveCallback callback)147     public void onSaveRequest(SaveRequest request, SaveCallback callback) {
148         // No current test should have triggered it...
149         Log.e(TAG, "onSaveRequest() should not have been called");
150         callback.onFailure("onSaveRequest() should not have been called");
151     }
152 
153     static final class CannedResponse {
154         private final Pair<AutofillId, String> mUsername;
155         private final Pair<AutofillId, String> mPassword;
156         private final AutofillId[] mIgnoredIds;
157 
CannedResponse(@onNull Builder builder)158         private CannedResponse(@NonNull Builder builder) {
159             mUsername = builder.mUsername;
160             mPassword = builder.mPassword;
161             mIgnoredIds = builder.mIgnoredIds;
162         }
163 
164         static class Builder {
165             private Pair<AutofillId, String> mUsername;
166             private Pair<AutofillId, String> mPassword;
167             private AutofillId[] mIgnoredIds;
168 
169             @NonNull
setUsername(@onNull AutofillId id, @NonNull String value)170             Builder setUsername(@NonNull AutofillId id, @NonNull String value) {
171                 mUsername = new Pair<>(id, value);
172                 return this;
173             }
174 
175             @NonNull
setPassword(@onNull AutofillId id, @NonNull String value)176             Builder setPassword(@NonNull AutofillId id, @NonNull String value) {
177                 mPassword = new Pair<>(id, value);
178                 return this;
179             }
180 
181             @NonNull
setIgnored(AutofillId... ids)182             Builder setIgnored(AutofillId... ids) {
183                 mIgnoredIds = ids;
184                 return this;
185             }
186 
reply()187             void reply() {
188                 sCannedResponses.add(new CannedResponse(this));
189             }
190         }
191     }
192 
193     /**
194      * Sets the expected canned {@link FillResponse} for the next
195      * {@link AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)}.
196      */
newCannedResponse()197     static CannedResponse.Builder newCannedResponse() {
198         return new CannedResponse.Builder();
199     }
200 
onError(@onNull String msg, @NonNull FillCallback callback)201     private void onError(@NonNull String msg, @NonNull FillCallback callback) {
202         Log.e(TAG, msg);
203         callback.onFailure(msg);
204     }
205 
onError(@onNull String msg, @NonNull Exception e, @NonNull FillCallback callback)206     private void onError(@NonNull String msg, @NonNull Exception e,
207             @NonNull FillCallback callback) {
208         Log.e(TAG, msg, e);
209         callback.onFailure(msg);
210     }
211 
212     @NonNull
newDatasetPresentation(@onNull CharSequence text)213     private static RemoteViews newDatasetPresentation(@NonNull CharSequence text) {
214         RemoteViews presentation =
215                 new RemoteViews(PACKAGE_NAME, R.layout.autofill_dataset_picker_text_only);
216         presentation.setTextViewText(R.id.text, text);
217         return presentation;
218     }
219 }
220