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 
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)248     private void maybeRequestShowInlineSuggestions(int sessionId,
249             @Nullable InlineSuggestionsRequest request,
250             @Nullable List<Dataset> inlineSuggestionsData, @Nullable Bundle clientState,
251             @NonNull AutofillId focusedId, @Nullable AutofillValue focusedValue,
252             @Nullable Function<InlineFillUi, Boolean> inlineSuggestionsCallback,
253             @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback,
254             @Nullable RemoteInlineSuggestionRenderService remoteRenderService,
255             int userId,
256             @NonNull ComponentName targetActivity, @NonNull IBinder targetActivityToken) {
257         if (inlineSuggestionsData == null || inlineSuggestionsData.isEmpty()
258                 || inlineSuggestionsCallback == null || request == null
259                 || remoteRenderService == null) {
260             // If it was an inline request and the response doesn't have any inline suggestions,
261             // we will send an empty response to IME.
262             if (inlineSuggestionsCallback != null && request != null) {
263                 inlineSuggestionsCallback.apply(InlineFillUi.emptyUi(focusedId));
264             }
265             return;
266         }
267         mCallbacks.setLastResponse(sessionId);
268 
269         final String filterText =
270                 focusedValue != null && focusedValue.isText()
271                         ? focusedValue.getTextValue().toString() : null;
272 
273         final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
274                 new InlineFillUi.InlineFillUiInfo(request, focusedId, filterText,
275                         remoteRenderService, userId, sessionId);
276 
277         final InlineFillUi inlineFillUi =
278                 InlineFillUi.forAugmentedAutofill(
279                         inlineFillUiInfo, inlineSuggestionsData,
280                         new InlineFillUi.InlineSuggestionUiCallback() {
281                             @Override
282                             public void autofill(Dataset dataset, int datasetIndex) {
283                                 if (dataset.getAuthentication() != null) {
284                                     mCallbacks.logAugmentedAutofillAuthenticationSelected(sessionId,
285                                             dataset.getId(), clientState);
286                                     final IntentSender action = dataset.getAuthentication();
287                                     final int authenticationId =
288                                             AutofillManager.makeAuthenticationId(
289                                                     Session.AUGMENTED_AUTOFILL_REQUEST_ID,
290                                                     datasetIndex);
291                                     final Intent fillInIntent = new Intent();
292                                     fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE,
293                                             clientState);
294                                     try {
295                                         client.authenticate(sessionId, authenticationId, action,
296                                                 fillInIntent, false);
297                                     } catch (RemoteException e) {
298                                         Slog.w(TAG, "Error starting auth flow");
299                                         inlineSuggestionsCallback.apply(
300                                                 InlineFillUi.emptyUi(focusedId));
301                                     }
302                                     return;
303                                 }
304                                 mCallbacks.logAugmentedAutofillSelected(sessionId,
305                                         dataset.getId(), clientState);
306                                 try {
307                                     final ArrayList<AutofillId> fieldIds = dataset.getFieldIds();
308                                     final ClipData content = dataset.getFieldContent();
309                                     if (content != null) {
310                                         mUriGrantsManager.grantUriPermissions(targetActivity,
311                                                 targetActivityToken, userId, content);
312                                         final AutofillId fieldId = fieldIds.get(0);
313                                         if (sDebug) {
314                                             Slog.d(TAG, "Calling client autofillContent(): "
315                                                     + "id=" + fieldId + ", content=" + content);
316                                         }
317                                         client.autofillContent(sessionId, fieldId, content);
318                                     } else {
319                                         final int size = fieldIds.size();
320                                         final boolean hideHighlight = size == 1
321                                                 && fieldIds.get(0).equals(focusedId);
322                                         if (sDebug) {
323                                             Slog.d(TAG, "Calling client autofill(): "
324                                                     + "ids=" + fieldIds
325                                                     + ", values=" + dataset.getFieldValues());
326                                         }
327                                         client.autofill(
328                                                 sessionId,
329                                                 fieldIds,
330                                                 dataset.getFieldValues(),
331                                                 hideHighlight);
332                                     }
333                                     inlineSuggestionsCallback.apply(
334                                             InlineFillUi.emptyUi(focusedId));
335                                 } catch (RemoteException e) {
336                                     Slog.w(TAG, "Encounter exception autofilling the values");
337                                 }
338                             }
339 
340                             @Override
341                             public void authenticate(int requestId, int datasetIndex) {
342                                 Slog.e(TAG, "authenticate not implemented for augmented autofill");
343                             }
344 
345                             @Override
346                             public void startIntentSender(IntentSender intentSender) {
347                                 try {
348                                     client.startIntentSender(intentSender, new Intent());
349                                 } catch (RemoteException e) {
350                                     Slog.w(TAG, "RemoteException starting intent sender");
351                                 }
352                             }
353 
354                             @Override
355                             public void onError() {
356                                 onErrorCallback.run();
357                             }
358                         });
359 
360         if (inlineSuggestionsCallback.apply(inlineFillUi)) {
361             mCallbacks.logAugmentedAutofillShown(sessionId, clientState);
362         }
363     }
364 
365     @Override
toString()366     public String toString() {
367         return "RemoteAugmentedAutofillService["
368                 + ComponentName.flattenToShortString(mComponentName) + "]";
369     }
370 
371     /**
372      * Called by {@link Session} when it's time to destroy all augmented autofill requests.
373      */
onDestroyAutofillWindowsRequest()374     public void onDestroyAutofillWindowsRequest() {
375         run((s) -> s.onDestroyAllFillWindowsRequest());
376     }
377 
378     public interface RemoteAugmentedAutofillServiceCallbacks
379             extends AbstractRemoteService.VultureCallback<RemoteAugmentedAutofillService> {
resetLastResponse()380         void resetLastResponse();
381 
setLastResponse(int sessionId)382         void setLastResponse(int sessionId);
383 
logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState)384         void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState);
385 
logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId, @Nullable Bundle clientState)386         void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId,
387                 @Nullable Bundle clientState);
388 
logAugmentedAutofillAuthenticationSelected(int sessionId, @Nullable String suggestionId, @Nullable Bundle clientState)389         void logAugmentedAutofillAuthenticationSelected(int sessionId,
390                 @Nullable String suggestionId, @Nullable Bundle clientState);
391     }
392 }
393