AutofillManagerServiceImpl.java revision f7a7bb6b50cda0d292e55e900f70166e9a1fd6ed
1/*
2 * Copyright (C) 2016 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
17package com.android.server.autofill;
18
19import static android.service.autofill.AutofillService.EXTRA_ACTIVITY_TOKEN;
20import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS;
21import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
22import static android.view.autofill.AutofillManager.FLAG_VIEW_ENTERED;
23import static android.view.autofill.AutofillManager.FLAG_VIEW_EXITED;
24import static android.view.autofill.AutofillManager.FLAG_START_SESSION;
25import static android.view.autofill.AutofillManager.FLAG_VALUE_CHANGED;
26import static android.view.autofill.AutofillManager.FLAG_MANUAL_REQUEST;
27
28import static com.android.server.autofill.Helper.DEBUG;
29import static com.android.server.autofill.Helper.VERBOSE;
30import static com.android.server.autofill.Helper.findValue;
31
32import android.annotation.NonNull;
33import android.annotation.Nullable;
34import android.app.Activity;
35import android.app.ActivityManager;
36import android.app.AppGlobals;
37import android.app.assist.AssistStructure;
38import android.app.assist.AssistStructure.ViewNode;
39import android.app.assist.AssistStructure.WindowNode;
40import android.content.ComponentName;
41import android.content.Context;
42import android.content.Intent;
43import android.content.IntentSender;
44import android.content.pm.ApplicationInfo;
45import android.content.pm.PackageManager;
46import android.content.pm.ServiceInfo;
47import android.graphics.Rect;
48import android.metrics.LogMaker;
49import android.os.Binder;
50import android.os.Bundle;
51import android.os.IBinder;
52import android.os.Looper;
53import android.os.Parcelable;
54import android.os.RemoteCallbackList;
55import android.os.RemoteException;
56import android.provider.Settings;
57import android.service.autofill.AutofillService;
58import android.service.autofill.AutofillServiceInfo;
59import android.service.autofill.Dataset;
60import android.service.autofill.FillResponse;
61import android.service.autofill.IAutoFillService;
62import android.service.autofill.SaveInfo;
63import android.text.TextUtils;
64import android.util.ArrayMap;
65import android.util.LocalLog;
66import android.util.Log;
67import android.util.PrintWriterPrinter;
68import android.util.Slog;
69import android.view.autofill.AutofillId;
70import android.view.autofill.AutofillManager;
71import android.view.autofill.AutofillValue;
72import android.view.autofill.IAutoFillManagerClient;
73import android.view.autofill.AutofillManager.AutofillCallback;
74
75import com.android.internal.annotations.GuardedBy;
76import com.android.internal.logging.MetricsLogger;
77import com.android.internal.logging.nano.MetricsProto;
78import com.android.internal.os.HandlerCaller;
79import com.android.internal.os.IResultReceiver;
80import com.android.server.autofill.ui.AutoFillUI;
81
82import java.io.PrintWriter;
83import java.util.ArrayList;
84import java.util.Map;
85import java.util.Map.Entry;
86
87/**
88 * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the
89 * app's {@link IAutoFillService} implementation.
90 *
91 */
92final class AutofillManagerServiceImpl {
93
94    private static final String TAG = "AutofillManagerServiceImpl";
95
96    private static final int MSG_SERVICE_SAVE = 1;
97
98    private final int mUserId;
99    private final Context mContext;
100    private final Object mLock;
101    private final AutoFillUI mUi;
102    private final MetricsLogger mMetricsLogger = new MetricsLogger();
103
104    private RemoteCallbackList<IAutoFillManagerClient> mClients;
105    private AutofillServiceInfo mInfo;
106
107    private final LocalLog mRequestsHistory;
108
109    private final HandlerCaller.Callback mHandlerCallback = (msg) -> {
110        switch (msg.what) {
111            case MSG_SERVICE_SAVE:
112                handleSessionSave((IBinder) msg.obj);
113                break;
114            default:
115                Slog.w(TAG, "invalid msg on handler: " + msg);
116        }
117    };
118
119    private final HandlerCaller mHandlerCaller = new HandlerCaller(null, Looper.getMainLooper(),
120            mHandlerCallback, true);
121
122    /**
123     * Cache of pending {@link Session}s, keyed by {@code activityToken}.
124     *
125     * <p>They're kept until the {@link AutofillService} finished handling a request, an error
126     * occurs, or the session times out.
127     */
128    // TODO(b/33197203): need to make sure service is bound while callback is pending and/or
129    // use WeakReference
130    @GuardedBy("mLock")
131    private final ArrayMap<IBinder, Session> mSessions = new ArrayMap<>();
132
133    /**
134     * Receiver of assist data from the app's {@link Activity}.
135     */
136    private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
137        @Override
138        public void send(int resultCode, Bundle resultData) throws RemoteException {
139            if (VERBOSE) {
140                Slog.v(TAG, "resultCode on mAssistReceiver: " + resultCode);
141            }
142
143            final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE);
144            if (structure == null) {
145                Slog.wtf(TAG, "no assist structure for id " + resultCode);
146                return;
147            }
148
149            final Bundle receiverExtras = resultData.getBundle(KEY_RECEIVER_EXTRAS);
150            if (receiverExtras == null) {
151                Slog.wtf(TAG, "No " + KEY_RECEIVER_EXTRAS + " on receiver");
152                return;
153            }
154
155            final IBinder activityToken = receiverExtras.getBinder(EXTRA_ACTIVITY_TOKEN);
156            final Session session;
157            synchronized (mLock) {
158                session = mSessions.get(activityToken);
159                if (session == null) {
160                    Slog.w(TAG, "no server session for activityToken " + activityToken);
161                    return;
162                }
163                // TODO(b/33197203): since service is fetching the data (to use for save later),
164                // we should optimize what's sent (for example, remove layout containers,
165                // color / font info, etc...)
166                session.mStructure = structure;
167            }
168
169
170            // TODO(b/33197203, b/33269702): Must fetch the data so it's available later on
171            // handleSave(), even if if the activity is gone by then, but structure.ensureData()
172            // gives a ONE_WAY warning because system_service could block on app calls.
173            // We need to change AssistStructure so it provides a "one-way" writeToParcel()
174            // method that sends all the data
175            structure.ensureData();
176
177            // Sanitize structure before it's sent to service.
178            structure.sanitizeForParceling(true);
179
180            // TODO(b/33197203): Need to pipe the bundle
181            session.mRemoteFillService.onFillRequest(structure, null, session.mFlags);
182        }
183    };
184
185    AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
186            int userId, AutoFillUI ui) {
187        mContext = context;
188        mLock = lock;
189        mRequestsHistory = requestsHistory;
190        mUserId = userId;
191        mUi = ui;
192        updateLocked();
193    }
194
195    CharSequence getServiceName() {
196        if (mInfo == null) {
197            return null;
198        }
199        final ComponentName serviceComponent = mInfo.getServiceInfo().getComponentName();
200        final String packageName = serviceComponent.getPackageName();
201
202        try {
203            final PackageManager pm = mContext.getPackageManager();
204            final ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
205            return pm.getApplicationLabel(info);
206        } catch (Exception e) {
207            Slog.e(TAG, "Could not get label for " + packageName + ": " + e);
208            return packageName;
209        }
210    }
211
212    void updateLocked() {
213        ComponentName serviceComponent = null;
214        ServiceInfo serviceInfo = null;
215        final String componentName = Settings.Secure.getStringForUser(
216                mContext.getContentResolver(), Settings.Secure.AUTOFILL_SERVICE, mUserId);
217        if (!TextUtils.isEmpty(componentName)) {
218            try {
219                serviceComponent = ComponentName.unflattenFromString(componentName);
220                serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
221                        0, mUserId);
222            } catch (RuntimeException | RemoteException e) {
223                Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e);
224                return;
225            }
226        }
227        try {
228            final boolean hadService = hasService();
229            if (serviceInfo != null) {
230                mInfo = new AutofillServiceInfo(mContext.getPackageManager(),
231                        serviceComponent, mUserId);
232            } else {
233                mInfo = null;
234            }
235            if (hadService != hasService()) {
236                if (!hasService()) {
237                    final int sessionCount = mSessions.size();
238                    for (int i = sessionCount - 1; i >= 0; i--) {
239                        final Session session = mSessions.valueAt(i);
240                        session.removeSelfLocked();
241                    }
242                }
243                sendStateToClients();
244            }
245        } catch (PackageManager.NameNotFoundException e) {
246            Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e);
247        }
248    }
249
250    /**
251     * Used by {@link AutofillManagerServiceShellCommand} to request save for the current top app.
252     */
253    void requestSaveForUserLocked(IBinder activityToken) {
254        if (!hasService()) {
255            return;
256        }
257        final Session session = mSessions.get(activityToken);
258        if (session == null) {
259            Slog.w(TAG, "requestSaveForUserLocked(): no session for " + activityToken);
260            return;
261        }
262
263        session.callSaveLocked();
264    }
265
266    boolean addClientLocked(IAutoFillManagerClient client) {
267        if (mClients == null) {
268            mClients = new RemoteCallbackList<>();
269        }
270        mClients.register(client);
271        return hasService();
272    }
273
274    void setAuthenticationResultLocked(Bundle data, IBinder activityToken) {
275        if (!hasService()) {
276            return;
277        }
278        final Session session = mSessions.get(activityToken);
279        if (session != null) {
280            session.setAuthenticationResultLocked(data);
281        }
282    }
283
284    void setHasCallback(IBinder activityToken, boolean hasIt) {
285        if (!hasService()) {
286            return;
287        }
288        final Session session = mSessions.get(activityToken);
289        if (session != null) {
290            session.setHasCallback(hasIt);
291        }
292    }
293
294    void startSessionLocked(@NonNull IBinder activityToken, @Nullable IBinder windowToken,
295            @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, @NonNull Rect bounds,
296            @Nullable AutofillValue value, boolean hasCallback, int flags,
297            @NonNull String packageName) {
298        if (!hasService()) {
299            return;
300        }
301
302        final String historyItem = "s=" + mInfo.getServiceInfo().packageName
303                + " u=" + mUserId + " a=" + activityToken
304
305                + " i=" + autofillId + " b=" + bounds + " hc=" + hasCallback + " f=" + flags;
306        mRequestsHistory.log(historyItem);
307
308        // TODO(b/33197203): Handle partitioning
309        final Session session = mSessions.get(activityToken);
310        if (session != null) {
311            // Already started...
312            return;
313        }
314
315        final Session newSession = createSessionByTokenLocked(activityToken,
316                windowToken, appCallbackToken, hasCallback, flags, packageName);
317        newSession.updateLocked(autofillId, bounds, value, FLAG_START_SESSION);
318    }
319
320    void finishSessionLocked(IBinder activityToken) {
321        if (!hasService()) {
322            return;
323        }
324
325        final Session session = mSessions.get(activityToken);
326        if (session == null) {
327            Slog.w(TAG, "finishSessionLocked(): no session for " + activityToken);
328            return;
329        }
330
331        final boolean finished = session.showSaveLocked();
332        if (DEBUG) {
333            Log.d(TAG, "finishSessionLocked(): session finished on save? " + finished);
334        }
335        if (finished) {
336            session.removeSelf();
337        }
338    }
339
340    void cancelSessionLocked(IBinder activityToken) {
341        if (!hasService()) {
342            return;
343        }
344
345        final Session session = mSessions.get(activityToken);
346        if (session == null) {
347            Slog.w(TAG, "cancelSessionLocked(): no session for " + activityToken);
348            return;
349        }
350        session.removeSelfLocked();
351    }
352
353    private Session createSessionByTokenLocked(@NonNull IBinder activityToken,
354            @Nullable IBinder windowToken, @NonNull IBinder appCallbackToken, boolean hasCallback,
355            int flags, @NonNull String packageName) {
356        final Session newSession = new Session(mContext, activityToken,
357                windowToken, appCallbackToken, hasCallback, flags, packageName);
358        mSessions.put(activityToken, newSession);
359
360        /*
361         * TODO(b/33197203): apply security checks below:
362         * - checks if disabled by secure settings / device policy
363         * - log operation using noteOp()
364         * - check flags
365         * - display disclosure if needed
366         */
367        try {
368            final Bundle receiverExtras = new Bundle();
369            receiverExtras.putBinder(EXTRA_ACTIVITY_TOKEN, activityToken);
370            final long identity = Binder.clearCallingIdentity();
371            try {
372                if (!ActivityManager.getService().requestAutofillData(mAssistReceiver,
373                        receiverExtras, activityToken)) {
374                    Slog.w(TAG, "failed to request autofill data for " + activityToken);
375                }
376            } finally {
377                Binder.restoreCallingIdentity(identity);
378            }
379        } catch (RemoteException e) {
380            // Should not happen, it's a local call.
381        }
382        return newSession;
383    }
384
385    void updateSessionLocked(IBinder activityToken, AutofillId autofillId, Rect bounds,
386            AutofillValue value, int flags) {
387        final Session session = mSessions.get(activityToken);
388        if (session == null) {
389            if (VERBOSE) {
390                Slog.v(TAG, "updateSessionLocked(): session gone for " + activityToken);
391            }
392            return;
393        }
394
395        session.updateLocked(autofillId, bounds, value, flags);
396    }
397
398    private void handleSessionSave(IBinder activityToken) {
399        synchronized (mLock) {
400            final Session session = mSessions.get(activityToken);
401            if (session == null) {
402                Slog.w(TAG, "handleSessionSave(): already gone: " + activityToken);
403
404                return;
405            }
406            session.callSaveLocked();
407        }
408    }
409
410    void destroyLocked() {
411        if (VERBOSE) {
412            Slog.v(TAG, "destroyLocked()");
413        }
414
415        for (Session session : mSessions.values()) {
416            session.destroyLocked();
417        }
418        mSessions.clear();
419    }
420
421    void dumpLocked(String prefix, PrintWriter pw) {
422        final String prefix2 = prefix + "  ";
423
424        pw.print(prefix); pw.print("Component:"); pw.println(mInfo != null
425                ? mInfo.getServiceInfo().getComponentName() : null);
426
427        if (VERBOSE) {
428            // ServiceInfo dump is too noisy and redundant (it can be obtained through other dumps)
429            pw.print(prefix); pw.println("ServiceInfo:");
430            mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), prefix + prefix);
431        }
432
433        final int size = mSessions.size();
434        if (size == 0) {
435            pw.print(prefix); pw.println("No sessions");
436        } else {
437            pw.print(prefix); pw.print(size); pw.println(" sessions:");
438            for (int i = 0; i < size; i++) {
439                pw.print(prefix); pw.print("#"); pw.println(i + 1);
440                mSessions.valueAt(i).dumpLocked(prefix2, pw);
441            }
442        }
443    }
444
445    void destroySessionsLocked() {
446        for (Session session : mSessions.values()) {
447            session.removeSelf();
448        }
449    }
450
451    void listSessionsLocked(ArrayList<String> output) {
452        for (IBinder activityToken : mSessions.keySet()) {
453            output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName()
454                    : null) + ":" + activityToken);
455        }
456    }
457
458    private void sendStateToClients() {
459        final RemoteCallbackList<IAutoFillManagerClient> clients;
460        final int userClientCount;
461        synchronized (mLock) {
462            if (mClients == null) {
463                return;
464            }
465            clients = mClients;
466            userClientCount = clients.beginBroadcast();
467        }
468        try {
469            for (int i = 0; i < userClientCount; i++) {
470                IAutoFillManagerClient client = clients.getBroadcastItem(i);
471                try {
472                    client.setState(hasService());
473                } catch (RemoteException re) {
474                    /* ignore */
475                }
476            }
477        } finally {
478            clients.finishBroadcast();
479        }
480    }
481
482    private boolean hasService() {
483        return mInfo != null;
484    }
485
486    @Override
487    public String toString() {
488        return "AutofillManagerServiceImpl: [userId=" + mUserId
489                + ", component=" + (mInfo != null
490                ? mInfo.getServiceInfo().getComponentName() : null) + "]";
491    }
492
493    /**
494     * State for a given view with a AutofillId.
495     *
496     * <p>This class holds state about a view and calls its listener when the fill UI is ready to
497     * be displayed for the view.
498     */
499    static final class ViewState {
500        interface Listener {
501            /**
502             * Called when the fill UI is ready to be shown for this view.
503             */
504            void onFillReady(ViewState viewState, FillResponse fillResponse, Rect bounds,
505                    AutofillId focusedId, @Nullable AutofillValue value);
506        }
507
508        final AutofillId mId;
509        private final Listener mListener;
510        // TODO(b/33197203): would not need a reference to response and session if it was an inner
511        // class of Session...
512        private final Session mSession;
513        // TODO(b/33197203): encapsulate access so it's not called by UI
514        FillResponse mResponse;
515        Intent mAuthIntent;
516
517        private AutofillValue mAutofillValue;
518        private Rect mBounds;
519
520        private boolean mValueUpdated;
521
522        ViewState(Session session, AutofillId id, Listener listener) {
523            mSession = session;
524            mId = id;
525            mListener = listener;
526        }
527
528        /**
529         * Response should only be set once.
530         */
531        void setResponse(FillResponse response) {
532            mResponse = response;
533            maybeCallOnFillReady();
534        }
535
536        /**
537         * Used when a {@link FillResponse} requires authentication to be unlocked.
538         */
539        void setResponse(FillResponse response, Intent authIntent) {
540            mAuthIntent = authIntent;
541            setResponse(response);
542        }
543
544        CharSequence getServiceName() {
545            return mSession.getServiceName();
546        }
547
548        // TODO(b/33197203): need to refactor / rename / document this method to make it clear that
549        // it can change  the value and update the UI; similarly, should replace code that
550        // directly sets mAutoFilLValue to use encapsulation.
551        void update(@Nullable AutofillValue autofillValue, @Nullable Rect bounds) {
552            if (autofillValue != null) {
553                mAutofillValue = autofillValue;
554            }
555            if (bounds != null) {
556                mBounds = bounds;
557            }
558
559            maybeCallOnFillReady();
560        }
561
562        /**
563         * Calls {@link
564         * Listener#onFillReady(ViewState, FillResponse, Rect, AutofillId, AutofillValue)} if the
565         * fill UI is ready to be displayed (i.e. when response and bounds are set).
566         */
567        void maybeCallOnFillReady() {
568            if (mResponse != null && (mResponse.getAuthentication() != null
569                    || mResponse.getDatasets() != null) && mBounds != null) {
570                mListener.onFillReady(this, mResponse, mBounds, mId, mAutofillValue);
571            }
572        }
573
574        @Override
575        public String toString() {
576            return "ViewState: [id=" + mId + ", value=" + mAutofillValue + ", bounds=" + mBounds
577                    + ", updated = " + mValueUpdated + "]";
578        }
579
580        void dump(String prefix, PrintWriter pw) {
581            pw.print(prefix); pw.print("id:" ); pw.println(mId);
582            pw.print(prefix); pw.print("value:" ); pw.println(mAutofillValue);
583            pw.print(prefix); pw.print("updated:" ); pw.println(mValueUpdated);
584            pw.print(prefix); pw.print("bounds:" ); pw.println(mBounds);
585            pw.print(prefix); pw.print("authIntent:" ); pw.println(mAuthIntent);
586        }
587    }
588
589    /**
590     * A session for a given activity.
591     *
592     * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track
593     * of the current {@link ViewState} to display the appropriate UI.
594     *
595     * <p>Although the autofill requests and callbacks are stateless from the service's point of
596     * view, we need to keep state in the framework side for cases such as authentication. For
597     * example, when service return a {@link FillResponse} that contains all the fields needed
598     * to fill the activity but it requires authentication first, that response need to be held
599     * until the user authenticates or it times out.
600     */
601    // TODO(b/33197203): make sure sessions are removed (and tested by CTS):
602    // - On all authentication scenarios.
603    // - When user does not interact back after a while.
604    // - When service is unbound.
605    final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
606            AutoFillUI.AutoFillUiCallback {
607        private final IBinder mActivityToken;
608        private final IBinder mWindowToken;
609
610        /** Package name of the app that is auto-filled */
611        @NonNull private final String mPackageName;
612
613        @GuardedBy("mLock")
614        private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>();
615
616        @GuardedBy("mLock")
617        @Nullable
618        private ViewState mCurrentViewState;
619
620        private final IAutoFillManagerClient mClient;
621
622        @GuardedBy("mLock")
623        RemoteFillService mRemoteFillService;
624
625        // TODO(b/33197203): Get a response per view instead of per activity.
626        @GuardedBy("mLock")
627        private FillResponse mCurrentResponse;
628
629        /**
630         * Used to remember which {@link Dataset} filled the session.
631         */
632        // TODO(b/33197203): might need more than one once we support partitions
633        @GuardedBy("mLock")
634        private Dataset mAutoFilledDataset;
635
636        /**
637         * Assist structure sent by the app; it will be updated (sanitized, change values for save)
638         * before sent to {@link AutofillService}.
639         */
640        @GuardedBy("mLock")
641        private AssistStructure mStructure;
642
643        /**
644         * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}.
645         */
646        private boolean mHasCallback;
647
648        /**
649         * Flags used to start the session.
650         */
651        private int mFlags;
652        private Session(@NonNull Context context, @NonNull IBinder activityToken,
653                @Nullable IBinder windowToken, @NonNull IBinder client, boolean hasCallback,
654                int flags, @NonNull String packageName) {
655            mRemoteFillService = new RemoteFillService(context,
656                    mInfo.getServiceInfo().getComponentName(), mUserId, this);
657            mActivityToken = activityToken;
658            mWindowToken = windowToken;
659            mHasCallback = hasCallback;
660            mFlags = flags;
661            mPackageName = packageName;
662
663            mClient = IAutoFillManagerClient.Stub.asInterface(client);
664            try {
665                client.linkToDeath(() -> {
666                    if (DEBUG) {
667                        Slog.d(TAG, "app binder died");
668                    }
669
670                    removeSelf();
671                }, 0);
672            } catch (RemoteException e) {
673                Slog.w(TAG, "linkToDeath() on mClient failed: " + e);
674            }
675
676            mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName);
677        }
678
679        // FillServiceCallbacks
680        @Override
681        public void onFillRequestSuccess(@Nullable FillResponse response,
682                @NonNull String servicePackageName) {
683            if (response == null) {
684                // Nothing to be done, but need to notify client.
685                notifyUnavailableToClient();
686                removeSelf();
687                return;
688            }
689
690            if ((response.getDatasets() == null || response.getDatasets().isEmpty())
691                            && response.getAuthentication() == null) {
692                // Response is "empty" from an UI point of view, need to notify client.
693                notifyUnavailableToClient();
694            }
695            synchronized (mLock) {
696                processResponseLocked(response);
697            }
698
699            LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_REQUEST))
700                    .setType(MetricsProto.MetricsEvent.TYPE_SUCCESS)
701                    .setPackageName(mPackageName)
702                    .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
703                            response.getDatasets() == null ? 0 : response.getDatasets().size())
704                    .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE,
705                            servicePackageName);
706            mMetricsLogger.write(log);
707        }
708
709        // FillServiceCallbacks
710        @Override
711        public void onFillRequestFailure(@Nullable CharSequence message,
712                @NonNull String servicePackageName) {
713            LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_REQUEST))
714                    .setType(MetricsProto.MetricsEvent.TYPE_FAILURE)
715                    .setPackageName(mPackageName)
716                    .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE,
717                            servicePackageName);
718            mMetricsLogger.write(log);
719
720            getUiForShowing().showError(message);
721            removeSelf();
722        }
723
724        // FillServiceCallbacks
725        @Override
726        public void onSaveRequestSuccess(@NonNull String servicePackageName) {
727            LogMaker log = (new LogMaker(
728                    MetricsProto.MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
729                    .setType(MetricsProto.MetricsEvent.TYPE_SUCCESS)
730                    .setPackageName(mPackageName)
731                    .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE,
732                            servicePackageName);
733            mMetricsLogger.write(log);
734
735            // Nothing left to do...
736            removeSelf();
737        }
738
739        // FillServiceCallbacks
740        @Override
741        public void onSaveRequestFailure(@Nullable CharSequence message,
742                @NonNull String servicePackageName) {
743            LogMaker log = (new LogMaker(
744                    MetricsProto.MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
745                    .setType(MetricsProto.MetricsEvent.TYPE_FAILURE)
746                    .setPackageName(mPackageName)
747                    .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE,
748                            servicePackageName);
749            mMetricsLogger.write(log);
750
751            getUiForShowing().showError(message);
752            removeSelf();
753        }
754
755        // FillServiceCallbacks
756        @Override
757        public void authenticate(IntentSender intent) {
758            final Intent fillInIntent;
759            synchronized (mLock) {
760                fillInIntent = createAuthFillInIntent(mStructure);
761            }
762            mHandlerCaller.getHandler().post(() -> startAuthentication(intent, fillInIntent));
763        }
764
765        // FillServiceCallbacks
766        @Override
767        public void onDisableSelf() {
768            final long identity = Binder.clearCallingIdentity();
769            try {
770                final String autoFillService = Settings.Secure.getStringForUser(
771                        mContext.getContentResolver(),
772                        Settings.Secure.AUTOFILL_SERVICE, mUserId);
773                if (mInfo.getServiceInfo().getComponentName().equals(
774                        ComponentName.unflattenFromString(autoFillService))) {
775                    Settings.Secure.putStringForUser(mContext.getContentResolver(),
776                            Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
777                }
778            } finally {
779                Binder.restoreCallingIdentity(identity);
780            }
781            synchronized (mLock) {
782                removeSelfLocked();
783            }
784        }
785
786        // FillServiceCallbacks
787        @Override
788        public void onServiceDied(RemoteFillService service) {
789            // TODO(b/33197203): implement
790        }
791
792        // AutoFillUiCallback
793        @Override
794        public void fill(Dataset dataset) {
795            mHandlerCaller.getHandler().post(() -> autoFill(dataset));
796        }
797
798        // AutoFillUiCallback
799        @Override
800        public void save() {
801            mHandlerCaller.getHandler().obtainMessage(MSG_SERVICE_SAVE, mActivityToken)
802                    .sendToTarget();
803        }
804
805        // AutoFillUiCallback
806        @Override
807        public void cancelSave() {
808            mHandlerCaller.getHandler().post(() -> removeSelf());
809        }
810
811        // AutoFillUiCallback
812        @Override
813        public void onEvent(AutofillId id, int event) {
814            mHandlerCaller.getHandler().post(() -> notifyChangeToClient(id, event));
815        }
816
817        public void setAuthenticationResultLocked(Bundle data) {
818            if (mCurrentResponse == null || data == null) {
819                removeSelf();
820            } else {
821                Parcelable result = data.getParcelable(
822                        AutofillManager.EXTRA_AUTHENTICATION_RESULT);
823                if (result instanceof FillResponse) {
824                    mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_AUTHENTICATED,
825                            mPackageName);
826
827                    mCurrentResponse = (FillResponse) result;
828                    processResponseLocked(mCurrentResponse);
829                } else if (result instanceof Dataset) {
830                    Dataset dataset = (Dataset) result;
831                    mCurrentResponse.getDatasets().remove(mAutoFilledDataset);
832                    mCurrentResponse.getDatasets().add(dataset);
833                    mAutoFilledDataset = dataset;
834                    processResponseLocked(mCurrentResponse);
835                }
836            }
837        }
838
839        public void setHasCallback(boolean hasIt) {
840            mHasCallback = hasIt;
841        }
842
843        /**
844         * Shows the save UI, when session can be saved.
845         *
846         * @return {@code true} if session is done, or {@code false} if it's pending user action.
847         */
848        public boolean showSaveLocked() {
849            if (mStructure == null) {
850                Slog.wtf(TAG, "showSaveLocked(): no mStructure");
851                return true;
852            }
853            if (mCurrentResponse == null) {
854                // Happens when the activity / session was finished before the service replied, or
855                // when the service cannot autofill it (and returned a null response).
856                if (DEBUG) {
857                    Slog.d(TAG, "showSaveLocked(): no mCurrentResponse");
858                }
859                return true;
860            }
861            final SaveInfo saveInfo = mCurrentResponse.getSaveInfo();
862            if (DEBUG) {
863                Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo);
864            }
865
866            /*
867             * The Save dialog is only shown if all conditions below are met:
868             *
869             * - saveInfo is not null
870             * - autofillValue of all required ids is not null
871             * - autofillValue of at least one id (required or optional) has changed.
872             */
873
874            if (saveInfo == null) {
875                return true;
876            }
877
878            final AutofillId[] requiredIds = saveInfo.getRequiredIds();
879            if (requiredIds == null || requiredIds.length == 0) {
880                Slog.w(TAG, "showSaveLocked(): no required ids on saveInfo");
881                return true;
882            }
883
884            boolean allRequiredAreNotEmpty = true;
885            boolean atLeastOneChanged = false;
886            for (int i = 0; i < requiredIds.length; i++) {
887                final AutofillId id = requiredIds[i];
888                final ViewState state = mViewStates.get(id);
889                if (state == null || state.mAutofillValue == null
890                         || state.mAutofillValue.isEmpty()) {
891                    final ViewNode node = findViewNodeByIdLocked(id);
892                    if (node == null) {
893                        Slog.w(TAG, "Service passed invalid id on SavableInfo: " + id);
894                        allRequiredAreNotEmpty = false;
895                        break;
896                    }
897                    final AutofillValue initialValue = node.getAutofillValue();
898                    if (initialValue == null || initialValue.isEmpty()) {
899                        if (DEBUG) {
900                            Slog.d(TAG, "finishSessionLocked(): empty initial value for " + id );
901                        }
902                        allRequiredAreNotEmpty = false;
903                        break;
904                    }
905                }
906                if (state.mValueUpdated) {
907                    final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
908                    if (!state.mAutofillValue.equals(filledValue)) {
909                        if (DEBUG) {
910                            Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": "
911                                    + filledValue + " => " + state.mAutofillValue);
912                        }
913                        atLeastOneChanged = true;
914                    }
915                } else {
916                    if (state.mAutofillValue == null || state.mAutofillValue.isEmpty()) {
917                        if (DEBUG) {
918                            Slog.d(TAG, "finishSessionLocked(): empty value for " + id + ": "
919                                    + state.mAutofillValue);
920                        }
921                        allRequiredAreNotEmpty = false;
922                        break;
923
924                    }
925                }
926            }
927
928            if (allRequiredAreNotEmpty) {
929                if (!atLeastOneChanged && saveInfo.getOptionalIds() != null) {
930                    for (int i = 0; i < saveInfo.getOptionalIds().length; i++) {
931                        final AutofillId id = saveInfo.getOptionalIds()[i];
932                        final ViewState state = mViewStates.get(id);
933                        if (state != null && state.mAutofillValue != null && state.mValueUpdated) {
934                            final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
935                            if (!state.mAutofillValue.equals(filledValue)) {
936                                if (DEBUG) {
937                                    Slog.d(TAG, "finishSessionLocked(): found a change on optional "
938                                            + id + ": " + filledValue + " => "
939                                            + state.mAutofillValue);
940                                }
941                                atLeastOneChanged = true;
942                                break;
943                            }
944                        }
945                    }
946                }
947                if (atLeastOneChanged) {
948                    getUiForShowing().showSaveUi(
949                            mInfo.getServiceInfo().loadLabel(mContext.getPackageManager()),
950                            saveInfo, mPackageName);
951                    return false;
952                }
953            }
954            // Nothing changed...
955            if (DEBUG) {
956                Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities."
957                        + "allRequiredAreNotNull=" + allRequiredAreNotEmpty
958                        + ", atLeastOneChanged=" + atLeastOneChanged);
959            }
960            return true;
961        }
962
963        /**
964         * Calls service when user requested save.
965         */
966        private void callSaveLocked() {
967            if (DEBUG) {
968                Slog.d(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
969            }
970
971            final Bundle extras = this.mCurrentResponse.getExtras();
972
973            for (Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
974                final AutofillValue value = entry.getValue().mAutofillValue;
975                if (value == null) {
976                    if (VERBOSE) {
977                        Slog.v(TAG, "callSaveLocked(): skipping " + entry.getKey());
978                    }
979                    continue;
980                }
981                final AutofillId id = entry.getKey();
982                final ViewNode node = findViewNodeByIdLocked(id);
983                if (node == null) {
984                    Slog.w(TAG, "callSaveLocked(): did not find node with id " + id);
985                    continue;
986                }
987                if (VERBOSE) {
988                    Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
989                }
990
991                node.updateAutofillValue(value);
992            }
993
994            // Sanitize structure before it's sent to service.
995            mStructure.sanitizeForParceling(false);
996
997            if (VERBOSE) {
998                Slog.v(TAG, "Dumping " + mStructure + " before calling service.save()");
999                mStructure.dump();
1000            }
1001
1002            mRemoteFillService.onSaveRequest(mStructure, extras);
1003        }
1004
1005        void updateLocked(AutofillId id, Rect bounds, AutofillValue value, int flags) {
1006            if (mAutoFilledDataset != null && (flags & FLAG_VALUE_CHANGED) == 0) {
1007                // TODO(b/33197203): ignoring because we don't support partitions yet
1008                Slog.d(TAG, "updateLocked(): ignoring " + flags + " after app was autofilled");
1009                return;
1010            }
1011
1012            ViewState viewState = mViewStates.get(id);
1013            if (viewState == null) {
1014                viewState = new ViewState(this, id, this);
1015                mViewStates.put(id, viewState);
1016            }
1017
1018            if ((flags & FLAG_START_SESSION) != 0) {
1019                // View is triggering autofill.
1020                mCurrentViewState = viewState;
1021                viewState.update(value, bounds);
1022                return;
1023            }
1024
1025            if ((flags & FLAG_VALUE_CHANGED) != 0) {
1026                if (value != null && !value.equals(viewState.mAutofillValue)) {
1027                    viewState.mValueUpdated = true;
1028
1029                    // Must check if this update was caused by autofilling the view, in which
1030                    // case we just update the value, but not the UI.
1031                    if (mAutoFilledDataset != null) {
1032                        final AutofillValue filledValue = findValue(mAutoFilledDataset, id);
1033                        if (value.equals(filledValue)) {
1034                            viewState.mAutofillValue = value;
1035                            return;
1036                        }
1037                    }
1038
1039                    // Change value
1040                    viewState.mAutofillValue = value;
1041
1042                    // Update the chooser UI
1043                    if (value.isText()) {
1044                        getUiForShowing().filterFillUi(value.getTextValue().toString());
1045                    } else {
1046                        getUiForShowing().filterFillUi(null);
1047                    }
1048                }
1049
1050                return;
1051            }
1052
1053            if ((flags & FLAG_VIEW_ENTERED) != 0) {
1054                // Remove the UI if the ViewState has changed.
1055                if (mCurrentViewState != viewState) {
1056                    mUi.hideFillUi(mCurrentViewState != null ? mCurrentViewState.mId : null);
1057                    mCurrentViewState = viewState;
1058                }
1059
1060                // If the ViewState is ready to be displayed, onReady() will be called.
1061                viewState.update(value, bounds);
1062
1063                // TODO(b/33197203): Remove when there is a response per activity.
1064                if (mCurrentResponse != null) {
1065                    viewState.setResponse(mCurrentResponse);
1066                }
1067
1068                return;
1069            }
1070
1071            if ((flags & FLAG_VIEW_EXITED) != 0) {
1072                if (mCurrentViewState == viewState) {
1073                    mUi.hideFillUi(viewState.mId);
1074                    mCurrentViewState = null;
1075                }
1076                return;
1077            }
1078
1079            Slog.w(TAG, "updateLocked(): unknown flags " + flags);
1080        }
1081
1082        @Override
1083        public void onFillReady(ViewState viewState, FillResponse response, Rect bounds,
1084                AutofillId filledId, @Nullable AutofillValue value) {
1085            String filterText = null;
1086            if (value != null && value.isText()) {
1087                filterText = value.getTextValue().toString();
1088            }
1089
1090            getUiForShowing().showFillUi(filledId, response, bounds, filterText, mPackageName);
1091        }
1092
1093        private void notifyChangeToClient(AutofillId id, int event) {
1094            if (!mHasCallback) return;
1095            try {
1096                mClient.onAutofillEvent(mWindowToken, id, event);
1097            } catch (RemoteException e) {
1098                Slog.e(TAG, "Error notifying client on change: id=" + id + ", event=" + event, e);
1099            }
1100        }
1101
1102        private void notifyUnavailableToClient() {
1103            if (mCurrentViewState == null) {
1104                // TODO(b/33197203): temporary sanity check; should never happen
1105                Slog.w(TAG, "notifyUnavailable(): mCurrentViewState is null");
1106                return;
1107            }
1108            notifyChangeToClient(mCurrentViewState.mId, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
1109        }
1110
1111        private void processResponseLocked(FillResponse response) {
1112            if (DEBUG) {
1113                Slog.d(TAG, "processResponseLocked(auth=" + response.getAuthentication()
1114                    + "):" + response);
1115            }
1116
1117            if (mCurrentViewState == null) {
1118                // TODO(b/33197203): temporary sanity check; should never happen
1119                Slog.w(TAG, "processResponseLocked(): mCurrentViewState is null");
1120                return;
1121            }
1122
1123            mCurrentResponse = response;
1124
1125            if (mCurrentResponse.getAuthentication() != null) {
1126                // Handle authentication.
1127                final Intent fillInIntent = createAuthFillInIntent(mStructure);
1128                mCurrentViewState.setResponse(mCurrentResponse, fillInIntent);
1129                return;
1130            }
1131
1132            if ((mFlags & FLAG_MANUAL_REQUEST) != 0 && response.getDatasets() != null
1133                    && response.getDatasets().size() == 1) {
1134                Slog.d(TAG, "autofilling manual request directly");
1135                autoFill(response.getDatasets().get(0));
1136                return;
1137            }
1138
1139            mCurrentViewState.setResponse(mCurrentResponse);
1140        }
1141
1142        void autoFill(Dataset dataset) {
1143            synchronized (mLock) {
1144                mAutoFilledDataset = dataset;
1145
1146                // Autofill it directly...
1147                if (dataset.getAuthentication() == null) {
1148                    autoFillApp(dataset);
1149                    return;
1150                }
1151
1152                // ...or handle authentication.
1153                Intent fillInIntent = createAuthFillInIntent(mStructure);
1154                startAuthentication(dataset.getAuthentication(), fillInIntent);
1155            }
1156        }
1157
1158        CharSequence getServiceName() {
1159            return AutofillManagerServiceImpl.this.getServiceName();
1160        }
1161
1162        private Intent createAuthFillInIntent(AssistStructure structure) {
1163            Intent fillInIntent = new Intent();
1164            fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, structure);
1165            return fillInIntent;
1166        }
1167
1168        private void startAuthentication(IntentSender intent, Intent fillInIntent) {
1169            try {
1170                mClient.authenticate(intent, fillInIntent);
1171            } catch (RemoteException e) {
1172                Slog.e(TAG, "Error launching auth intent", e);
1173            }
1174        }
1175
1176        void dumpLocked(String prefix, PrintWriter pw) {
1177            pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
1178            pw.print(prefix); pw.print("mFlags: "); pw.println(mFlags);
1179            pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse);
1180            pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset);
1181            pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState);
1182            pw.print(prefix); pw.print("mViewStates: "); pw.println(mViewStates.size());
1183            final String prefix2 = prefix + "  ";
1184            for (Map.Entry<AutofillId, ViewState> entry : mViewStates.entrySet()) {
1185                pw.print(prefix); pw.print("State for id "); pw.println(entry.getKey());
1186                entry.getValue().dump(prefix2, pw);
1187            }
1188            if (VERBOSE) {
1189                pw.print(prefix); pw.print("mStructure: " );
1190                // TODO(b/33197203): add method do dump AssistStructure on pw
1191                if (mStructure != null) {
1192                    pw.println("look at logcat" );
1193                    mStructure.dump(); // dumps to logcat
1194                } else {
1195                    pw.println("null");
1196                }
1197            }
1198            pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback);
1199            mRemoteFillService.dump(prefix, pw);
1200        }
1201
1202        void autoFillApp(Dataset dataset) {
1203            synchronized (mLock) {
1204                try {
1205                    if (DEBUG) {
1206                        Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset);
1207                    }
1208                    mClient.autofill(dataset.getFieldIds(), dataset.getFieldValues());
1209                } catch (RemoteException e) {
1210                    Slog.w(TAG, "Error autofilling activity: " + e);
1211                }
1212            }
1213        }
1214
1215        private AutoFillUI getUiForShowing() {
1216            synchronized (mLock) {
1217                mUi.setCallback(this, mWindowToken);
1218                return mUi;
1219            }
1220        }
1221
1222        private ViewNode findViewNodeByIdLocked(AutofillId id) {
1223            final int size = mStructure.getWindowNodeCount();
1224            for (int i = 0; i < size; i++) {
1225                final WindowNode window = mStructure.getWindowNodeAt(i);
1226                final ViewNode root = window.getRootViewNode();
1227                if (id.equals(root.getAutofillId())) {
1228                    return root;
1229                }
1230                final ViewNode child = findViewNodeByIdLocked(root, id);
1231                if (child != null) {
1232                    return child;
1233                }
1234            }
1235            return null;
1236        }
1237
1238        private ViewNode findViewNodeByIdLocked(ViewNode parent, AutofillId id) {
1239            final int childrenSize = parent.getChildCount();
1240            if (childrenSize > 0) {
1241                for (int i = 0; i < childrenSize; i++) {
1242                    final ViewNode child = parent.getChildAt(i);
1243                    if (id.equals(child.getAutofillId())) {
1244                        return child;
1245                    }
1246                    final ViewNode grandChild = findViewNodeByIdLocked(child, id);
1247                    if (grandChild != null && id.equals(grandChild.getAutofillId())) {
1248                        return grandChild;
1249                    }
1250                }
1251            }
1252            return null;
1253        }
1254
1255        private void destroyLocked() {
1256            mRemoteFillService.destroy();
1257            mUi.setCallback(null, null);
1258
1259            mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_SESSION_FINISHED,
1260                    mPackageName);
1261        }
1262
1263        void removeSelf() {
1264            synchronized (mLock) {
1265                removeSelfLocked();
1266            }
1267        }
1268
1269        private void removeSelfLocked() {
1270            if (VERBOSE) {
1271                Slog.v(TAG, "removeSelfLocked()");
1272            }
1273            destroyLocked();
1274            mSessions.remove(mActivityToken);
1275        }
1276    }
1277}
1278