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