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