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 17 package com.android.server.autofill; 18 19 import static android.service.autofill.augmented.Helper.logResponse; 20 21 import static com.android.server.autofill.Helper.sDebug; 22 import static com.android.server.autofill.Helper.sVerbose; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.UserIdInt; 27 import android.app.AppGlobals; 28 import android.content.ClipData; 29 import android.content.ComponentName; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentSender; 33 import android.content.pm.PackageManager; 34 import android.content.pm.ServiceInfo; 35 import android.os.Bundle; 36 import android.os.Handler; 37 import android.os.IBinder; 38 import android.os.ICancellationSignal; 39 import android.os.RemoteException; 40 import android.os.SystemClock; 41 import android.service.autofill.Dataset; 42 import android.service.autofill.augmented.AugmentedAutofillService; 43 import android.service.autofill.augmented.IAugmentedAutofillService; 44 import android.service.autofill.augmented.IFillCallback; 45 import android.util.Pair; 46 import android.util.Slog; 47 import android.view.autofill.AutofillId; 48 import android.view.autofill.AutofillManager; 49 import android.view.autofill.AutofillValue; 50 import android.view.autofill.IAutoFillManagerClient; 51 import android.view.inputmethod.InlineSuggestionsRequest; 52 53 import com.android.internal.infra.AbstractRemoteService; 54 import com.android.internal.infra.AndroidFuture; 55 import com.android.internal.infra.ServiceConnector; 56 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 57 import com.android.internal.os.IResultReceiver; 58 import com.android.server.autofill.ui.InlineFillUi; 59 60 import java.util.ArrayList; 61 import java.util.List; 62 import java.util.concurrent.CancellationException; 63 import java.util.concurrent.TimeUnit; 64 import java.util.concurrent.TimeoutException; 65 import java.util.concurrent.atomic.AtomicReference; 66 import java.util.function.Function; 67 68 final class RemoteAugmentedAutofillService 69 extends ServiceConnector.Impl<IAugmentedAutofillService> { 70 71 private static final String TAG = RemoteAugmentedAutofillService.class.getSimpleName(); 72 73 private final int mIdleUnbindTimeoutMs; 74 private final int mRequestTimeoutMs; 75 private final ComponentName mComponentName; 76 private final RemoteAugmentedAutofillServiceCallbacks mCallbacks; 77 private final AutofillUriGrantsManager mUriGrantsManager; 78 RemoteAugmentedAutofillService(Context context, int serviceUid, ComponentName serviceName, int userId, RemoteAugmentedAutofillServiceCallbacks callbacks, boolean bindInstantServiceAllowed, boolean verbose, int idleUnbindTimeoutMs, int requestTimeoutMs)79 RemoteAugmentedAutofillService(Context context, int serviceUid, ComponentName serviceName, 80 int userId, RemoteAugmentedAutofillServiceCallbacks callbacks, 81 boolean bindInstantServiceAllowed, boolean verbose, int idleUnbindTimeoutMs, 82 int requestTimeoutMs) { 83 super(context, 84 new Intent(AugmentedAutofillService.SERVICE_INTERFACE).setComponent(serviceName), 85 bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0, 86 userId, IAugmentedAutofillService.Stub::asInterface); 87 mIdleUnbindTimeoutMs = idleUnbindTimeoutMs; 88 mRequestTimeoutMs = requestTimeoutMs; 89 mComponentName = serviceName; 90 mCallbacks = callbacks; 91 mUriGrantsManager = new AutofillUriGrantsManager(serviceUid); 92 93 // Bind right away. 94 connect(); 95 } 96 97 @Nullable getComponentName(@onNull String componentName, @UserIdInt int userId, boolean isTemporary)98 static Pair<ServiceInfo, ComponentName> getComponentName(@NonNull String componentName, 99 @UserIdInt int userId, boolean isTemporary) { 100 int flags = PackageManager.GET_META_DATA; 101 if (!isTemporary) { 102 flags |= PackageManager.MATCH_SYSTEM_ONLY; 103 } 104 105 final ComponentName serviceComponent; 106 ServiceInfo serviceInfo = null; 107 try { 108 serviceComponent = ComponentName.unflattenFromString(componentName); 109 serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, flags, 110 userId); 111 if (serviceInfo == null) { 112 Slog.e(TAG, "Bad service name for flags " + flags + ": " + componentName); 113 return null; 114 } 115 } catch (Exception e) { 116 Slog.e(TAG, "Error getting service info for '" + componentName + "': " + e); 117 return null; 118 } 119 return new Pair<>(serviceInfo, serviceComponent); 120 } 121 getComponentName()122 public ComponentName getComponentName() { 123 return mComponentName; 124 } 125 getAutofillUriGrantsManager()126 public AutofillUriGrantsManager getAutofillUriGrantsManager() { 127 return mUriGrantsManager; 128 } 129 130 @Override // from ServiceConnector.Impl onServiceConnectionStatusChanged( IAugmentedAutofillService service, boolean connected)131 protected void onServiceConnectionStatusChanged( 132 IAugmentedAutofillService service, boolean connected) { 133 try { 134 if (connected) { 135 service.onConnected(sDebug, sVerbose); 136 } else { 137 service.onDisconnected(); 138 } 139 } catch (Exception e) { 140 Slog.w(TAG, 141 "Exception calling onServiceConnectionStatusChanged(" + connected + "): ", e); 142 } 143 } 144 145 @Override // from AbstractRemoteService getAutoDisconnectTimeoutMs()146 protected long getAutoDisconnectTimeoutMs() { 147 return mIdleUnbindTimeoutMs; 148 } 149 150 /** 151 * Called by {@link Session} to request augmented autofill. 152 */ onRequestAutofillLocked(int sessionId, @NonNull IAutoFillManagerClient client, int taskId, @NonNull ComponentName activityComponent, @NonNull IBinder activityToken, @NonNull AutofillId focusedId, @Nullable AutofillValue focusedValue, @Nullable InlineSuggestionsRequest inlineSuggestionsRequest, @Nullable Function<InlineFillUi, Boolean> inlineSuggestionsCallback, @NonNull Runnable onErrorCallback, @Nullable RemoteInlineSuggestionRenderService remoteRenderService, int userId)153 public void onRequestAutofillLocked(int sessionId, @NonNull IAutoFillManagerClient client, 154 int taskId, @NonNull ComponentName activityComponent, @NonNull IBinder activityToken, 155 @NonNull AutofillId focusedId, @Nullable AutofillValue focusedValue, 156 @Nullable InlineSuggestionsRequest inlineSuggestionsRequest, 157 @Nullable Function<InlineFillUi, Boolean> inlineSuggestionsCallback, 158 @NonNull Runnable onErrorCallback, 159 @Nullable RemoteInlineSuggestionRenderService remoteRenderService, int userId) { 160 long requestTime = SystemClock.elapsedRealtime(); 161 AtomicReference<ICancellationSignal> cancellationRef = new AtomicReference<>(); 162 163 postAsync(service -> { 164 AndroidFuture<Void> requestAutofill = new AndroidFuture<>(); 165 // TODO(b/122728762): set cancellation signal, timeout (from both client and service), 166 // cache IAugmentedAutofillManagerClient reference, etc... 167 client.getAugmentedAutofillClient(new IResultReceiver.Stub() { 168 @Override 169 public void send(int resultCode, Bundle resultData) throws RemoteException { 170 final IBinder realClient = resultData 171 .getBinder(AutofillManager.EXTRA_AUGMENTED_AUTOFILL_CLIENT); 172 service.onFillRequest(sessionId, realClient, taskId, activityComponent, 173 focusedId, focusedValue, requestTime, inlineSuggestionsRequest, 174 new IFillCallback.Stub() { 175 @Override 176 public void onSuccess(@Nullable List<Dataset> inlineSuggestionsData, 177 @Nullable Bundle clientState, boolean showingFillWindow) { 178 mCallbacks.resetLastResponse(); 179 maybeRequestShowInlineSuggestions(sessionId, 180 inlineSuggestionsRequest, inlineSuggestionsData, 181 clientState, focusedId, focusedValue, 182 inlineSuggestionsCallback, client, onErrorCallback, 183 remoteRenderService, userId, 184 activityComponent, activityToken); 185 if (!showingFillWindow) { 186 requestAutofill.complete(null); 187 } 188 } 189 190 @Override 191 public boolean isCompleted() { 192 return requestAutofill.isDone() 193 && !requestAutofill.isCancelled(); 194 } 195 196 @Override 197 public void onCancellable(ICancellationSignal cancellation) { 198 if (requestAutofill.isCancelled()) { 199 dispatchCancellation(cancellation); 200 } else { 201 cancellationRef.set(cancellation); 202 } 203 } 204 205 @Override 206 public void cancel() { 207 requestAutofill.cancel(true); 208 } 209 }); 210 } 211 }); 212 return requestAutofill; 213 }).orTimeout(mRequestTimeoutMs, TimeUnit.MILLISECONDS) 214 .whenComplete((res, err) -> { 215 if (err instanceof CancellationException) { 216 dispatchCancellation(cancellationRef.get()); 217 } else if (err instanceof TimeoutException) { 218 Slog.w(TAG, "PendingAutofillRequest timed out (" + mRequestTimeoutMs 219 + "ms) for " + RemoteAugmentedAutofillService.this); 220 // NOTE: so far we don't need notify RemoteAugmentedAutofillServiceCallbacks 221 dispatchCancellation(cancellationRef.get()); 222 if (mComponentName != null) { 223 logResponse(MetricsEvent.TYPE_ERROR, mComponentName.getPackageName(), 224 activityComponent, sessionId, mRequestTimeoutMs); 225 } 226 } else if (err != null) { 227 Slog.e(TAG, "exception handling getAugmentedAutofillClient() for " 228 + sessionId + ": ", err); 229 } else { 230 // NOTE: so far we don't need notify RemoteAugmentedAutofillServiceCallbacks 231 } 232 }); 233 } 234 dispatchCancellation(@ullable ICancellationSignal cancellation)235 void dispatchCancellation(@Nullable ICancellationSignal cancellation) { 236 if (cancellation == null) { 237 return; 238 } 239 Handler.getMain().post(() -> { 240 try { 241 cancellation.cancel(); 242 } catch (RemoteException e) { 243 Slog.e(TAG, "Error requesting a cancellation", e); 244 } 245 }); 246 } 247 248 @SuppressWarnings("ReturnValueIgnored") maybeRequestShowInlineSuggestions(int sessionId, @Nullable InlineSuggestionsRequest request, @Nullable List<Dataset> inlineSuggestionsData, @Nullable Bundle clientState, @NonNull AutofillId focusedId, @Nullable AutofillValue focusedValue, @Nullable Function<InlineFillUi, Boolean> inlineSuggestionsCallback, @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback, @Nullable RemoteInlineSuggestionRenderService remoteRenderService, int userId, @NonNull ComponentName targetActivity, @NonNull IBinder targetActivityToken)249 private void maybeRequestShowInlineSuggestions(int sessionId, 250 @Nullable InlineSuggestionsRequest request, 251 @Nullable List<Dataset> inlineSuggestionsData, @Nullable Bundle clientState, 252 @NonNull AutofillId focusedId, @Nullable AutofillValue focusedValue, 253 @Nullable Function<InlineFillUi, Boolean> inlineSuggestionsCallback, 254 @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback, 255 @Nullable RemoteInlineSuggestionRenderService remoteRenderService, 256 int userId, 257 @NonNull ComponentName targetActivity, @NonNull IBinder targetActivityToken) { 258 if (inlineSuggestionsData == null || inlineSuggestionsData.isEmpty() 259 || inlineSuggestionsCallback == null || request == null 260 || remoteRenderService == null) { 261 // If it was an inline request and the response doesn't have any inline suggestions, 262 // we will send an empty response to IME. 263 if (inlineSuggestionsCallback != null && request != null) { 264 inlineSuggestionsCallback.apply(InlineFillUi.emptyUi(focusedId)); 265 } 266 return; 267 } 268 mCallbacks.setLastResponse(sessionId); 269 270 final String filterText = 271 focusedValue != null && focusedValue.isText() 272 ? focusedValue.getTextValue().toString() : null; 273 274 final InlineFillUi.InlineFillUiInfo inlineFillUiInfo = 275 new InlineFillUi.InlineFillUiInfo(request, focusedId, filterText, 276 remoteRenderService, userId, sessionId); 277 278 final InlineFillUi inlineFillUi = 279 InlineFillUi.forAugmentedAutofill( 280 inlineFillUiInfo, inlineSuggestionsData, 281 new InlineFillUi.InlineSuggestionUiCallback() { 282 @Override 283 public void autofill(Dataset dataset, int datasetIndex) { 284 if (dataset.getAuthentication() != null) { 285 mCallbacks.logAugmentedAutofillAuthenticationSelected(sessionId, 286 dataset.getId(), clientState); 287 final IntentSender action = dataset.getAuthentication(); 288 final int authenticationId = 289 AutofillManager.makeAuthenticationId( 290 Session.AUGMENTED_AUTOFILL_REQUEST_ID, 291 datasetIndex); 292 final Intent fillInIntent = new Intent(); 293 fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, 294 clientState); 295 try { 296 client.authenticate(sessionId, authenticationId, action, 297 fillInIntent, false); 298 } catch (RemoteException e) { 299 Slog.w(TAG, "Error starting auth flow"); 300 inlineSuggestionsCallback.apply( 301 InlineFillUi.emptyUi(focusedId)); 302 } 303 return; 304 } 305 mCallbacks.logAugmentedAutofillSelected(sessionId, 306 dataset.getId(), clientState); 307 try { 308 final ArrayList<AutofillId> fieldIds = dataset.getFieldIds(); 309 final ClipData content = dataset.getFieldContent(); 310 if (content != null) { 311 mUriGrantsManager.grantUriPermissions(targetActivity, 312 targetActivityToken, userId, content); 313 final AutofillId fieldId = fieldIds.get(0); 314 if (sDebug) { 315 Slog.d(TAG, "Calling client autofillContent(): " 316 + "id=" + fieldId + ", content=" + content); 317 } 318 client.autofillContent(sessionId, fieldId, content); 319 } else { 320 final int size = fieldIds.size(); 321 final boolean hideHighlight = size == 1 322 && fieldIds.get(0).equals(focusedId); 323 if (sDebug) { 324 Slog.d(TAG, "Calling client autofill(): " 325 + "ids=" + fieldIds 326 + ", values=" + dataset.getFieldValues()); 327 } 328 client.autofill( 329 sessionId, 330 fieldIds, 331 dataset.getFieldValues(), 332 hideHighlight); 333 } 334 inlineSuggestionsCallback.apply( 335 InlineFillUi.emptyUi(focusedId)); 336 } catch (RemoteException e) { 337 Slog.w(TAG, "Encounter exception autofilling the values"); 338 } 339 } 340 341 @Override 342 public void authenticate(int requestId, int datasetIndex) { 343 Slog.e(TAG, "authenticate not implemented for augmented autofill"); 344 } 345 346 @Override 347 public void startIntentSender(IntentSender intentSender) { 348 try { 349 client.startIntentSender(intentSender, new Intent()); 350 } catch (RemoteException e) { 351 Slog.w(TAG, "RemoteException starting intent sender"); 352 } 353 } 354 355 @Override 356 public void onError() { 357 onErrorCallback.run(); 358 } 359 360 @Override 361 public void onInflate() { 362 /* nothing */ 363 } 364 }); 365 366 if (inlineSuggestionsCallback.apply(inlineFillUi)) { 367 mCallbacks.logAugmentedAutofillShown(sessionId, clientState); 368 } 369 } 370 371 @Override toString()372 public String toString() { 373 return "RemoteAugmentedAutofillService[" 374 + ComponentName.flattenToShortString(mComponentName) + "]"; 375 } 376 377 /** 378 * Called by {@link Session} when it's time to destroy all augmented autofill requests. 379 */ onDestroyAutofillWindowsRequest()380 public void onDestroyAutofillWindowsRequest() { 381 run((s) -> s.onDestroyAllFillWindowsRequest()); 382 } 383 384 public interface RemoteAugmentedAutofillServiceCallbacks 385 extends AbstractRemoteService.VultureCallback<RemoteAugmentedAutofillService> { resetLastResponse()386 void resetLastResponse(); 387 setLastResponse(int sessionId)388 void setLastResponse(int sessionId); 389 logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState)390 void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState); 391 logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId, @Nullable Bundle clientState)392 void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId, 393 @Nullable Bundle clientState); 394 logAugmentedAutofillAuthenticationSelected(int sessionId, @Nullable String suggestionId, @Nullable Bundle clientState)395 void logAugmentedAutofillAuthenticationSelected(int sessionId, 396 @Nullable String suggestionId, @Nullable Bundle clientState); 397 } 398 } 399