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