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