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